---
title: Logs, traces and metrics
description: Structured logs, OTLP trace export and a Prometheus registry for an axum service.
order: 2
---

Feature: `observability`.

## Logs and traces

```rust
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).

```rust
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));
```

- `track` is 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)`.
- `serve` answers `GET /metrics` in the Prometheus text format. Keep it off your public
  domain: route only the paths your users need.
- `render()` returns the same text as a `String`, 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:

```rust
metrics::set_gauge("queue_depth", "Jobs waiting in the queue", 42.0);
let now = metrics::gauge("queue_depth"); // Some(42.0)
```
