ecluse
Safe HaskellNone
LanguageGHC2021

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 Manager settings (instrumentDataPlaneManagerSettings), which also injects W3C trace context into the outbound request so a downstream service continues the trace.
  • Domain spanswithRuleEvalSpan (the per-version verdict, so a 403 is explainable from the trace alone), withMirrorEnqueueSpan (the synchronous serve handing off to the asynchronous mirror), and withMirrorJobSpan (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

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

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.