headgate-otel
Rust crate and headgateotel Go module translate worker events into OpenTelemetry traces
and metrics.
The integration has two distinct parts:
- Attach the adapter to the Rust worker or Go runner so completed attempts and worker signals are exported.
- Attach trace-context middleware to the producer client so the executing job span is a child of the request or operation that enqueued it.
The OpenTelemetry adapter belongs on the worker or runner, not on the producer client.
The client uses enqueue middleware only for propagation.
headgate-otel does not
install an SDK, choose a sampler, configure an exporter, or replace a global provider.Install
Configure providers and an exporter
The application owns the providers because it usually needs one resource and one export pipeline for HTTP, database, and job telemetry. The examples below use OTLP and standard environment variables, so the same binary can send to an OpenTelemetry Collector or a vendor endpoint.Rust providers
OTEL_EXPORTER_OTLP_ENDPOINT, signal-specific endpoints, headers, timeout,
compression, and protocol from the environment. Configure those before constructing the
exporters.
Go providers
autoexport selects exporters from OTEL_TRACES_EXPORTER and
OTEL_METRICS_EXPORTER; both default to OTLP. Set either value to none to disable that
signal without changing application code.
Attach telemetry to execution
This is the step that makes Headgate emit signals. Creating providers alone is not enough.nil to the Go adapter for either provider uses OpenTelemetry’s current global
provider. This is useful when application bootstrap already installs globals, but a no-op
global provider silently produces no telemetry. Passing providers explicitly makes the
dependency visible and is easier to test.
Connect producer and worker traces
Headgate reserves thetraceparent and tracestate envelope headers. The worker parses
them at dispatch and uses a valid context as the remote parent of headgate.process.
Missing or invalid context starts a root span and never makes the job undecodable.
Install a W3C propagator once during application startup, then inject the current context
into every envelope in the enqueue batch.
Rust producer middleware
This example assumes the application usestracing-opentelemetry, so
tracing::Span::current().context() returns the current OpenTelemetry context.
Go producer middleware
traceparent.
Execution span
Everyjob_span runtime event becomes one span with these semantics:
Span attributes:
The job payload and result are never attached. They can contain credentials, personal
data, or large documents and should be inspected through access-controlled application
tools instead.
Metrics
Exporter and backend naming rules may translate dots to underscores or append suffixes
such as
_total. Inspect the names in your backend before copying a query verbatim.
Useful dashboards and alerts
Start with operational questions rather than raw queue depth:- Why are jobs not starting? Graph the rejection rate by
headgate.policyand queue. - Are workers saturated? Compare utilization, inflight, and capacity by worker.
- Is polling wasteful? Alert on a sustained high empty-poll ratio, not a single sample.
- Are attempts slowing down? Graph duration percentiles by kind, queue, and outcome.
- Is the memory guard cycling workers? Correlate memory, memory limit, and restart count.
- Is the fleet falling behind? Combine runtime metrics with the control API’s arrival rate, drain rate, oldest-job age, and time-to-drain. Those durable aggregates are not emitted by this process-local adapter.
Cardinality and privacy
Job IDs appear on spans because spans are sampled event records. They never appear on metric attributes. Fingerprints, partition keys, tenant IDs, and payload fields are also excluded from metric attributes.Shutdown and flushing
Stop admission and let the worker or runner finish its bounded graceful shutdown first. Then shut down the tracer provider and meter provider. Providers commonly buffer spans and metrics; exiting without shutdown can lose the final attempts from a deployment. In Go, use a fresh bounded context for provider shutdown if the runner’s context has already been cancelled. In Rust, keep both provider values alive untilworker.run()
returns.
Troubleshooting
No spans or metrics arrive
- Confirm
Telemetryis set onWorkerConfigorheadgate.Config. - Confirm the providers have a real exporter and are not no-op globals.
- Check
OTEL_EXPORTER_OTLP_ENDPOINT, protocol, TLS, and authentication headers. - Shut providers down during a local test to force buffered data to flush.
Job spans appear as separate traces
Inspect the stored envelope headers. The producer must inject a valid W3Ctraceparent.
Also confirm the enqueue call receives the context containing the active request span.
Creating the client once is fine; passing context.Background() to enqueue is not.
Metrics arrive but worker gauges do not
Worker gauges are emitted from runtime saturation and memory sampling events. Confirm the runner has started and that your metric reader’s collection interval has elapsed.Duplicate-looking duration measurements
headgate.job.duration is recorded from both the compact completion event and the richer
attempt-span event, with different attribute sets. Select the attribute form you need in
the dashboard instead of summing both shapes indiscriminately.
Payload data is missing from traces
This is intentional. Payloads and results are not telemetry attributes. Add a bounded, non-sensitive business identifier in application instrumentation when correlation needs more than the Headgate job ID.Connection budgets
Keep lease renewal and heartbeat traffic moving while handlers hold transactions.