Observability
What the broker emits
The broker emits structured logs, counters, duration histograms and request traces. Docker writes JSON logs to stdout (debug/info) and stderr (warn/error), and metric JSON lines to stdout. Workers write structured console records to Workers Logs and metrics to the configured Analytics Engine binding. OTLP export adds another output on either runtime. Audit events use their own persistent store; these outputs do not replace it.
OTLP resource attributes are service.name=package-broker, deployment.id from
DEPLOYMENT_ID, and process.runtime.name=node or cloudflare. The runtime wiring
does not currently set service.version. DEPLOYMENT_ID must contain 1–64 ASCII
letters, digits, underscores, dots or hyphens.
Request logs use the event http.request and include request_id, trace_id,
span_id, method, registered route template, status and duration where available.
token_id identifies the authenticated token record, never the token credential;
it is null before token authentication in local logs and omitted when null in
OTLP. It is a log attribute, not a metric label. Local logs call the deployment
field deployment_id. Errors contain approved names and reason codes rather than
request data or exception messages.
The runtime derives a server span from each emitted request completion log, named
METHOD <route template> (or just the method for an unmatched route). Successful
health checks log at debug, so the default info level also suppresses their spans.
Set the build-config key observability.level to change the log level.
Inbound traceparent is trusted only when build configuration sets
observability.trustInboundTraceparent: true. The broker exports one server span
per request completion that passes the log-level filter. Each upstream request
inside it, redirects included, adds one upstream.fetch client span with the
server span as parent. Its attributes are upstream.origin (scheme, host and
port of the first hop the outbound policy accepted), method, status (final
response only), redirects, cross_origin, duration_ms and, on failure,
outcome. Refused targets are never recorded. The span ends when response
headers arrive, so failures while reading the body are not reflected. Upstream
spans are exported regardless of the log level. Background jobs export no
upstream spans. No configuration enables upstream traceparent propagation in
this release. Upstream activity also appears as pb.upstream.requests and
pb.upstream.duration_ms metrics. Cloudflare's platform tracing is a separate
source of spans; see Workers tracing.
Metrics include pb.http.requests, pb.http.duration_ms, cache/blob operations,
job execution and upstream counters and histograms. OTLP sums and histograms use
delta temporality. pb.obs.export_failures counts unsuccessful exports. On Node,
this counter goes to the OTLP sink, so an unavailable Collector can also prevent
its delivery. On Workers, it goes to the Workers sink and its configured Analytics
Engine dataset. Delivery is bounded and best effort; confirm ingestion in the
backend, not just an HTTP success response.
Turn on OTLP export
The broker sends OTLP/HTTP JSON, with Content-Type: application/json, to a
base URL plus /v1/logs, /v1/metrics or /v1/traces. Do not include a signal path
in the broker endpoint. It does not send gRPC or HTTP/protobuf.
Each POST has a 1500 ms deadline. Redirects are never followed. Non-2xx responses
and partialSuccess responses with rejected items count as export failures.
Between flushes, each signal buffers at most 500 records and 512 KiB. Records
that exceed either limit are dropped and counted in pb.obs.dropped, not
pb.obs.export_failures.
Environment variable names are chosen at build time. Add these fields to the
existing platformPorts.node.observability or
platformPorts.cloudflare.observability stanza in broker.config.ts, retaining
its other fields:
// Node
observability: {
adapter: 'stdio',
deploymentIdEnv: 'DEPLOYMENT_ID',
endpointEnv: 'PB_OTLP_ENDPOINT',
headersEnv: 'PB_OTLP_HEADERS',
},
// Workers
observability: {
adapter: 'workers',
analyticsBinding: 'ANALYTICS',
deploymentIdEnv: 'DEPLOYMENT_ID',
endpointEnv: 'PB_OTLP_ENDPOINT',
headersEnv: 'PB_OTLP_HEADERS',
},
Rebuild and redeploy for the target runtime. PB_OTLP_ENDPOINT and
PB_OTLP_HEADERS are the exact names selected by this example, not built-in
variables. Setting OTEL_EXPORTER_OTLP_ENDPOINT alone has no effect. Without
endpointEnv, OTLP is disabled. If endpointEnv is configured, its variable must
be set. If headersEnv is configured, its variable must hold a JSON object whose
values are strings; use {} for an unauthenticated receiver. To omit the headers
variable entirely, also omit headersEnv at build time. This format differs from
the comma-separated header format used by some OpenTelemetry SDKs. For an
authenticated ingress, populate that JSON object with its required headers from
your deployment's secret configuration. Do not put credentials in the URL.
Header names must match [A-Za-z0-9-]{1,64}. Values must contain 0–4096 printable
ASCII characters (space through ~); control characters and non-ASCII characters
are refused. Names are case-insensitive and duplicate names are refused. These
names are also refused: traceparent, tracestate, baggage, host,
content-length, content-type, connection, keep-alive, proxy-authenticate,
proxy-authorization, te, trailer, transfer-encoding, upgrade. The broker
sets Content-Type itself. Invalid endpoint or header-set validation on Node raises
StartupConfigError with PB_OTLP_ENDPOINT is not a valid OTLP endpoint or header set
for these example variable names. A malformed header name or value in
PB_OTLP_HEADERS is therefore reported under the endpoint variable name,
PB_OTLP_ENDPOINT. Missing variables and malformed JSON header objects have their
own startup errors under the affected variable name.
Docker
Set these variables on the broker container:
DEPLOYMENT_ID=broker-production
PB_OTLP_ENDPOINT=http://127.0.0.1:4318
PB_OTLP_HEADERS='{}'
Run the Collector in the broker's network namespace (for example with Docker's
--network container:<broker-container>), so loopback reaches its HTTP receiver.
The broker permits plain HTTP only for loopback in its current runtime wiring.
For a Collector in a separate network namespace, use an HTTPS ingress with a
trusted certificate, for example https://collector.example.internal, instead
of a plain HTTP container service address. Keep the receiver on a private network
and expose only the ports your deployment needs.
Workers
Export the endpoint and headers variables in the shell that runs
pb cloudflare setup or pb cloudflare deploy. With the build configuration
above, use PB_OTLP_ENDPOINT=https://collector.example.internal and populate
PB_OTLP_HEADERS with the ingress's JSON header object (or {} for an
unauthenticated ingress). Pass --secret PB_OTLP_HEADERS to both commands, along
with your other deployment options. The CLI owns these bindings: it stores the
endpoint as a plain Worker var and the headers as a Worker secret, and validates
the endpoint and supplied headers before deployment. It also manages
DEPLOYMENT_ID and the configured Analytics Engine binding.
The endpoint shell variable must be present on every deploy; a missing or empty
value fails validation. On pb cloudflare deploy, headers validation and secret
updates apply only when --secret PB_OTLP_HEADERS is passed. With that flag,
a missing headers shell variable retains an existing secret; deployment fails
validation if no such secret exists. Without the flag, deploy neither validates
nor touches the headers secret. A missing secret then surfaces at request time as
a KernelConfigError.
pb cloudflare setup sets the headers secret on first setup only; rerunning setup
retains the existing secret. To rotate it, export PB_OTLP_HEADERS with the new
value and run pb cloudflare deploy --secret PB_OTLP_HEADERS with your other
deployment options.
Use an HTTPS ingress reachable from the Worker and forward its three /v1/*
paths to the Collector. See Workers ingress authentication
for a receiver with TLS and bearer authentication.
Cloudflare can also export platform Workers logs and traces to OTLP destinations configured in its dashboard. That path is independent of the broker's port configuration; enabling both can duplicate logs. See Workers tracing.
Collector recipes
These files target OpenTelemetry Collector contrib 0.140.1. Each has an OTLP
HTTP receiver on 127.0.0.1:4318, followed by memory_limiter and batch processors.
Replace backend example.internal hostnames and provide trusted TLS certificates.
All credentials come from Collector environment variables; they are separate from
broker ingress headers. Authorization variables hold the complete header value,
including the scheme required by the backend or its authentication proxy.
Every recipe defines traces, metrics and logs pipelines. Signals the selected
backend does not take go to the nop exporter, which accepts and discards them,
so the broker never counts export failures for those signals. To retain more
signals, combine the required exporters and pipelines into one Collector config,
sharing one receiver and processor definitions. Run one recipe at a time when
using the same listening ports. Before deploying, validate each chosen file with
its referenced environment variables set:
otelcol-contrib validate --config docs/operations/otel-collector/tempo.yaml
Workers ingress authentication
Use ingress-auth.yaml as the receiver and
extension configuration for a Collector behind an HTTPS ingress. Keep the
Collector on loopback and run the ingress in the same network namespace. Forward
all three signal paths over TLS, preserve Authorization, and verify the
Collector certificate. Set OTLP_TLS_CERT_FILE and OTLP_TLS_KEY_FILE to readable
PEM certificate and private-key paths, and OTLP_INGRESS_TOKEN to the bearer token
on the Collector. The receiver listens on loopback, so its TLS certificate must
be valid for the name the ingress uses to reach it (127.0.0.1 or localhost).
Receiver configuration:
receivers:
otlp:
protocols:
http:
endpoint: 127.0.0.1:4318
tls:
cert_file: ${env:OTLP_TLS_CERT_FILE}
key_file: ${env:OTLP_TLS_KEY_FILE}
auth:
authenticator: bearertokenauth
extensions:
bearertokenauth:
token: ${env:OTLP_INGRESS_TOKEN}
The standalone file enables bearertokenauth in service.extensions and sends
all three signals to nop for ingress validation. Replace its exporters and
pipelines with the selected backend recipe's exporters and pipelines to retain
telemetry. Keep its receiver, extension and service.extensions when combining.
Set the broker's PB_OTLP_HEADERS secret to a JSON object whose Authorization
value is Bearer followed by the same token as OTLP_INGRESS_TOKEN. Supply the
certificate/key files as well as environment variables when validating this file.
Grafana Tempo: traces
Use tempo.yaml. Enable Tempo's OTLP HTTP receiver on
port 4318 and make it available through the HTTPS endpoint shown in the file.
The otlphttp/tempo exporter appends /v1/traces. Set TEMPO_AUTHORIZATION on the
Collector for your authentication proxy; Tempo itself does not provide an
authentication layer.
Broker variables: DEPLOYMENT_ID, PB_OTLP_ENDPOINT, PB_OTLP_HEADERS, with the
Docker or Workers values above. The broker endpoint points to the Collector.
Grafana Loki: logs
Use loki.yaml. Loki's native OTLP ingestion accepts
logs at /otlp/v1/logs; otlphttp/loki uses the base /otlp. Enable structured
metadata in Loki. deployment.id lands in structured metadata unless Loki's
otlp_config promotes it to an index label. Set LOKI_AUTHORIZATION on the
Collector for your authentication proxy. For a multi-tenant deployment, add its X-Scope-OrgID header from an
${env:LOKI_TENANT_ID} reference. The removed loki exporter is not used.
Broker variables: DEPLOYMENT_ID, PB_OTLP_ENDPOINT, PB_OTLP_HEADERS, with the
Docker or Workers values above.
Elasticsearch: logs and traces
Use elasticsearch.yaml. The elasticsearch
exporter writes directly to Elasticsearch over HTTPS. Set
ELASTICSEARCH_API_KEY on the Collector to the encoded API key value (without an
ApiKey prefix) with permission to write the exporter's data streams. Use an
Elasticsearch deployment compatible with the explicitly selected
mapping: { mode: otel }; check the exporter README in contrib 0.140.1 for the
minimum Elasticsearch version required by OTel mode. These are OTel data streams,
not an Elastic APM Server ingestion recipe.
Broker variables: DEPLOYMENT_ID, PB_OTLP_ENDPOINT, PB_OTLP_HEADERS, with the
Docker or Workers values above.
Graylog: logs through OTLP
Use graylog.yaml. This recipe uses the Collector's
otlp/graylog gRPC exporter, not GELF. Create a Graylog OpenTelemetry (gRPC)
input on port 4317 in a Graylog version that supplies that input, enable TLS and
set GRAYLOG_CA_FILE on the Collector to the readable PEM CA certificate file
that validates the input's certificate. If an authentication proxy protects the input,
add headers: { Authorization: '${env:GRAYLOG_AUTHORIZATION}' } under
otlp/graylog and set that variable to its complete authorization header value.
This header is optional and absent from the supplied file. An older Graylog
deployment with only GELF inputs cannot receive this recipe's output.
Broker variables: DEPLOYMENT_ID, PB_OTLP_ENDPOINT, PB_OTLP_HEADERS, with the
Docker or Workers values above. Broker-to-Collector traffic remains HTTP JSON;
only Collector-to-Graylog traffic uses gRPC.
InfluxDB v2: metrics
Use influxdb.yaml. The influxdb exporter writes
metrics to the InfluxDB v2 API. Set INFLUXDB_ORG, INFLUXDB_BUCKET and
INFLUXDB_TOKEN on the Collector. Create the destination bucket and grant the
token write access to it. The broker exports delta sums and histograms, while the
exporter needs cumulative metrics; deltatocumulative converts them before the
influxdb exporter, as in the Prometheus recipe.
Broker variables: DEPLOYMENT_ID, PB_OTLP_ENDPOINT, PB_OTLP_HEADERS, with the
Docker or Workers values above.
Prometheus: scrape the Collector
Use prometheus.yaml. The deltatocumulative
processor converts the broker's delta sums and histograms for the prometheus
exporter. Resource attributes become metric labels, preserving the deployment
identifier on each series. Configure Prometheus to scrape the Collector:
scrape_configs:
- job_name: package-broker
static_configs:
- targets: ['127.0.0.1:8889']
The Collector serves /metrics at 127.0.0.1:8889; run Prometheus in the same
network namespace for the scrape target above. Exposing this listener beyond the
host requires protecting the scrape path with network policy or a proxy with
authentication, because the exporter has no authentication. No Collector backend
credentials are needed for this recipe. A native scrape endpoint is planned.
Broker variables: DEPLOYMENT_ID, PB_OTLP_ENDPOINT, PB_OTLP_HEADERS, with the
Docker or Workers values above.