| Option | Best for |
|---|---|
| Tero Distro | New deployments or replacing your existing collector |
| Edge Proxy | Adding to an existing collector without rebuilding |
| Policy Processor | Custom collector distributions you build yourself |
- Tero Distro
- Edge Proxy
- Policy Processor
Run the Tero Collector—a pre-built OpenTelemetry Collector distribution with the Policy Processor included.
How it works
The Tero Collector is a standard OTel Collector with our Policy Processor baked in. Deploy it like any collector, configure policies, and it filters logs before they reach your backend.The Policy Processor supports three actions:- Drop — Remove logs matching patterns
- Keep — Retain only logs matching patterns
- Sample — Probabilistically sample at configurable rates
Included components
The Tero Collector includes standard OTel components:| Type | Components |
|---|---|
| Receivers | OTLP (gRPC, HTTP) |
| Processors | Policy, Batch, Memory Limiter, Attributes, Filter, Resource |
| Exporters | Debug, OTLP (gRPC, HTTP) |
| Connectors | Forward |
| Extensions | Health Check v2, zPages, PProf, Basic Auth, Bearer Token Auth |
Prerequisites
- Docker or Kubernetes cluster
- Tero account
Deploy
Create an API key
Open your terminal and run:Navigate to Edge → API Keys → Create. Name your key (e.g., “Tero Collector”). Copy the key when shown—it’s only displayed once.
Copy
tero
Deploy the collector
- Docker
- Kubernetes (Operator)
- Kubernetes (Helm)
Create a collector config:Run the collector:
config.yaml
Copy
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
policy:
providers:
- type: http
id: tero
url: https://sync.usetero.com/v1/policy/sync
headers:
- name: Authorization
value: Bearer ${TERO_API_KEY}
poll_interval_secs: 60
exporters:
otlphttp:
endpoint: https://your-backend-endpoint.com
service:
pipelines:
logs:
receivers: [otlp]
processors: [policy]
exporters: [otlphttp]
Copy
docker run --rm -p 4317:4317 -p 4318:4318 \
-e TERO_API_KEY=YOUR_API_KEY \
-v $(pwd)/config.yaml:/etc/tero-collector/config.yaml:ro \
ghcr.io/usetero/tero-collector-distro:latest
Create the namespace and secret:Deploy the collector:
Copy
kubectl create namespace observability
kubectl create secret generic tero-collector \
--namespace observability \
--from-literal=api-key=YOUR_API_KEY
tero-collector.yaml
Copy
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
name: tero-collector
namespace: observability
spec:
mode: deployment
image: ghcr.io/usetero/tero-collector-distro:latest
env:
- name: TERO_API_KEY
valueFrom:
secretKeyRef:
name: tero-collector
key: api-key
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
policy:
providers:
- type: http
id: tero
url: https://sync.usetero.com/v1/policy/sync
headers:
- name: Authorization
value: Bearer ${TERO_API_KEY}
poll_interval_secs: 60
exporters:
otlphttp:
endpoint: https://your-backend-endpoint.com
service:
pipelines:
logs:
receivers: [otlp]
processors: [policy]
exporters: [otlphttp]
Copy
kubectl apply -f tero-collector.yaml
Create the namespace and secret:Add to your Install the chart:
Copy
kubectl create namespace observability
kubectl create secret generic tero-collector \
--namespace observability \
--from-literal=api-key=YOUR_API_KEY
values.yaml:values.yaml
Copy
mode: deployment
image:
repository: ghcr.io/usetero/tero-collector-distro
tag: latest
extraEnvs:
- name: TERO_API_KEY
valueFrom:
secretKeyRef:
name: tero-collector
key: api-key
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
policy:
providers:
- type: http
id: tero
url: https://sync.usetero.com/v1/policy/sync
headers:
- name: Authorization
value: Bearer ${TERO_API_KEY}
poll_interval_secs: 60
exporters:
otlphttp:
endpoint: https://your-backend-endpoint.com
service:
pipelines:
logs:
receivers: [otlp]
processors: [policy]
exporters: [otlphttp]
Copy
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm install tero-collector open-telemetry/opentelemetry-collector \
--namespace observability \
-f values.yaml
Verify
- Docker
- Kubernetes (Operator)
- Kubernetes (Helm)
Check the container is running:Check logs for policy sync:
Copy
docker ps | grep tero-collector
Copy
docker logs <container-id> 2>&1 | grep -i policy
Copy
kubectl get pods -n observability -l app.kubernetes.io/name=tero-collector-collector
kubectl logs -n observability -l app.kubernetes.io/name=tero-collector-collector | grep -i policy
Copy
kubectl get pods -n observability -l app.kubernetes.io/instance=tero-collector
kubectl logs -n observability -l app.kubernetes.io/instance=tero-collector | grep -i policy
Policy providers
Configure where the collector loads policies from.File provider
Load from a local file. Good for static policies or GitOps workflows.Copy
processors:
policy:
providers:
- type: file
id: local
path: /etc/tero-collector/policies.json
poll_interval_secs: 30
HTTP provider
Fetch from a remote endpoint. Good for dynamic policies managed via Tero.Copy
processors:
policy:
providers:
- type: http
id: tero
url: https://sync.usetero.com/v1/policy/sync
headers:
- name: Authorization
value: Bearer ${TERO_API_KEY}
poll_interval_secs: 60
Run Edge as a sidecar to your existing OpenTelemetry Collector. Edge proxies telemetry, applies policies, and forwards to your backend.
How it works
Edge runs alongside your OTel Collector. The collector exports telemetry through Edge, which applies policies and forwards to your backend.Prerequisites
- Existing OpenTelemetry Collector
- Tero account
Deploy
Create an API key
Open your terminal and run:Navigate to Edge → API Keys → Create. Name your key (e.g., “OTel Collector”). Copy the key when shown—it’s only displayed once.
Copy
tero
Deploy Edge alongside your collector
- Docker
- Kubernetes (Operator)
- Kubernetes (Helm)
Create an Edge config:Run Edge:Configure your collector to export to Edge:
edge-config.json
Copy
{
"listen_address": "0.0.0.0",
"listen_port": 8080,
"upstream_url": "https://your-backend-endpoint.com",
"log_level": "info",
"max_body_size": 1048576,
"policy_providers": [
{
"id": "tero",
"type": "http",
"url": "https://sync.usetero.com/v1/policy/sync",
"headers": [
{ "name": "Authorization", "value": "Bearer ${TERO_API_KEY}" }
],
"poll_interval_secs": 60
}
]
}
Copy
docker run --rm -p 8080:8080 \
-e TERO_API_KEY=YOUR_API_KEY \
-v $(pwd)/edge-config.json:/etc/tero/config.json:ro \
ghcr.io/usetero/edge:latest /etc/tero/config.json
Copy
exporters:
otlphttp:
endpoint: http://localhost:8080
Create the secret and ConfigMap:Add Edge as a sidecar to your
Copy
kubectl create secret generic tero-edge \
--from-literal=api-key=YOUR_API_KEY
tero-edge-config.yaml
Copy
apiVersion: v1
kind: ConfigMap
metadata:
name: tero-edge-config
data:
config.json: |
{
"listen_address": "127.0.0.1",
"listen_port": 8080,
"upstream_url": "https://your-backend-endpoint.com",
"log_level": "info",
"max_body_size": 1048576,
"policy_providers": [
{
"id": "tero",
"type": "http",
"url": "https://sync.usetero.com/v1/policy/sync",
"headers": [
{ "name": "Authorization", "value": "Bearer ${TERO_API_KEY}" }
],
"poll_interval_secs": 60
}
]
}
Copy
kubectl apply -f tero-edge-config.yaml
OpenTelemetryCollector CR:Copy
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
name: otel-collector
spec:
mode: deployment
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
otlphttp:
endpoint: http://localhost:8080
service:
pipelines:
logs:
receivers: [otlp]
exporters: [otlphttp]
metrics:
receivers: [otlp]
exporters: [otlphttp]
traces:
receivers: [otlp]
exporters: [otlphttp]
volumes:
- name: tero-edge-config
configMap:
name: tero-edge-config
volumeMounts:
- name: tero-edge-config
mountPath: /etc/tero
readOnly: true
additionalContainers:
- name: tero-edge
image: ghcr.io/usetero/edge:latest
args:
- /etc/tero/config.json
env:
- name: TERO_API_KEY
valueFrom:
secretKeyRef:
name: tero-edge
key: api-key
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 200m
memory: 64Mi
volumeMounts:
- name: tero-edge-config
mountPath: /etc/tero
readOnly: true
livenessProbe:
httpGet:
path: /_health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /_health
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
Create the secret and ConfigMap:Add to your collector
Copy
kubectl create secret generic tero-edge \
--from-literal=api-key=YOUR_API_KEY
tero-edge-config.yaml
Copy
apiVersion: v1
kind: ConfigMap
metadata:
name: tero-edge-config
data:
config.json: |
{
"listen_address": "127.0.0.1",
"listen_port": 8080,
"upstream_url": "https://your-backend-endpoint.com",
"log_level": "info",
"max_body_size": 1048576,
"policy_providers": [
{
"id": "tero",
"type": "http",
"url": "https://sync.usetero.com/v1/policy/sync",
"headers": [
{ "name": "Authorization", "value": "Bearer ${TERO_API_KEY}" }
],
"poll_interval_secs": 60
}
]
}
Copy
kubectl apply -f tero-edge-config.yaml
values.yaml:Copy
extraVolumes:
- name: tero-edge-config
configMap:
name: tero-edge-config
extraVolumeMounts:
- name: tero-edge-config
mountPath: /etc/tero
readOnly: true
extraContainers:
- name: tero-edge
image: ghcr.io/usetero/edge:latest
args:
- /etc/tero/config.json
env:
- name: TERO_API_KEY
valueFrom:
secretKeyRef:
name: tero-edge
key: api-key
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 200m
memory: 64Mi
volumeMounts:
- name: tero-edge-config
mountPath: /etc/tero
readOnly: true
livenessProbe:
httpGet:
path: /_health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /_health
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
otlphttp:
endpoint: http://localhost:8080
service:
pipelines:
logs:
receivers: [otlp]
exporters: [otlphttp]
metrics:
receivers: [otlp]
exporters: [otlphttp]
traces:
receivers: [otlp]
exporters: [otlphttp]
Verify
- Docker
- Kubernetes (Operator)
- Kubernetes (Helm)
Check Edge is running:
Copy
docker ps | grep edge
curl http://localhost:8080/_health
Check that the collector pod has both containers running:
Copy
kubectl get pods -l app.kubernetes.io/name=otel-collector
kubectl logs <collector-pod> -c tero-edge --tail=50
Check that the collector pod has both containers running:
Copy
kubectl get pods -l app.kubernetes.io/instance=otel-collector
kubectl logs <collector-pod> -c tero-edge --tail=50
Policy providers
Edge supports multiple policy sources. Configure them in thepolicy_providers array.File provider
Load policies from a local file. Good for static policies.Copy
{
"id": "file",
"type": "file",
"path": "/etc/tero/policies.json"
}
HTTP provider
Fetch policies from a remote endpoint. Good for dynamic policies managed via Tero.Copy
{
"id": "tero",
"type": "http",
"url": "https://sync.usetero.com/v1/policy/sync",
"headers": [{ "name": "Authorization", "value": "Bearer ${TERO_API_KEY}" }],
"poll_interval_secs": 60
}
Add the Policy Processor to your own custom OpenTelemetry Collector distribution using the OpenTelemetry Collector Builder (OCB).The built binary will be in
How it works
Build a custom collector with the Policy Processor included. This gives you full control over which components to include while adding Tero’s filtering capabilities.The Policy Processor uses Hyperscan for high-performance regex matching. This requires CGO and platform-specific libraries.
Prerequisites
- Go 1.24+
- OpenTelemetry Collector Builder (ocb)
- CGO-compatible build environment
- Hyperscan/Vectorscan libraries
- Tero account
Install Hyperscan
- macOS
- Ubuntu/Debian
- Alpine
Copy
brew install vectorscan
Copy
apt-get install libhyperscan-dev
Copy
apk add vectorscan-dev
Build
Create an API key
Open your terminal and run:Navigate to Edge → API Keys → Create. Name your key (e.g., “Custom Collector”). Copy the key when shown—it’s only displayed once.
Copy
tero
Create the OCB manifest
manifest.yaml
Copy
dist:
name: my-collector
description: Custom OTel Collector with Policy Processor
output_path: ./build
otelcol_version: 0.115.0
# Required for Hyperscan
cgo_enabled: true
receivers:
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.115.0
processors:
- gomod: github.com/usetero/tero-collector-distro/processor/policyprocessor v0.2.0
exporters:
- gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.115.0
- gomod: go.opentelemetry.io/collector/exporter/otlphttpexporter v0.115.0
extensions:
- gomod: go.opentelemetry.io/collector/extension/healthcheckextension v0.115.0
cgo_enabled: true is required. Without it, the build will fail due to Hyperscan dependencies.Build the collector
Copy
ocb --config manifest.yaml
./build/my-collector.Create the collector config
config.yaml
Copy
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
policy:
providers:
- type: http
id: tero
url: https://sync.usetero.com/v1/policy/sync
headers:
- name: Authorization
value: Bearer ${TERO_API_KEY}
poll_interval_secs: 60
exporters:
otlphttp:
endpoint: https://your-backend-endpoint.com
service:
pipelines:
logs:
receivers: [otlp]
processors: [policy]
exporters: [otlphttp]
Deploy
- Binary
- Docker
- Kubernetes (Operator)
- Kubernetes (Helm)
Copy
export TERO_API_KEY=YOUR_API_KEY
./build/my-collector --config config.yaml
Create a Dockerfile:Build and run:
Dockerfile
Copy
FROM golang:1.24-bookworm AS builder
# Install Hyperscan
RUN apt-get update && apt-get install -y libhyperscan-dev
# Install OCB
RUN go install go.opentelemetry.io/collector/cmd/builder@latest
WORKDIR /build
COPY manifest.yaml .
# Build with CGO enabled
ENV CGO_ENABLED=1
RUN builder --config manifest.yaml
FROM debian:bookworm-slim
# Install runtime dependencies
RUN apt-get update && apt-get install -y libhyperscan5 ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /build/build/my-collector /collector
COPY config.yaml /etc/collector/config.yaml
ENTRYPOINT ["/collector"]
CMD ["--config", "/etc/collector/config.yaml"]
Copy
docker build -t my-collector .
docker run --rm -p 4317:4317 -p 4318:4318 \
-e TERO_API_KEY=YOUR_API_KEY \
my-collector
After building and pushing your image, deploy with the OpenTelemetry Operator:
Copy
kubectl create namespace observability
kubectl create secret generic tero-collector \
--namespace observability \
--from-literal=api-key=YOUR_API_KEY
my-collector.yaml
Copy
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
name: my-collector
namespace: observability
spec:
mode: deployment
image: your-registry/my-collector:latest
env:
- name: TERO_API_KEY
valueFrom:
secretKeyRef:
name: tero-collector
key: api-key
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
policy:
providers:
- type: http
id: tero
url: https://sync.usetero.com/v1/policy/sync
headers:
- name: Authorization
value: Bearer ${TERO_API_KEY}
poll_interval_secs: 60
exporters:
otlphttp:
endpoint: https://your-backend-endpoint.com
service:
pipelines:
logs:
receivers: [otlp]
processors: [policy]
exporters: [otlphttp]
Copy
kubectl apply -f my-collector.yaml
After building and pushing your image, deploy with Helm:
Copy
kubectl create namespace observability
kubectl create secret generic tero-collector \
--namespace observability \
--from-literal=api-key=YOUR_API_KEY
values.yaml
Copy
mode: deployment
image:
repository: your-registry/my-collector
tag: latest
extraEnvs:
- name: TERO_API_KEY
valueFrom:
secretKeyRef:
name: tero-collector
key: api-key
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
policy:
providers:
- type: http
id: tero
url: https://sync.usetero.com/v1/policy/sync
headers:
- name: Authorization
value: Bearer ${TERO_API_KEY}
poll_interval_secs: 60
exporters:
otlphttp:
endpoint: https://your-backend-endpoint.com
service:
pipelines:
logs:
receivers: [otlp]
processors: [policy]
exporters: [otlphttp]
Copy
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm install my-collector open-telemetry/opentelemetry-collector \
--namespace observability \
-f values.yaml
Policy configuration
The processor accepts aproviders array for policy sources.Provider types
| Type | Description |
|---|---|
file | Load from local file |
http | Fetch from HTTP endpoint |
grpc | Fetch from gRPC endpoint |
Provider fields
| Field | Type | Description |
|---|---|---|
type | string | Provider type: file, http, or grpc |
id | string | Unique identifier for this provider |
path | string | File path (file provider only) |
url | string | Remote endpoint (http/grpc only) |
poll_interval_secs | int | How often to check for updates |
headers | array | HTTP headers (http provider only) |
Telemetry support
| Signal | Status |
|---|---|
| Logs | Alpha |
| Metrics | Not yet supported |
| Traces | Not yet supported |
Metrics
The processor emitsprocessor_policy_records (Counter) with attributes:telemetry_type:logs,metrics, ortracesresult:dropped,kept,sampled, orno_match
Example policies
Policies use JSON format. Here are common patterns:Copy
{
"policies": [
{
"id": "drop-debug-logs",
"name": "drop-debug-logs",
"enabled": true,
"log": {
"match": [{ "log_field": "severity_text", "regex": "DEBUG" }],
"keep": "none"
}
},
{
"id": "sample-noisy-service",
"name": "sample-noisy-service",
"enabled": true,
"log": {
"match": [
{ "resource_attribute": "service.name", "regex": "noisy-service" }
],
"sample_rate": 0.1
}
},
{
"id": "keep-errors",
"name": "keep-errors",
"enabled": true,
"log": {
"match": [
{ "log_field": "severity_text", "regex": "ERROR" },
{ "log_field": "severity_text", "regex": "CRITICAL" }
],
"keep": "all"
}
}
]
}
Troubleshooting
Collector won’t start Check the config syntax:Copy
./collector --config config.yaml --dry-run
CGO_ENABLED=1 is set. On macOS,
you may need to set PKG_CONFIG_PATH:
Copy
export PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH"