ecluse
Safe HaskellNone
LanguageGHC2021

Ecluse.Telemetry

Description

The OpenTelemetry substrate: the tracer and meter providers the rest of the proxy will hang spans and metrics on, behind a master switch that defaults to off.

Écluse is a self-hosted proxy operators run inside their own infrastructure, so observability is opt-in and vendor-neutral: the substrate is OpenTelemetry, emitting OTLP that any compatible backend can receive. The maintainer's choice of backend (Datadog) must never become every consumer's obligation, so __with PROXY_TELEMETRY unset nothing is wired and no telemetry is emitted__ — the SDK is not even initialised.

This module is purely the substrate: it stands up (or, by default, declines to stand up) the providers and brackets their lifecycle. The spans on the request lifecycle and the metric instruments layer on top of this substrate; nothing here instruments the hot path.

The switch and the handle

TelemetrySwitch is the PROXY_TELEMETRY master switch, parsed at the configuration boundary (Ecluse.Config) in the same strict, fail-loud style as the other enums. The Telemetry handle it produces is one of two shapes:

  • telemetryDisabled — the off-by-default no-op. It holds no providers, the SDK is never initialised, and nothing is exported. This is what an unset PROXY_TELEMETRY yields.
  • an enabled handle carrying the SDK's tracer and meter providers, built from the standard OTEL_* environment variables the SDK reads directly (OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, the sampler). The OTLP exporter defaults to HTTP/protobuf; gRPC stays behind the exporter's cabal flag, off.

withTelemetry is the lifecycle bracket the composition root (Ecluse.Env) runs the proxy within: when enabled it initialises the providers and tears them down — flushing buffered spans and metrics — along every exit path; when disabled it is a pure pass-through that opens nothing to tear down.

When enabled it also makes export failures visible: the OTLP span and metric exporters are wrapped so a failed export — which hs-opentelemetry 1.0.0.0 otherwise drops silently — is observed and routed through the shared katip throttle (Ecluse.Telemetry.Resolve), the first failure logged plainly then a periodic heartbeat. The wrappers only observe; export semantics are unchanged, so an unreachable collector still degrades off the request path.

The configuration model and the signal catalogue are described in docs/architecture/observability.md.

Synopsis

Master switch

data TelemetrySwitch Source #

The PROXY_TELEMETRY master switch: telemetry is opt-in, so TelemetryOff is the default and the FOSS posture. A sum type rather than a Bool so each case names its intent and a future mode (e.g. a metrics-only switch) is a new constructor, not a second flag.

Constructors

TelemetryOff

Telemetry is disabled (the default): nothing is wired and nothing is emitted.

TelemetryOn

Telemetry is enabled: the SDK providers are built from the standard OTEL_* environment and the OTLP exporter is active.

parseTelemetrySwitch :: Text -> Either Text TelemetrySwitch Source #

Parse a TelemetrySwitch from its wire name, naming the accepted set on failure. The same strict, fail-loud style as the other configuration enums (Ecluse.Config): an unrecognised value is a loud failure, never a silent fallback to one mode or the other.

>>> parseTelemetrySwitch "off"
Right TelemetryOff
>>> parseTelemetrySwitch "on"
Right TelemetryOn
>>> parseTelemetrySwitch "maybe"
Left "unknown telemetry switch \"maybe\" (expected one of: on, off)"

The telemetry handle

data Telemetry Source #

The telemetry handle held in the composition root: either the off-by-default no-op or the enabled providers. Spans and metric instruments are derived from the providers it carries; the disabled case carries none, so a layer that reaches for a provider finds nothing to emit through — telemetry is genuinely inert, not merely unsampled.

Constructors

TelemetryDisabled

The off-by-default no-op: no providers, nothing emitted.

TelemetryEnabled TelemetryProviders

The enabled handle carrying the SDK's providers, built from the standard OTEL_* environment. The providers live in a TelemetryProviders product so neither field is a partial record selector on this sum.

data TelemetryProviders Source #

The SDK providers an enabled Telemetry handle carries — a total product, so its fields are not partial selectors over the Telemetry sum.

Constructors

TelemetryProviders 

Fields

telemetryDisabled :: Telemetry Source #

The disabled telemetry handle: the off-by-default no-op that holds no providers and emits nothing. This is what an unset PROXY_TELEMETRY resolves to.

telemetryEnabled :: OTelSignals -> Telemetry Source #

Build an enabled telemetry handle from the SDK signals — the tracer and meter providers. The disabled case has no constructor argument, so this is the only way to obtain an enabled handle, keeping its providers' origin (the bracketed SDK lifecycle) explicit.

telemetryTracerProvider :: Telemetry -> Maybe TracerProvider Source #

The tracer provider a Telemetry handle exposes, Nothing when telemetry is disabled. A caller that wants to create a span resolves this first; Nothing is the signal to emit nothing rather than to fabricate a no-op provider at the edge.

telemetryMeterProvider :: Telemetry -> Maybe MeterProvider Source #

The meter provider a Telemetry handle exposes, Nothing when telemetry is disabled (the dual of telemetryTracerProvider for metric instruments).

Lifecycle

withTelemetry :: TelemetrySwitch -> LogEnv -> (Telemetry -> IO a) -> IO a Source #

Run an action with a Telemetry handle whose lifecycle is bracketed by the TelemetrySwitch, tearing the providers down — flushing buffered spans and metrics — along every exit path.

  • TelemetryOff (the default) is a pure pass-through: the SDK is __never initialised__, the body runs against telemetryDisabled, the LogEnv is unused, and there is nothing to tear down. An unset PROXY_TELEMETRY therefore opens no exporter and emits nothing.
  • TelemetryOn initialises the SDK from the standard OTEL_* environment with the OTLP exporters wrapped for failure observation (the shared throttle feeds the supplied LogEnv), runs the body against the enabled handle, and shuts the providers down on exit.

This is the scope the composition root (Ecluse.Env) runs the server and worker within, so telemetry is established once and flushed on shutdown.

Export-failure observation (exporter wrappers)