| 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.
tero
Deploy the collector
- Docker
- Kubernetes (Operator)
- Kubernetes (Helm)
Create a collector config:Run the collector:
config.yaml
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]
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:
kubectl create namespace observability
kubectl create secret generic tero-collector \
--namespace observability \
--from-literal=api-key=YOUR_API_KEY
tero-collector.yaml
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]
kubectl apply -f tero-collector.yaml
Create the namespace and secret:Add to your Install the chart:
kubectl create namespace observability
kubectl create secret generic tero-collector \
--namespace observability \
--from-literal=api-key=YOUR_API_KEY
values.yaml:values.yaml
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]
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:
docker ps | grep tero-collector
docker logs <container-id> 2>&1 | grep -i policy
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
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.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.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.
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
{
"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
}
]
}
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
exporters:
otlphttp:
endpoint: http://localhost:8080
Create the secret and ConfigMap:Add Edge as a sidecar to your
kubectl create secret generic tero-edge \
--from-literal=api-key=YOUR_API_KEY
tero-edge-config.yaml
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
}
]
}
kubectl apply -f tero-edge-config.yaml
OpenTelemetryCollector CR: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
kubectl create secret generic tero-edge \
--from-literal=api-key=YOUR_API_KEY
tero-edge-config.yaml
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
}
]
}
kubectl apply -f tero-edge-config.yaml
values.yaml: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:
docker ps | grep edge
curl http://localhost:8080/_health
Check that the collector pod has both containers running:
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:
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.{
"id": "file",
"type": "file",
"path": "/etc/tero/policies.json"
}
HTTP provider
Fetch policies from a remote endpoint. Good for dynamic policies managed via Tero.{
"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).
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
brew install vectorscan
apt-get install libhyperscan-dev
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.
tero
Create the OCB manifest
manifest.yaml
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.Create the collector config
config.yaml
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)
export TERO_API_KEY=YOUR_API_KEY
./build/my-collector --config config.yaml
Create a Dockerfile:Build and run:
Dockerfile
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"]
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:
kubectl create namespace observability
kubectl create secret generic tero-collector \
--namespace observability \
--from-literal=api-key=YOUR_API_KEY
my-collector.yaml
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]
kubectl apply -f my-collector.yaml
After building and pushing your image, deploy with Helm:
kubectl create namespace observability
kubectl create secret generic tero-collector \
--namespace observability \
--from-literal=api-key=YOUR_API_KEY
values.yaml
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]
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:{
"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:./collector --config config.yaml --dry-run
CGO_ENABLED=1 is set. On macOS,
you may need to set PKG_CONFIG_PATH:
export PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH"