| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Telemetry.Tracing
Description
The request-lifecycle tracing layer on top of the OpenTelemetry substrate (Ecluse.Telemetry): the WAI server span, the http-client child spans on the data plane, and the hand-added domain spans that carry the decisions an operator cares about — all inert when telemetry is off.
The substrate decides whether telemetry is wired; this module decides what is
traced. Every entry point takes the Telemetry handle and, when it is
TelemetryDisabled, adds nothing and emits nothing: the
middleware is id, the manager settings are returned untouched, and a domain-span
bracket runs its body against no span. When telemetry is enabled, the handle's
provider is the process-global provider the substrate installed (when enabled,
"Ecluse.Telemetry.withTelemetry" calls initializeGlobalTracerProvider, which also
installs the global text-map propagator), so the WAI and http-client instrumentation — which read
the process globals — and the hand-added spans, which read the handle, all hang off
one coherent tracer and join into one trace.
What is traced
- Server span — one per request, from the WAI instrumentation, as the outermost
middleware so it spans the whole request (
telemetryWaiMiddleware). - Client spans — one per upstream fetch, from instrumenting the data-plane
Managersettings (instrumentDataPlaneManagerSettings), which also injects W3C trace context into the outbound request so a downstream service continues the trace. - Domain spans —
withRuleEvalSpan(the per-version verdict, so a403is explainable from the trace alone),withMirrorEnqueueSpan(the synchronous serve handing off to the asynchronous mirror), andwithMirrorJobSpan(the worker's fetch → verify → publish).
Secret discipline
The data-plane instrumentation uses dataPlaneInstrumentationConfig, which records
no request or response headers, so a forwarded client token or an Authorization
header is never captured on a client span; the WAI instrumentation likewise never
records Authorization. High-cardinality identifiers (package, version, the full
denial message) belong on these spans and are recorded here; secrets never are. The
attribute mapping and the scrub are covered by Ecluse.Telemetry.TracingSpec.
Synopsis
- telemetryWaiMiddleware :: Telemetry -> IO Middleware
- instrumentDataPlaneManagerSettings :: Telemetry -> ManagerSettings -> IO ManagerSettings
- dataPlaneInstrumentationConfig :: HttpClientInstrumentationConfig
- withRuleEvalSpan :: MonadUnliftIO m => Telemetry -> PackageName -> Version -> m (a, ServeDecision) -> m a
- withMirrorEnqueueSpan :: MonadUnliftIO m => Telemetry -> PackageName -> Version -> Text -> m a -> m a
- withMirrorJobSpan :: MonadUnliftIO m => Telemetry -> PackageName -> Version -> (a -> JobSpanOutcome) -> m a -> m a
- data JobSpanOutcome = JobSpanOutcome {}
- ruleVerdictFields :: ServeDecision -> [(Text, Text)]
WAI server span
telemetryWaiMiddleware :: Telemetry -> IO Middleware Source #
The WAI server-span middleware for the request stack: one server span per
request, built over the handle's tracer and meter providers. When telemetry is
disabled it is id — the stack is unchanged and no span is opened — so it is
additive and inert exactly as the substrate's off posture requires.
It belongs outermost in the stack so the span covers the whole request, including the other middlewares (see Ecluse.Server).
http-client data-plane instrumentation
instrumentDataPlaneManagerSettings :: Telemetry -> ManagerSettings -> IO ManagerSettings Source #
Instrument a data-plane ManagerSettings so every upstream fetch through the
resulting manager opens a client span and carries W3C trace-context headers, or
return the settings untouched when telemetry is disabled.
The gate is the handle, not a per-request check: when telemetry is enabled the substrate has installed the process-global providers the http-client instrumentation reads, so the spans hang off the same tracer as everything else; when disabled the settings are returned verbatim and the data plane runs exactly as it would without this layer.
The configuration is dataPlaneInstrumentationConfig, which records no headers, so a
forwarded client token never reaches a span.
dataPlaneInstrumentationConfig :: HttpClientInstrumentationConfig Source #
The http-client instrumentation configuration the data plane uses: the default,
which records no request or response headers. This is the secret-scrub guarantee
at the configuration boundary — an Authorization header is never lifted onto a span
— so it is named rather than inlined, and the scrub test pins the very same value.
Domain spans
withRuleEvalSpan :: MonadUnliftIO m => Telemetry -> PackageName -> Version -> m (a, ServeDecision) -> m a Source #
Run a rule-evaluation domain span around an action that yields its result and the verdict to record. The span carries the package and version and, from the verdict, the decision and — on a denial — the deciding rule, the reason class, and the human-readable message, so a refusal is explainable from the trace alone.
Inert when telemetry is disabled: the action runs against no span and its result is returned unchanged.
withMirrorEnqueueSpan :: MonadUnliftIO m => Telemetry -> PackageName -> Version -> Text -> m a -> m a Source #
Run a mirror-enqueue domain span around the serve-time hand-off to the
asynchronous mirror, carrying the package, version, and the artifact's authoritative
URL. A Producer span, since it produces the work the worker later consumes. Inert
when telemetry is disabled.
withMirrorJobSpan :: MonadUnliftIO m => Telemetry -> PackageName -> Version -> (a -> JobSpanOutcome) -> m a -> m a Source #
Run a mirror-worker-job domain span around the worker's fetch → verify →
publish, carrying the package and version and, once the job finishes, its outcome.
A Consumer span (it consumes the enqueued work); the outcome projection names the
bounded outcome label and, for a non-success, the detail that sets the span status to
Error. Inert when telemetry is disabled.
data JobSpanOutcome Source #
The projection a caller supplies for the mirror-job span: the bounded outcome
label always, and, for a job that did not publish, the detail that marks the span
Error. Kept a small record here — rather than the worker's own outcome type — so
the tracing layer does not depend on Ecluse.Worker.
Constructors
| JobSpanOutcome | |
Fields
| |
Instances
| Show JobSpanOutcome Source # | |
Defined in Ecluse.Telemetry.Tracing Methods showsPrec :: Int -> JobSpanOutcome -> ShowS # show :: JobSpanOutcome -> String # showList :: [JobSpanOutcome] -> ShowS # | |
| Eq JobSpanOutcome Source # | |
Defined in Ecluse.Telemetry.Tracing Methods (==) :: JobSpanOutcome -> JobSpanOutcome -> Bool # (/=) :: JobSpanOutcome -> JobSpanOutcome -> Bool # | |
Verdict attribute mapping
ruleVerdictFields :: ServeDecision -> [(Text, Text)] Source #
Map a serve verdict to the rule-evaluation span's attribute fields. Pure and total.
An Admit records only the decision; a Reject records the decision, the bounded
reason class, the human-readable message, and — for a policy denial — the deciding
RuleName. None of these fields can carry a secret: the rule name and reason class
are a closed vocabulary and the message is the rendered decision, never a credential.