Observability
tinyguard can export OpenTelemetry spans and log records to any OTLP
collector over http/protobuf. The feature is off by default: with
[observability] disabled no provider, exporter, or span exists, and the
stdout logs are byte-identical to a deployment without it.
Enabling
Section titled “Enabling”[observability]enabled = true# Base collector endpoint; /v1/traces and /v1/logs are appended.# https:// everywhere except loopback; a plaintext non-loopback endpoint# must also set allow_insecure_endpoint = true.endpoint = "https://otel-collector.observability:4318"service_name = "tinyguard"Every key has an environment override
(GUARD_OBSERVABILITY_ENDPOINT, GUARD_OBSERVABILITY_SERVICE_NAME, …), and
the standard OTEL_* variables are read too (see the
environment reference). The keys are in the
configuration reference. The
exporter runs on its own threads with a bounded queue: a slow or unreachable
collector drops telemetry, it never delays or blocks a request. On SIGTERM
the shutdown path flushes both batch processors before the process exits.
Sampling: sample_ratio (0–1) with parent_based = true (the default)
honors an incoming traceparent header — a request sampled by the caller is
sampled here; with parent_based = false the ratio applies on its own. An
incoming traceparent also becomes the request span’s parent, so
cross-service traces stay connected.
What is exported
Section titled “What is exported”- Spans — only spans the server itself creates for telemetry, never
arbitrary internal spans:
- Request span (one per admitted request): HTTP method, path without the query string, response status, duration in milliseconds, and the bound tenant slug.
- Token issuance: grant type, tenant, outcome class (
issued/denied/ a status class for the device grant). - Login verification: outcome class only (
authenticated,invalid_credentials,second_factor_pending, …). - Federation: one span per provider-addressed federation request with the provider slug and a response-status outcome class.
- Log records — the same events the stdout logs carry, exported through
the masked bridge with the process resource (
service.name,service.version, any configured resource attributes) attached.
Logs on stdout
Section titled “Logs on stdout”With logging.format = "json" (GUARD_LOGGING_FORMAT=json) every line is one
JSON object with these top-level fields, the same for every TinyBlox component:
| Field | Meaning |
|---|---|
timestamp |
RFC 3339, UTC, millisecond precision. |
level |
error, warn, info, debug or trace. |
message |
The event’s message. |
tenant |
The request’s tenant (default for the default tenant). Absent on lines written outside a request. |
trace_id |
The W3C trace id, when telemetry is enabled and the request has a trace. Absent otherwise. |
Everything else an event carries is an extra key at the same level (method,
path, status, …), not nested. The target is target. An extra field cannot
overwrite a standard one: it is written as field_<name>.
The masking below applies to stdout too, not only to the OTLP exporter: a field
named for a credential is replaced with [REDACTED], and a bearer token or a
password= fragment in a message or value is scrubbed. The check the platform
runs is kubectl logs <pod> | jq -e 'has("timestamp") and has("level") and has("message")'.
Metrics
Section titled “Metrics”The metrics listener ([server.metrics], port 9090, never routed by the ingress)
serves Prometheus text. Every name starts with tinyguard_:
tinyguard_requests_total, tinyguard_logged_requests_total,
tinyguard_responses_total{class="2xx|4xx|5xx"},
tinyguard_security_blocks_total and tinyguard_uptime_seconds.
The masking guarantees
Section titled “The masking guarantees”Masking is unconditional whenever observability is enabled — there is no configuration that weakens it, and it applies to span attributes, log field values, and log message bodies alike:
- By key: a field whose key names a credential is replaced wholesale
with
[REDACTED]. This covers passwords, access/refresh/ID tokens, API keys, client secrets, authorization headers, cookies, session ids (including CSRF tokens and DPoP nonces), one-time codes (authorization, device, user, verification, await codes), recovery codes, sealed values, JWT ids, assertions, webhook URLs, and email addresses / subjects. Pure classification keys (error_code,status_code, …) survive. - By value pattern: any remaining string — the log message included —
is scrubbed of
code=,token=,client_secret=,password=, andassertion=query/form fragments and ofBearer/Basiccredentials, with the secret replaced by[REDACTED].
Neither spans nor logs ever carry: passwords, raw request bodies, query
strings, subject or email values (emails are [REDACTED] if a field ever
names one; the login and federation spans never record them), or token
values of any kind.
What does leave the process: HTTP method, path, status codes and durations, tenant and provider slugs, grant types, outcome classes, opaque user and client identifiers, error classifications, and the same structured log fields the stdout logs print (post-masking).
What is not exported
Section titled “What is not exported”- OTLP metrics: the server’s own metrics surface stays on its dedicated
listener (
[server.metrics]); the OTLP exporter carries traces and logs only. - Request or response bodies, query strings, cookies, or headers (beyond
the
traceparentpropagation header). - Spans from arbitrary internal code: the span bridge accepts only the server’s own telemetry spans, so future internal spans cannot leak unmasked by accident.