# Observability

Configure request IDs, structured logs, protected metrics, event-loop sampling, and OpenTelemetry traces.

Source: https://flasgo.dev/guides/observability/

## Request IDs and logs

Every HTTP request and WebSocket connection gets a locally generated request ID. It is returned as `x-request-id`, included in Flasgo events, bound to background-task logs, and correlated with active OpenTelemetry traces.

Configure only the `flasgo` logger hierarchy:

```python
from flasgo import configure_logging

configure_logging(format="json", level="INFO")
```

Or set application settings:

```python
app = Flasgo(
    settings={
        "LOG_FORMAT": "json",
        "LOG_LEVEL": "INFO",
        "LOG_SECURITY_EVENTS": True,
    }
)
```

`LOG_FORMAT` accepts `text` or `json`. `LOG_LEVEL` names are case-insensitive, so `"info"` and `"INFO"` are equivalent. Log fields are bounded and sanitized to prevent control-character injection. Flasgo logs access outcomes, security denials, WebSocket lifecycle, lifespan failures, and background-task outcomes without logging request bodies or credentials.

Both formats include exception tracebacks and stack information when a record carries them: text output appends them on the lines after the event, and JSON output adds `exception` and `stack` fields. Calling `configure_logging()` again updates the existing Flasgo handler's format and level, and passing `stream=` redirects it to the new stream instead of adding a second handler.

Incoming `x-request-id` is ignored by default. Enable `TRUST_INCOMING_REQUEST_ID=True` only when a trusted proxy overwrites the header; accepted IDs are limited to 128 ASCII letters, digits, `.`, `_`, and `-`.

## Prometheus metrics

Install the optional dependency:

```bash
uv add flasgo --extra metrics
```

Enable the protected endpoint:

```python
import os

from flasgo import Flasgo

app = Flasgo(
    settings={
        "METRICS_ENABLED": True,
        "METRICS_PATH": "/metrics",
        "METRICS_BEARER_TOKEN": os.environ[
            "FLASGO_METRICS_TOKEN"
        ],
    }
)
```

The token must contain at least 32 bearer-safe ASCII characters. Malformed or non-ASCII credentials fail closed with `401 Unauthorized`. Scrape with:

```bash
curl \
  -H "Authorization: Bearer $FLASGO_METRICS_TOKEN" \
  https://api.example.com/metrics
```

The endpoint uses constant-time token comparison and excludes scrapes from ordinary HTTP and backend metrics. Authentication failures have a dedicated counter and are handled by the security-failure limiter. Metrics responses are explicitly `no-store`, and scrapes do not create session or CSRF cookies. Collection and encoding run in a worker thread after authentication succeeds.

HTTP metrics include completed request attempts, active requests, status-class request duration, successfully sent response-body size, and response-send failures. Counters retain exact status codes; duration and size distributions use bounded `1xx` through `5xx` classes. Known methods use a bounded method set, unknown methods use `_OTHER`, and labels use route templates such as `/users/<int:user_id>` rather than attacker-controlled raw paths. Pre-dispatch `431` rejections are counted under `<unmatched>`.

WebSocket metrics include active and completed connections, route and outcome labels, and duration buckets extending to 24 hours. A handler that returns without calling `accept()` is recorded as `not_accepted` rather than `success`. Background-task and lifespan outcomes are included alongside `flasgo_info` and standard Python, garbage-collector, platform, and process collectors.

Content negotiation supports both Prometheus text and OpenMetrics. When OpenTelemetry is also active and the scraper requests OpenMetrics, request counters, duration, and response-size observations carry `trace_id` and `span_id` exemplars for trace correlation.

The registry is per process. Scrape every worker as a distinct target or place a supported aggregation layer in front of them; a load-balanced metrics URL can return a different worker on each scrape.

### Response, backend, and background measurements

Since 0.9.1, Flasgo records response-start and first-body timing, incremental stream bytes and terminal outcomes, logical backend call durations and outcomes, framework rejection reasons, and internal failures. It separates responses in flight from active requests (which also include background work) and tracks pending, active, cancelled, and skipped background tasks.

Use the [Metrics Reference](/reference/metrics/) for exact names, labels, buckets, and timing boundaries. A backend returning invalid credentials normally records successful execution plus an authentication rejection. A stream that sent `200` headers can still fail or exceed its cleanup deadline.

### Event-loop sampling and custom metrics

With metrics enabled, event-loop lag sampling starts after successful lifespan startup and stops on lifespan exit. `METRICS_EVENT_LOOP_ENABLED=True` and `METRICS_EVENT_LOOP_INTERVAL_SECONDS=0.1` are the defaults. The interval must be a finite number from `0.01` to `60` seconds; set the enabled flag to `False` to disable sampling. Check `flasgo_event_loop_sampler_running` alongside lag observations.

Register custom counters, gauges, and histograms through `app.metrics_registry` during setup. It returns `None` when metrics are disabled; see [Application Metrics](/reference/metrics/#application-metrics) for an example and collector requirements.

## OpenTelemetry tracing

Tracing is a separate optional extra and exports spans over OTLP/HTTP using standard OpenTelemetry environment variables:

```bash
uv add flasgo --extra otel
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otel.example.com"
export OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer%20${OTEL_TOKEN}"
```

```python
app = Flasgo(
    settings={
        "OTEL_ENABLED": True,
        "OTEL_SERVICE_NAME": "billing-api",
        "OTEL_SERVICE_VERSION": "1.4.0",
        "OTEL_TRACE_SAMPLE_RATIO": 0.25,
        "OTEL_EXCLUDED_PATHS": {"/health"},
    }
)
```

Flasgo traces HTTP requests and WebSocket connections. Span names and `http.route` use bounded route templates. Known methods, including `QUERY`, appear in span names; unknown methods use the stable `HTTP` placeholder.

For HTTP spans, the real request path is retained while each non-empty query value is replaced with `REDACTED`. WebSocket URL attributes remain route-template-only. Invalid or ambiguous `Host` values are removed from the scope passed to instrumentation before a denial is traced.

Oversized HTTP and WebSocket request heads are rejected before tracing or session loading. These denials use a fresh local request ID even with `TRUST_INCOMING_REQUEST_ID=True`; HTTP `431` metrics still record the rejection under `<unmatched>`.

`OTEL_EXCLUDED_PATHS` accepts exact paths or route templates such as `/reset/<token>`; template exclusions also apply to method-rejection spans. The metrics path is always excluded.

Flasgo uses a parent-based ratio sampler and a batch span processor. A provider it builds is process-global by default so other instrumentations can share its sampler and exporter; set `OTEL_SET_GLOBAL_PROVIDER=False` to keep it private. You may instead pass `tracer_provider=` to `Flasgo(...)` for tests or application-owned lifecycle management; this enables tracing even when `OTEL_ENABLED` is `False`.

At lifespan shutdown, Flasgo flushes a provider it created but does not shut it down, so a later lifespan on the same app, such as a second test client context, keeps exporting spans. The OpenTelemetry SDK closes it at interpreter exit. Flasgo never flushes or closes a provider passed as `tracer_provider=`.

Active trace and span IDs are added to framework event logs automatically.

## Dependency, streaming, and storage failures

Flasgo reports dependency cleanup, streaming, and shared-storage failures through structured events. Monitor `dependency-cleanup-failed`, `response-prepare-failed`, `response-send-failed`, and `shared-storage-unavailable` events. A session save failure can be reported during response preparation; `StoreUnavailable` is the exception type, not an event name.

A client disconnecting from a streaming or SSE response is routine, so Flasgo logs it as an INFO `http-client-disconnected` access event instead of an ERROR `response-send-failed` event. The stream outcome is `client_disconnect`, and the attempt still counts in `flasgo_http_response_send_failures_total`. Other send failures keep the `response-send-failed` event and an `http-response-send-failed` access event.

An HTTP stream remains active until sending completes or fails. Its completion log and duration cover the connection lifetime; a response whose headers were already sent can retain a `200` status while recording a send failure. Use the response-send outcome as well as status when monitoring streams. Request dependency cleanup failures are logged after delivery and cannot retract the response.

Never add session identifiers, storage connection strings, rejected output values, or credentials to custom log fields. See [Streaming Responses](/guides/streaming-responses/) and [Shared Quotas and Sessions](/guides/shared-storage/) for their failure boundaries.

## Operational boundaries

- Keep metrics behind network controls in addition to bearer authentication.
- Send JSON logs to a bounded central sink and define retention.
- Keep trace sampling and export queues deliberate for the service's traffic volume.
- Alert on sustained `5xx`, security denials, WebSocket failures, lifespan failures, and exporter health.
- Avoid high-cardinality labels or attributes such as user IDs, raw tokens, or arbitrary exception messages.

Sources:

- [README.md](https://github.com/L1ghtn1ng/flasgo/blob/main/README.md)
- [flasgo/telemetry.py](https://github.com/L1ghtn1ng/flasgo/blob/main/flasgo/telemetry.py)
- [flasgo/logging.py](https://github.com/L1ghtn1ng/flasgo/blob/main/flasgo/logging.py)
