| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
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 unsetPROXY_TELEMETRYyields.- 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
- data TelemetrySwitch
- parseTelemetrySwitch :: Text -> Either Text TelemetrySwitch
- renderTelemetrySwitch :: TelemetrySwitch -> Text
- data Telemetry
- data TelemetryProviders = TelemetryProviders {}
- telemetryDisabled :: Telemetry
- telemetryEnabled :: OTelSignals -> Telemetry
- telemetryTracerProvider :: Telemetry -> Maybe TracerProvider
- telemetryMeterProvider :: Telemetry -> Maybe MeterProvider
- withTelemetry :: TelemetrySwitch -> LogEnv -> (Telemetry -> IO a) -> IO a
- observeSpanExporter :: ExportFailureSink -> SpanExporter -> SpanExporter
- observeMetricExporter :: ExportFailureSink -> MetricExporter -> MetricExporter
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
|
Instances
| WireVocab TelemetrySwitch Source # | |
| Show TelemetrySwitch Source # | |
Defined in Ecluse.Telemetry Methods showsPrec :: Int -> TelemetrySwitch -> ShowS # show :: TelemetrySwitch -> String # showList :: [TelemetrySwitch] -> ShowS # | |
| Eq TelemetrySwitch Source # | |
Defined in Ecluse.Telemetry Methods (==) :: TelemetrySwitch -> TelemetrySwitch -> Bool # (/=) :: TelemetrySwitch -> TelemetrySwitch -> Bool # | |
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)"
renderTelemetrySwitch :: TelemetrySwitch -> Text Source #
The wire name of a TelemetrySwitch (the inverse of parseTelemetrySwitch).
The telemetry handle
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
|
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.
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 againsttelemetryDisabled, theLogEnvis unused, and there is nothing to tear down. An unsetPROXY_TELEMETRYtherefore opens no exporter and emits nothing.TelemetryOninitialises the SDK from the standardOTEL_*environment with the OTLP exporters wrapped for failure observation (the shared throttle feeds the suppliedLogEnv), 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.