| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Telemetry.Instruments
Description
The runtime metric instruments and the typed emit helpers the hot path records
through — the IO layer over the pure ecluse.* catalogue (Ecluse.Telemetry.Metrics).
Ecluse.Telemetry.Metrics defines what the catalogue is (the names and the closed
set of bounded labels); this module turns that catalogue into live OpenTelemetry
instruments and exposes one typed record* per signal. Each helper takes only the
bounded label values its metric carries — never a free identifier — so the
bounded-label discipline is enforced at the call site by the type, and the attribute
set an instrument ever sees is drawn from a small fixed product of the label domains.
Gating: inert when telemetry is off
newMetrics builds the instruments from the Telemetry handle's meter provider when
telemetry is enabled, and from the SDK's no-op meter provider when it is not. A
no-op instrument discards every measurement, so the record* helpers are called
unconditionally on the hot path and are genuinely inert when telemetry is off — no
per-call branch, no provider fabricated at the edge. The Metrics handle is therefore
total: every signal has a real instrument whichever posture the proxy is in. The no-op
recording function ignores its arguments, so the metricAttributes a call passes is
never forced — no attribute set is materialised when telemetry is off, only a thunk that
is discarded.
The catalogue and the cardinality rule are described in
docs/architecture/observability.md.
Synopsis
- data Metrics
- newMetrics :: Telemetry -> IO Metrics
- timedSeconds :: MonadIO m => m a -> m (a, Double)
- recordServeDecision :: MonadIO m => Metrics -> Decision -> m ()
- recordRuleDenial :: MonadIO m => Metrics -> Maybe Text -> ReasonClass -> m ()
- recordRuleEvalDuration :: MonadIO m => Metrics -> Tier -> Double -> m ()
- recordRuleEffectfulFailure :: MonadIO m => Metrics -> Cause -> m ()
- recordBreakerState :: MonadIO m => Metrics -> BreakerSource -> BreakerState -> m ()
- recordUpstreamFetch :: MonadIO m => Metrics -> Upstream -> StatusClass -> Double -> m ()
- recordUpstreamFetchError :: MonadIO m => Metrics -> Upstream -> Cause -> m ()
- recordCacheRequest :: MonadIO m => Metrics -> CacheResult -> m ()
- recordCacheEntries :: MonadIO m => Metrics -> Int -> m ()
- recordMirrorEnqueued :: MonadIO m => Metrics -> m ()
- recordMirrorEnqueueFailure :: MonadIO m => Metrics -> m ()
- recordMirrorJobProcessed :: MonadIO m => Metrics -> MirrorResult -> m ()
- recordMirrorPublishDuration :: MonadIO m => Metrics -> Double -> m ()
- recordCredentialRefresh :: MonadIO m => Metrics -> Provider -> CredentialResult -> m ()
- recordCredentialTokenTtl :: MonadIO m => Metrics -> Provider -> Int -> m ()
The instrument handle
The live metric instruments, one per ecluse.* signal, created against a single
meter. Opaque: built with newMetrics and recorded through the record* helpers.
Held in the composition root (Ecluse.Env) so every layer records through the same
instruments.
http.server.request.duration is not here: the WAI instrumentation emits it from
the server-span meter (Ecluse.Telemetry.Tracing), so duplicating it would double the
series. Advisory-sync and breaker instruments the catalogue names are present; their
wiring is layered on as the subsystems that own them are built.
newMetrics :: Telemetry -> IO Metrics Source #
Build the metric instruments from a Telemetry handle. When telemetry is enabled
the instruments are created on the handle's meter provider; when it is disabled they
are created on the SDK's no-op meter provider, so every recorded measurement is
discarded and the record* helpers are inert.
Instruments are created once here (at composition) rather than per measurement, so the hot path only records.
Timing
timedSeconds :: MonadIO m => m a -> m (a, Double) Source #
Run an action and return its result alongside the wall-clock seconds it took, measured on the monotonic clock so a system-clock step never yields a negative or absurd duration. The seconds are what the latency histograms record.
Serve decision
recordServeDecision :: MonadIO m => Metrics -> Decision -> m () Source #
Record one serve decision (ecluse.serve.decision): admit, deny, or unavailable.
Rule gate
recordRuleDenial :: MonadIO m => Metrics -> Maybe Text -> ReasonClass -> m () Source #
Record one rule denial (ecluse.rule.denials) by reason class and, for a policy
denial, the deciding rule. A non-policy refusal (a missing-integrity or upstream cause)
carries the reason class alone — no rule attributed it, so none is labelled.
recordRuleEvalDuration :: MonadIO m => Metrics -> Tier -> Double -> m () Source #
Record a rule-evaluation latency sample (ecluse.rule.eval.duration) by tier.
recordRuleEffectfulFailure :: MonadIO m => Metrics -> Cause -> m () Source #
Record one effectful-rule failure (ecluse.rule.effectful.failures) by cause.
recordBreakerState :: MonadIO m => Metrics -> BreakerSource -> BreakerState -> m () Source #
Record the current circuit-breaker state (ecluse.rule.breaker.state) for a
source as the gauge's bounded ordinal (0 closed, 1 half-open, 2 open).
Upstream fetch (data plane)
recordUpstreamFetch :: MonadIO m => Metrics -> Upstream -> StatusClass -> Double -> m () Source #
Record an upstream metadata-fetch latency sample (ecluse.upstream.fetch.duration)
by which upstream was fetched and the response's status class.
recordUpstreamFetchError :: MonadIO m => Metrics -> Upstream -> Cause -> m () Source #
Record one upstream metadata-fetch error (ecluse.upstream.fetch.errors) by
which upstream and the bounded cause.
Metadata cache
recordCacheRequest :: MonadIO m => Metrics -> CacheResult -> m () Source #
Record one metadata-cache lookup (ecluse.metadata_cache.requests) as a hit or miss.
recordCacheEntries :: MonadIO m => Metrics -> Int -> m () Source #
Record the metadata cache's current occupancy (ecluse.metadata_cache.entries).
Mirror
recordMirrorEnqueued :: MonadIO m => Metrics -> m () Source #
Record one mirror job enqueued (ecluse.mirror.enqueued).
recordMirrorEnqueueFailure :: MonadIO m => Metrics -> m () Source #
Record one mirror enqueue failure (ecluse.mirror.enqueue.failures).
recordMirrorJobProcessed :: MonadIO m => Metrics -> MirrorResult -> m () Source #
Record one processed mirror job (ecluse.mirror.jobs.processed) by its result.
recordMirrorPublishDuration :: MonadIO m => Metrics -> Double -> m () Source #
Record a mirror publish latency sample (ecluse.mirror.publish.duration).
Credentials
recordCredentialRefresh :: MonadIO m => Metrics -> Provider -> CredentialResult -> m () Source #
Record one credential refresh (ecluse.credential.refresh) by result and provider.