Skip to content

Observability

Metrics

Built-in Prometheus endpoint

GET /metrics is always available and returns metrics in the Prometheus text exposition format. No configuration is required.

Server-wide counters updated by the request loop:

MetricTypeDescription
rws_requests_totalCounterTotal HTTP requests received
rws_errors_totalCounterTotal requests that produced a 5xx response
rws_active_connectionsGaugeNumber of currently open connections

Per-route metrics with MetricsLayer

Add MetricsLayer to any application to record per-route counters and latency histograms:

use rust_web_server::app::App;
use rust_web_server::core::New;
use rust_web_server::metrics::MetricsLayer;
let app = App::new().wrap(MetricsLayer::new());

MetricsLayer adds:

MetricTypeLabelsDescription
rws_route_requests_totalCountermethod, path, statusRequests per route and status code
rws_route_duration_secondsHistogrammethod, pathResponse time distribution

Grafana / PromQL queries

# Request rate (requests per second over 1 m)
rate(rws_requests_total[1m])
# Error rate
rate(rws_errors_total[1m])
# p99 latency per route
histogram_quantile(0.99, rate(rws_route_duration_seconds_bucket[5m]))
# Top routes by request count
topk(10, sum by (path) (rate(rws_route_requests_total[5m])))

Logs

Combined Log Format (default)

By default the server writes one line per request to stdout in Apache Combined Log Format (CLF):

127.0.0.1 - - [02/Jul/2026:12:00:00 +0000] "GET /healthz HTTP/1.1" 200 2

This format is compatible with GoAccess, AWStats, and most log-analysis tools.

JSON structured logs

Set RWS_CONFIG_LOG_FORMAT=json (via environment variable, rws.config.toml, or CLI flag) to switch to newline-delimited JSON:

{"time":"2026-07-02T12:00:00Z","method":"GET","path":"/healthz","status":200,"bytes":2,"client":"127.0.0.1"}

JSON logs are well-suited for ingestion by Loki, Fluentd, Datadog, and other structured-log collectors.

Log aggregation in Kubernetes

The server writes all logs to stdout. Kubernetes log aggregation pipelines pick them up automatically:

Terminal window
# View logs in real time
kubectl logs -f deployment/my-rws-app
# Ship to Loki via Promtail — no application config required; just label the pod

Tracing

OtelLayer

OtelLayer implements Middleware and propagates W3C traceparent headers. Add it to your application and configure an OTLP exporter:

use rust_web_server::app::App;
use rust_web_server::core::New;
use rust_web_server::otel::{self, ExporterConfig, OtelLayer};
otel::setup(ExporterConfig::Otlp {
endpoint: "http://tempo:4318".to_string(),
});
let app = App::new().wrap(OtelLayer::new());

The layer reads the incoming traceparent header, creates a child span for each request, and exports completed spans to the configured OTLP endpoint.

Supported backends

  • Grafana Tempo — point endpoint at the OTLP HTTP port (default 4318)
  • Jaeger — enable the OTLP receiver and point at port 4318
  • OpenTelemetry Collector — use as a fan-out proxy to multiple backends

Distributed tracing across services

Include the traceparent header in outbound requests using the synchronous HTTP client:

use rust_web_server::http_client::Client;
let response = Client::new()
.get("http://downstream-service/api/data")
.header("traceparent", incoming_traceparent)
.send()?;

Health endpoints

Two health endpoints are built into every server instance without any configuration:

GET /healthz — liveness

Returns 200 OK as long as the binary is running. Use this as the Kubernetes liveness probe:

livenessProbe:
httpGet:
path: /healthz
port: 7878
initialDelaySeconds: 5
periodSeconds: 10

GET /readyz — readiness

Returns 200 OK during normal operation. Returns 503 Service Unavailable during graceful shutdown (after SIGTERM is received, while in-flight requests are being drained). Use as the Kubernetes readiness probe:

readinessProbe:
httpGet:
path: /readyz
port: 7878
initialDelaySeconds: 3
periodSeconds: 5

The readiness gate ensures the load balancer stops routing new traffic to a pod as soon as it begins draining, giving in-flight requests time to complete before the pod terminates.