Skip to main content

Observability events

Gate can ship a structured stream of observability events to the SlashID backend describing what a Gate instance is configured to do and how each incoming request was handled. The SlashID dashboard consumes this stream to give operators a unified view over every Gate deployment across an organization.

The stream is opt-in per Gate instance: enable it by setting monitoring.enabled: true and providing an org ID + API key.

gate:
service_name: my-gate # top-level; travels on every event's envelope
monitoring:
enabled: true
slashid_org_id: <SlashID org ID>
slashid_api_key: <SlashID API key>

Event envelope

Every event Gate emits carries three identifiers alongside the event body:

FieldDescription
analytics_correlation_idUUID generated on every process start. Uniquely identifies a running Gate process. The dashboard joins request events back to the boot event via this ID.
instance_idUUID identifying a Gate installation across process restarts. Persisted to disk (see Persistent instance identity), so a container restart keeps the same identity.
service_nameHuman-readable deployment label. Reuses the top-level service_name field, which is also OpenTelemetry's service.name resource attribute for traces and logs. One name across every observability channel Gate speaks.

When Gate can't read or write the instance-id file (unwritable directory, unreadable file, corrupt content), it logs a warning and falls back to a transient identity: instance_id is set to the same value as analytics_correlation_id. Downstream consumers can detect this trivially — instance_id == analytics_correlation_id means "this Gate has no persistent identity".

Event types

Gate emits three event kinds.

GateServerStarted_v1 — boot event

Sent at boot and re-sent every 24 hours from the same running Gate. Carries the sanitized plugin config snapshot:

  • plugins[] — catalog of every configured plugin definition (plugin_id, plugin_type, and the plugin-specific config with secrets replaced by *_configured booleans / *_count ints).
  • routes[] — Gate's routing tree, in the order Gate consults at request time. Each route entry carries its effective target, the enabled plugins, and any per-URL parameter overrides.
  • Optional per-plugin extras — for example the OPA plugin can opt into shipping its loaded rego bundle (see OPA-specific observability).

The 24-hour renewal reuses the same analytics_correlation_id and the same payload, so backends can upsert-by-correlation-id without a schema change. This keeps the boot event fresh inside the backend's retention window for long-lived Gate processes — without renewal, request events from a Gate that's been running for weeks would have no boot event to join against.

GateHeartbeat_v1 — aliveness signal

Opt-in periodic event, off by default. Enable via monitoring.heartbeat.enabled: true:

gate:
monitoring:
heartbeat:
enabled: true
interval: 1m # default; validated floor is 10s

Each heartbeat carries a strict-monotonic sequence number so downstream consumers detect drops as gaps in the sequence — a failed send still advances the counter, so a missed heartbeat is unambiguously visible.

GateRequestHandled_v1 — per-request event

Sent for every request Gate proxies. Carries:

  • Request identityrequest_id (Gate-generated UUID, always populated) plus, when a tracer provider is configured, otel_trace_id and otel_span_id. The trace ID is inherited from any incoming traceparent / Jaeger / Cloud Trace header, so consumers can link a Gate event to the caller's distributed trace. Gate also echoes the request_id back on the response as X-Gate-Request-ID, letting clients correlate their response to Gate's observability stream without subscribing to it.
  • Plugin decision telemetry — per-plugin outcome, reasons, and metadata. See Plugin decision telemetry below.
  • target_round_trip — the round-trip Gate made to the configured upstream: method, URL (sanitized per url_visibility), status code, timing, and request/response body lengths. Present when Gate actually reached the upstream; absent when a plugin short-circuited before the proxy.
  • Framework-derived request outcome — resolved across all plugin outcomes as error > reject > modify > allow.

Persistent instance identity

The instance_id UUID is persisted to monitoring.instance_id_file — default /data/gate/instance-id. Gate's Docker image declares VOLUME /data/gate, so mounting a volume at that path is enough to keep identity across container restarts:

services:
gate:
image: slashid/gate-free:latest
volumes:
- gate-data:/data/gate
volumes:
gate-data:

You can override the path via monitoring.instance_id_file: /some/other/path. On first boot Gate generates a new UUID and writes it atomically (tempfile + rename); subsequent boots read the file verbatim. A corrupt file is never overwritten — Gate logs a warning and falls back to the transient-identity mode so operators can inspect or repair the file.

URL visibility

Every URL that lands on an observability event — plugin config, route targets, per-request URL, target round-trip URL — flows through a single operator-configured sanitizer. Configure via monitoring.url_visibility:

ValueBehavior
noneURL fields dropped entirely. For compliance-sensitive deployments where any URL detail is considered leaked information.
host (default)Scheme + host (with port) only. Strips userinfo, path, query, fragment.
pathScheme + host + path. Strips userinfo, query, fragment. Useful for dashboards that break down traffic by endpoint.
fullRaw URL, unchanged. Ships userinfo + query + fragment — intended for local debugging only.

Plugin decision telemetry

Every plugin invocation on the request path records:

  • outcome — what the plugin actually did to this request, independent of the operator's policy intent:
    • allow — plugin forwarded the request downstream unchanged.
    • modify — plugin forwarded the request downstream but altered the request or the response.
    • reject — plugin short-circuited with its own response (a rejection, a redirect, a cached response, a UI flow, etc.).
    • error — framework fallback for a missing or panicked plugin call. Plugins never emit this themselves.
  • reasons[] — namespaced codes explaining the outcome, of the form <plugin_type>.<code> (e.g. opa.policy_denied, ratelimit.exceeded, anonymizer.pii_detected). Framework-emitted codes use the framework. namespace.
  • metadata — plugin-specific structured payload; currently populated by the anonymizer for credential findings (see Anonymizer credential findings).

Advisory denials in monitoring modes (OPA monitoring_mode: true, ratelimit's monitoring mode, etc.) let the request through and record outcome=allow with an advisory reason code (e.g. opa.policy_denied_advisory, ratelimit.exceeded_advisory), so a dashboard can distinguish a policy that would have blocked from one that admitted the request cleanly.

Anonymizer credential findings

When the anonymizer plugin runs, it can attach every detected credential finding to the per-request event via metadata.found_credentials[]. The level of detail is controlled by a per-plugin monitoring sub-block:

gate:
plugins:
- type: anonymizer
id: my-anonymizer
config:
# ... normal anonymizer config ...
monitoring:
credential_value_exposure: hashed # or type_only | redacted | plain
ValueWhat lands on the wire per finding
type_onlycredential_provider (e.g. aws, stripe) + hit_count — no fingerprints, no plaintext.
hashed (default)Adds sha256 / sha1 / md5 hex digests. Enables correlating occurrences of the same credential across requests without ever seeing plaintext.
redactedAdds a masked credential_value (keeps the last min(len/4, 4) characters). Hashes still included.
plainAdds the full plaintext credential_value. For controlled debugging environments.

hit_count on each finding preserves multiplicity across multiple JSON-path matches of the same credential value within one request. The per-request pii_count is the raw per-occurrence tally: sum(hit_count) ≤ pii_count is an invariant.

OPA-specific observability

The OPA plugin exposes two per-instance opt-in monitoring knobs:

  • policy_bundle: true — ship the plugin's loaded rego source (and any base data) inside the GateServerStarted_v1 boot event, so the dashboard can display the exact policy Gate is enforcing. Whole-or-nothing under a fixed 32 KiB cap.
  • explain: true — attribute each OPA decision to the rego source lines that produced it, via opa.trace_at reasons on every GateRequestHandled_v1. Each reason carries the source file / row / col, the rule's fully qualified rule_path, and the rule's evaluated result. Meaningfully slower than a plain decision — keep off on high-QPS paths.

Both are configured on the plugin under config.monitoring — see the plugin docs' Monitoring options section for details.