Packages / ops-sdk
Logs, traces and metrics
Structured logs, OTLP trace export and a Prometheus registry for an axum service.
Feature: observability.
Logs and traces
ops_sdk::observability::init_tracing("web");
Call it once, inside the Tokio runtime, right after env::load(). It installs the process
wide tracing subscriber.
| variable | effect |
|---|---|
LOG_FORMAT=json | one JSON object per line, which is what ops's log search reads best. Anything else prints a readable format for your terminal |
RUST_LOG | the filter, for example info,sqlx=warn. The default is info |
OTEL_EXPORTER_OTLP_ENDPOINT | when set, spans are also exported over OTLP (HTTP, protobuf), batched |
OTEL_SERVICE_NAME | the service name on the spans. The default is the name you passed to init_tracing |
If the trace exporter cannot be built, the service still starts and prints one line to stderr. Telemetry trouble never takes the service down.
Spans and log lines carry only what your code puts into them. Keep personal data such as emails and names out of fields; record ids instead.
Metrics
observability::metrics is a small in process Prometheus registry. It counts requests and
times them by method, route template and status class (2xx, 4xx and so on).
use axum::{Router, middleware, routing::get};
use ops_sdk::observability::metrics;
let app = Router::new()
.route("/orders/{id}", get(order))
.route("/metrics", get(metrics::serve))
.layer(middleware::from_fn(metrics::track));
trackis the middleware. It labels a request with the route template (/orders/{id}), never the concrete path, so the number of series stays small. A request no route matched is counted as(unmatched).serveanswersGET /metricsin the Prometheus text format. Keep it off your public domain: route only the paths your users need.render()returns the same text as aString, for a test or a server that is not axum.
The output always holds process_uptime_seconds, http_requests_total and the
http_request_duration_seconds histogram.
Gauges
Set a named level and it is rendered with the rest:
metrics::set_gauge("queue_depth", "Jobs waiting in the queue", 42.0);
let now = metrics::gauge("queue_depth"); // Some(42.0)