Documentation
¶
Overview ¶
Package telemetry owns Harbor's canonical structured logger.
The Logger wraps log/slog with three load-bearing additions:
- A pinned eight-attribute identity surface (tenant_id, user_id, session_id, run_id, task_id, trace_id, span_id, tool). The first five flow from ctx via the Phase 01 identity helpers; the rest are passthrough keys reserved for OTel wiring (Phase 55).
- Mandatory redaction: every record's attribute values AND the msg string flow through audit.Redactor before the slog handler sees them. Redaction failures are fail-loudly (D-020) — the record is replaced with a sentinel line, never silently emitted unredacted.
- A BusEmitter seam so Phase 05+ can fire a paired runtime.error bus event without re-opening this package (RFC §6.14, brief 06).
A *Logger is built once at boot via New, then shared across every emit path. WithIdentity / WithRun / With return derived loggers that carry additional bound attributes; the base *Logger stays unchanged. Concurrent reuse is enforced by D-025 — the shipping test suite runs N≥100 goroutines through a single shared instance under -race.
sloghandler.go — the *slog.Logger bridge over the canonical telemetry Logger (Wave C checkpoint audit; closes the 111f telemetry-threading gap). Long-lived subsystems constructed by the assembly (the notifications subscriber, the pause sweeper, the dispatch executor, the MCP attach loop, the search-cache warn path) accept a plain *slog.Logger; before this bridge they stayed on the bare boot logger for their whole lifetime, so their error paths bypassed the mandatory redactor and emitted no paired `runtime.error` bus event. Slog() lets the assembly hand them a *slog.Logger whose records flow through the full telemetry pipeline (identity stamping from ctx, redaction, bus-paired errors) without changing a single subsystem signature.
tracebridge.go — the bus→tracer bridge (Phase 111f, D-203).
BridgeBusToTracer is the OTel-span derivation of the canonical event bus, symmetric in shape and lifecycle with BridgeBusToMetrics (subscribe → drain goroutine → stop func; fail-loud on a nil bus / tracer). It closes the brief 06 "No OpenTelemetry in the runtime" anti-pattern's trace half: metrics got their bus bridge in PR #91 (D-082); traces had exporters blank-imported but NewTracer was never constructed on a production path and no bridge existed. Phase 111f constructs the tracer in `assemble.Assemble` and starts this bridge alongside the metrics bridge.
Span model (brief 06 §"span lifecycle from events") ¶
Canonical lifecycle pairs open and end spans:
- task.started → task.completed / task.failed / task.cancelled
- tool.invoked → tool.completed / tool.failed / tool.invalid_args / tool.policy_exhausted
A close event whose Type carries a failure suffix (.failed, .invalid_args, .policy_exhausted, .cancelled) marks the span status codes.Error. An opener nests under the most recent still-open span of the SAME identity quadruple (a tool span becomes a child of its task span), so the trace tree mirrors the run hierarchy. Every non-lifecycle event attaches as a span event on the most recent open span for its quadruple; with no enclosing span it is recorded as a standalone instantaneous span so nothing is silently dropped (CLAUDE.md §13).
Identity + run IDs ride as span attributes via Tracer.SpanFromEvent — the bridge never widens the metrics derivation (which stays Type/Producer/Node only; the brief 06 cardinality split is enforced by keeping the two bridges separate, not blurred).
Volume control ¶
A chatty bus produces span floods on busy runs; the bridge takes an events.Filter so the production assembly scopes the subscription. DefaultTraceBridgeFilter limits the production bridge to the lifecycle pairs above (streaming chunks et al. never become span traffic); embedders that want span events for every type pass a broader filter. See docs/recipes/observe-an-embedded-runtime.md.
Index ¶
- Constants
- Variables
- func BridgeBusToMetrics(ctx context.Context, bus events.EventBus, reg *MetricsRegistry, ...) (stop func(), err error)
- func BridgeBusToTracer(ctx context.Context, bus events.EventBus, tracer *Tracer, f events.Filter) (stop func(), err error)
- func DefaultTraceBridgeFilter() events.Filter
- func ExtractEnv(ctx context.Context, environ []string) context.Context
- func ExtractHTTP(ctx context.Context, h http.Header) context.Context
- func ExtractMeta(ctx context.Context, meta map[string]any) context.Context
- func InjectEnv(ctx context.Context, env []string) []string
- func InjectHTTP(ctx context.Context, h http.Header)
- func InjectMeta(ctx context.Context, meta map[string]any)
- func PrometheusHandler(reg *MetricsRegistry) (http.Handler, error)
- func RegisterExporter(name string, e SpanExporter)
- func RegisterMetricExporter(name string, e MetricExporter)
- type BusEmitter
- type CounterPoint
- type Logger
- func (l *Logger) Debug(ctx context.Context, msg string, attrs ...slog.Attr)
- func (l *Logger) Error(ctx context.Context, msg string, attrs ...slog.Attr)
- func (l *Logger) Info(ctx context.Context, msg string, attrs ...slog.Attr)
- func (l *Logger) Slog() *slog.Logger
- func (l *Logger) Warn(ctx context.Context, msg string, attrs ...slog.Attr)
- func (l *Logger) With(attrs ...slog.Attr) *Logger
- func (l *Logger) WithIdentity(id identity.Identity) *Logger
- func (l *Logger) WithRun(q identity.Quadruple) *Logger
- type MetricExporter
- type MetricSnapshot
- type MetricsOption
- type MetricsRegistry
- type Option
- type PromGatherer
- type SpanExporter
- type Tracer
- type TracerOption
Constants ¶
const ( // EnvTraceparent is the env var key carrying the W3C traceparent // to a stdio child process spawned by Harbor. EnvTraceparent = "HARBOR_TRACEPARENT" // EnvTracestate is the env var key carrying the W3C tracestate to // a stdio child process. Present only when the upstream trace // carried tracestate. EnvTracestate = "HARBOR_TRACESTATE" )
W3C header / map / env key names.
Variables ¶
var ( // ErrLoggerNotConfigured — the constructor received an invalid // TelemetryConfig (unknown LogFormat, unknown LogLevel, etc). // Phase 02 already validates these but the constructor must not // trust upstream; misconfiguration here is a fail-loudly. ErrLoggerNotConfigured = errors.New("telemetry: logger not configured") // ErrRedactorMissing — the constructor was given a nil redactor. // Construction-time only: New wraps both this sentinel and // audit.ErrRedactorMissing so callers can errors.Is on either. // Runtime redaction errors (Rule.Apply failures) bubble up via // audit.ErrRedactionFailed instead — distinct error class. ErrRedactorMissing = errors.New("telemetry: redactor missing") )
Sentinel errors. Callers compare via errors.Is.
var ( // ErrMetricsNotConfigured — NewMetricsRegistry received an invalid // TelemetryConfig (e.g. an empty ServiceName). Phase 02 validates // config upstream, but the constructor must not trust it. ErrMetricsNotConfigured = errors.New("telemetry: metrics registry not configured") // ErrMetricExporterUnknown — an explicitly-configured metric // exporter driver is not in the registry. The formatted message // lists the registered driver names so a misconfiguration is // obvious. ErrMetricExporterUnknown = errors.New("telemetry: unknown metric exporter driver") // a *MetricsRegistry that was not built with the prometheus // exporter driver active. The pull handler needs the prometheus // driver's Gatherer; an OTLP-backed registry has no pull surface. ErrPrometheusHandlerUnavailable = errors.New("telemetry: prometheus handler unavailable (registry not built with the prometheus exporter)") )
Sentinel errors. Callers compare via errors.Is.
var ( // ErrTracerNotConfigured — NewTracer received an invalid // TelemetryConfig (e.g. an empty ServiceName). Phase 02 validates // config upstream, but the constructor must not trust it. ErrTracerNotConfigured = errors.New("telemetry: tracer not configured") // ErrExporterUnknown — an explicitly-configured exporter driver is // not in the registry. The formatted message lists the registered // driver names so a misconfiguration is obvious. ErrExporterUnknown = errors.New("telemetry: unknown span exporter driver") )
Sentinel errors. Callers compare via errors.Is.
var ErrBridgeMisconfigured = errors.New("telemetry: BridgeBusToMetrics missing a mandatory dependency")
ErrBridgeMisconfigured — BridgeBusToMetrics was called with a nil bus or registry. Fails closed (CLAUDE.md §5).
var ErrTraceBridgeMisconfigured = errors.New("telemetry: BridgeBusToTracer missing a mandatory dependency")
ErrTraceBridgeMisconfigured — BridgeBusToTracer was called with a nil bus or tracer. Fails closed (CLAUDE.md §5), mirroring ErrBridgeMisconfigured on the metrics side.
Functions ¶
func BridgeBusToMetrics ¶
func BridgeBusToMetrics(ctx context.Context, bus events.EventBus, reg *MetricsRegistry, f events.Filter) (stop func(), err error)
BridgeBusToMetrics wires an events.EventBus → MetricsRegistry bridge: it subscribes to the bus with the supplied filter and calls reg.RegisterEvent for every delivered event. The returned `stop` function cancels the subscription and joins the drain goroutine; production callers `defer stop()` at process teardown.
PR #91 / D-082 (Wave 10 audit WARN-6): this helper pins the events→metrics wiring shape in production code BEFORE the Phase 64 `harbor dev` bootstrap reinvents it. Phase 56 originally shipped the bridge only as a test-local goroutine in `test/integration/phase56_metrics_test.go::drainToMetrics`; extracting it here exposes one canonical contract for the future server bootstrap to consume.
Both bus and reg are mandatory; a nil either returns ErrBridgeMisconfigured. The filter is the standard events.Filter shape — production wiring passes a triple-scoped filter for a per-session bridge, or an Admin filter for a fleet-wide bridge (the wire identity scope on the events.Filter is the caller's responsibility — the bridge does NOT widen the boundary).
The bridge is fail-soft on a per-event RegisterEvent (the OTel counter Add is in-memory and cannot fail), so the drain goroutine runs until the subscription channel closes — same lifecycle the existing test-local drainToMetrics implementation pinned.
func BridgeBusToTracer ¶ added in v1.3.0
func BridgeBusToTracer(ctx context.Context, bus events.EventBus, tracer *Tracer, f events.Filter) (stop func(), err error)
BridgeBusToTracer wires an events.EventBus → *Tracer bridge: it subscribes to the bus with the supplied filter and derives OTel spans per the package-doc span model. The returned stop function cancels the subscription, joins the drain goroutine, and ends any still-open spans; it is idempotent. Production callers defer it at process teardown (the assembly registers it on the closer chain).
Both bus and tracer are mandatory; a nil either returns ErrTraceBridgeMisconfigured. The drain goroutine runs until the subscription channel closes — the same lifecycle contract as BridgeBusToMetrics. Per-event work is in-memory span bookkeeping and cannot fail, so the bridge has no per-event error path.
func DefaultTraceBridgeFilter ¶ added in v1.3.0
DefaultTraceBridgeFilter is the production assembly's subscription filter: a fleet-wide (Admin) subscription narrowed to the canonical lifecycle pairs, so a chatty bus (streaming chunks, memory traffic) never becomes span traffic. The Admin claim mirrors the metrics bridge's fleet-wide derivation (a runtime-internal infra consumer; it does not widen the isolation boundary — identity still rides every span as attributes). Embedders that want broader span events pass their own filter to BridgeBusToTracer.
func ExtractEnv ¶
ExtractEnv returns a ctx carrying the remote span context decoded from HARBOR_TRACEPARENT (and HARBOR_TRACESTATE) in a process environment slice — the inverse of InjectEnv, called by a Harbor child process at startup to continue the parent's trace. A slice without a valid HARBOR_TRACEPARENT yields a ctx with no valid span context (best-effort).
func ExtractHTTP ¶
ExtractHTTP returns a ctx carrying the remote span context decoded from the W3C headers in h. A malformed or absent traceparent yields a ctx with no valid span context (best-effort, never panics) — the caller's own SpanFromEvent then starts a root span instead of a child.
func ExtractMeta ¶
ExtractMeta returns a ctx carrying the remote span context decoded from a stdio-MCP `_meta` map. A nil map or a map without a valid traceparent yields a ctx with no valid span context (best-effort).
func InjectEnv ¶
InjectEnv appends HARBOR_TRACEPARENT (and HARBOR_TRACESTATE, when present) to a process environment slice and returns the extended slice. The input slice is not mutated in place beyond the append — callers pass the result to exec.Cmd.Env. A ctx with no active span returns env unchanged.
func InjectHTTP ¶
InjectHTTP writes the W3C traceparent (and tracestate, when the span context carries it) header from the span context in ctx into h. A ctx with no active span writes nothing — h is left untouched.
func InjectMeta ¶
InjectMeta writes traceparent (and tracestate, when present) into a stdio-MCP `_meta` map. A nil map is a no-op; a ctx with no active span writes nothing.
func PrometheusHandler ¶
func PrometheusHandler(reg *MetricsRegistry) (http.Handler, error)
PrometheusHandler returns the http.Handler that serves the Prometheus text exposition format for reg. The Phase 60+ Runtime server mounts it at /metrics; RFC §6.14 promotes a built-in Prometheus endpoint to V1 for self-hosted setups.
PrometheusHandler returns a wrapped ErrPrometheusHandlerUnavailable when reg was not constructed with the prometheus exporter driver active — an OTLP-backed registry pushes to a collector and has no pull surface. The returned handler is safe for concurrent use (promhttp.HandlerFor's handler is).
func RegisterExporter ¶
func RegisterExporter(name string, e SpanExporter)
RegisterExporter installs a SpanExporter driver under name. Called from a driver package's init(). Re-registering the same name is a no-op (last registration wins is avoided — first wins, deterministic under blank-import ordering is not guaranteed so we keep it a no-op); registering an empty name panics, because a silent accept would defeat the registry's single-source-of-truth invariant.
func RegisterMetricExporter ¶
func RegisterMetricExporter(name string, e MetricExporter)
RegisterMetricExporter installs a MetricExporter driver under name. Called from a driver package's init(). Re-registering the same name is a no-op (first registration wins — deterministic blank-import ordering is not guaranteed, so a no-op is the safe choice); registering an empty name or a nil exporter panics, because a silent accept would defeat the registry's single-source-of-truth invariant.
Types ¶
type BusEmitter ¶
BusEmitter is the seam Phase 05+ uses to make Logger.Error fire a paired runtime.error event. Phase 04 ships the noopEmitter default; the production emitter is wired via WithBusEmitter once the event bus lands.
Implementations MUST NOT call back into the originating Logger (directly or transitively): Logger.Error guards against single-step recursion via a context sentinel, but a bus emitter that hands its payload to a *different* Logger that re-enters this one would bypass the guard. Keep emitter implementations simple.
type CounterPoint ¶
type CounterPoint struct {
// Name is the metric name (e.g. "harbor_events_total").
Name string
// Value is the counter's current cumulative value.
Value float64
// Labels is the data point's low-cardinality label set. The
// cardinality firewall guarantees no run_id / trace_id appears here.
Labels map[string]string
}
CounterPoint is one counter data point in a MetricSnapshot — a metric name, its current value, and its low-cardinality label set. It is a telemetry-local, OTel-SDK-free struct so callers (the Protocol posture surface's `metrics.snapshot` projection) can read live metrics without importing the OpenTelemetry SDK types (CLAUDE.md §13 single-source rule — the wire `MetricsSnapshot` is projected from this shape, not from `metricdata.*`).
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger is the canonical structured logger. Built once at boot via New; safe for concurrent use; immutable after construction.
WithIdentity, WithRun, and With return DERIVED *Logger instances — the receiver is never mutated. Bound attributes flow through every subsequent emit; ctx-derived identity attributes fill in keys not already bound. Per-call attributes append last.
func New ¶
New constructs a Logger from validated config and a Redactor. Returns a wrapped sentinel on invalid input. The handler is chosen once at construction and never swapped (RFC §6.14: no in-library toggle).
func (*Logger) Debug ¶
Debug emits a structured record at debug severity. Identity attributes are auto-stamped from ctx; every value flows through the configured Redactor before the slog handler is invoked.
func (*Logger) Error ¶
Error emits a structured record at error severity AND fires the configured BusEmitter.EmitRuntimeError. Recursion via the bus emitter is broken by a context sentinel: an emitter that hands control back to this Logger.Error uses a ctx that suppresses the second emitter invocation (the slog record still writes).
A panic from the bus emitter is recovered and recorded as a Warn entry (`telemetry.bus_emitter_panicked`); the panic does not propagate to the caller.
func (*Logger) Slog ¶ added in v1.3.0
Slog returns a *slog.Logger backed by this Logger: every record is routed through the canonical pipeline (ctx identity stamping, mandatory redaction, the bus-paired Error emit). Level filtering follows the Logger's configured log_level. Use it to thread the telemetry Logger into components that accept a plain *slog.Logger (CLAUDE.md §5 — one logger).
Mapping: records at or above slog.LevelError route through Logger.Error (slog record + paired runtime.error bus event); Warn / Info / Debug route through the matching Logger method. The record's own timestamp and PC are dropped — the pipeline stamps its own time, exactly as a direct Logger call does.
The returned *slog.Logger is safe for concurrent use (the Logger's D-025 contract carries over; the bridge holds no mutable state).
func (*Logger) With ¶
With returns a derived Logger carrying additional bound attributes. In Phase 04 this is the only path for task_id, trace_id, span_id, and tool — Phase 55 wires OTel-driven trace_id / span_id auto-stamping.
func (*Logger) WithIdentity ¶
WithIdentity returns a derived Logger that pre-stamps tenant_id, user_id, session_id from id. The base Logger is unchanged.
type MetricExporter ¶
type MetricExporter interface {
// Reader returns a ready metric.Reader. ctx scopes any connection
// setup; cfg carries the OTLP endpoint / service name.
Reader(ctx context.Context, cfg config.TelemetryConfig) (sdkmetric.Reader, error)
}
MetricExporter is the §4.4 seam. A driver constructs and returns a ready OTel SDK metric.Reader for the given config. Drivers (otlpmetric, prometheus) self-register from their init() via RegisterMetricExporter; callers depend only on this package.
The prometheus driver's Reader additionally satisfies the PromGatherer interface below so PrometheusHandler can recover its scrape registry; the otlpmetric driver's Reader does not (an OTLP-push registry has no pull surface), and PrometheusHandler returns ErrPrometheusHandlerUnavailable for it.
type MetricSnapshot ¶
type MetricSnapshot struct {
// Counters is the flat counter-data-point slice.
Counters []CounterPoint
}
MetricSnapshot is the telemetry-local read-out of the registry's live metrics — a flat slice of counter data points collected from the active SDK reader. It is the source the Protocol `metrics.snapshot` projection maps onto `types.MetricsSnapshot`. The struct carries no OTel SDK type so the Protocol boundary stays single-sourced.
type MetricsOption ¶
type MetricsOption func(*metricsConfig)
MetricsOption configures the MetricsRegistry at construction.
func WithMetricExporterDriver ¶
func WithMetricExporterDriver(name string) MetricsOption
WithMetricExporterDriver forces a specific registered metric-exporter driver by name, regardless of cfg.OTelEndpoint. Used by tests to exercise the ErrMetricExporterUnknown path deterministically and to select the prometheus driver explicitly. Production callers rely on the OTelEndpoint-based selection in NewMetricsRegistry.
func WithMetricReader ¶
func WithMetricReader(r sdkmetric.Reader) MetricsOption
WithMetricReader injects a pre-built sdkmetric.Reader, bypassing the driver registry. Test-only seam — a test uses a sdkmetric.ManualReader so recorded metrics are observable. Documented inline; no production caller uses it.
type MetricsRegistry ¶
type MetricsRegistry struct {
// contains filtered or unexported fields
}
MetricsRegistry is the canonical OTel metrics wrapper. Built once at boot via NewMetricsRegistry; safe for concurrent use; immutable after construction (D-025).
The struct holds only read-only references after construction: the SDK MeterProvider, the reader (kept so PrometheusHandler can recover the prometheus driver's Gatherer), and the core Int64Counter instruments. No field mutates post-construction; per-call state (the attribute set for one event) is built on the stack in RegisterEvent, never stored on the registry.
func NewMetricsRegistry ¶
func NewMetricsRegistry(cfg config.TelemetryConfig, opts ...MetricsOption) (*MetricsRegistry, func(context.Context) error, error)
NewMetricsRegistry constructs a MetricsRegistry from validated config. The returned shutdown func flushes and shuts down the reader + provider; callers defer it at process teardown.
Exporter selection: an empty cfg.OTelEndpoint selects the prometheus driver (pull-only /metrics; no collector needed); a non-empty endpoint selects the otlpmetric driver (OTLP/gRPC push). WithMetricExporterDriver / WithMetricReader override this for tests.
NewMetricsRegistry touches NO OTel global — it builds a private MeterProvider and the registry is passed to callers explicitly. A global MeterProvider would be a parallel, ambient metrics channel; the explicit-handle discipline keeps metrics a derivation of the event bus (the registry is wired where events are emitted).
func (*MetricsRegistry) RegisterEvent ¶
func (r *MetricsRegistry) RegisterEvent(ctx context.Context, ev events.Event)
RegisterEvent increments the canonical harbor_events_total counter from ev. The labels are derived ONLY from:
- event_type — ev.Type, verbatim. RegisterEvent does NOT re-validate against the events canonical registry; the bus already did at Publish time, and a metric for an otherwise-unknown type is still useful signal.
- producer — ev.Extra["producer"], or "unknown" when absent.
- node — ev.Extra["node"], or "" when absent.
RegisterEvent reads NO field of ev.Identity. The run quadruple — tenant_id / user_id / session_id / run_id — never reaches a metric label. This is the cardinality firewall (brief 06): a developer cannot accidentally tag a metric by RunID / TraceID here because there is no code path that touches ev.Identity. The static cardinality-lint (internal/telemetry/cardinalitylint) is the CI gate that keeps any future instrument honest.
ctx is honoured for cancellation but RegisterEvent does no I/O — the Int64Counter.Add is an in-memory aggregation; the reader exports asynchronously.
func (*MetricsRegistry) Snapshot ¶
func (r *MetricsRegistry) Snapshot(ctx context.Context) (MetricSnapshot, error)
Snapshot collects the registry's live metrics from the active SDK reader and projects them onto the OTel-SDK-free MetricSnapshot shape.
Snapshot is the production seam the Phase 72f `metrics.snapshot` posture method reads — the boot path wires it into `protocol.PostureDeps.Metrics`. It is safe for concurrent use: the reader's Collect is concurrency-safe and Snapshot holds no per-call state on the registry (D-025).
A reader Collect failure is returned wrapped — never silently degraded to an empty snapshot (CLAUDE.md §13 "Silent degradation").
type Option ¶
type Option func(*Logger)
Option configures the Logger at construction.
func WithBusEmitter ¶
func WithBusEmitter(b BusEmitter) Option
WithBusEmitter installs the production runtime.error emitter. Phase 05+ wires this when constructing the Logger. Without it, Logger.Error only writes the slog record (the default noopEmitter is a no-op).
func WithWriter ¶
WithWriter sets the destination io.Writer for the slog handler. Default is os.Stdout. Tests use this to inspect emitted records; production callers do not need it. Concurrent writes to the supplied writer must be safe at the writer's own layer — the Logger does not serialize writes itself (the slog handler does best-effort serialization but bytes.Buffer is not safe).
type PromGatherer ¶
type PromGatherer interface {
// PrometheusGatherer returns the prometheus.Gatherer the driver's
// scrape registry is registered with.
PrometheusGatherer() prometheus.Gatherer
}
PromGatherer is the contract the prometheus driver's metric.Reader satisfies so PrometheusHandler can recover the prometheus.Gatherer it builds a promhttp handler from. This is NOT a §4.4 "Supports*" capability smell — it is a single driver-specific pull surface that only one of the two drivers can have (an OTLP-push reader has no scrape registry), so the absence is a genuine fact, not an optional feature toggle. The type assertion in PrometheusHandler is how the fact is discovered.
The method is exported because the prometheus driver lives in a sub-package (internal/telemetry/drivers/prometheus) and must satisfy the interface across the package boundary; only the prometheus driver is expected to implement it.
type SpanExporter ¶
type SpanExporter interface {
// Exporter returns a ready trace.SpanExporter. ctx scopes any
// connection setup; cfg carries the OTLP endpoint / service name.
Exporter(ctx context.Context, cfg config.TelemetryConfig) (sdktrace.SpanExporter, error)
}
SpanExporter is the §4.4 seam. A driver constructs and returns a ready OTel SDK trace.SpanExporter for the given config. Drivers (noop, otlp) self-register from their init() via RegisterExporter; callers depend only on this package.
type Tracer ¶
type Tracer struct {
// contains filtered or unexported fields
}
Tracer is the canonical OTel tracer wrapper. Built once at boot via NewTracer; safe for concurrent use; immutable after construction (D-025).
The struct holds only read-only references after construction: the SDK TracerProvider, the trace.Tracer obtained from it, and the W3C TextMapPropagator. No field mutates post-construction; per-span state lives in the returned ctx, never on the Tracer.
func NewTracer ¶
func NewTracer(cfg config.TelemetryConfig, opts ...TracerOption) (*Tracer, func(context.Context) error, error)
NewTracer constructs a Tracer from validated config. The returned shutdown func flushes and shuts down the exporter + provider; callers defer it at process teardown.
Exporter selection: an empty cfg.OTelEndpoint selects the noop driver (spans are still created so in-process propagation works); a non-empty endpoint selects the otlp driver. WithExporterDriver / WithSpanExporter override this for tests.
NewTracer sets the OTel global TextMapPropagator to the W3C TraceContext propagator. That global is write-once mutable package state in the OTel SDK; setting it to the same value on every NewTracer call is idempotent, so repeated construction (tests) is safe.
func (*Tracer) LogAttrs ¶
LogAttrs returns the trace_id / span_id slog.Attr pair from the span context in ctx. Returns an empty slice when no valid span is active — the Phase 04 Logger elides absent attributes, so an empty slice composes cleanly: logger.With(tracer.LogAttrs(ctx)...).
LogAttrs is a free function in spirit (it does not touch Tracer fields) but is a method so the call site reads as "the tracer's log attributes" and so a future change can consult tracer state without a signature break.
func (*Tracer) SpanFromEvent ¶
SpanFromEvent starts a span derived from ev. The span name is derived from ev.Type; the identity quadruple and ev.Extra become span attributes. NO EventPayload bytes are stamped onto the span — payload content is not span-safe and the audit redactor is the only sanctioniser of payload bytes (D-020). The returned ctx carries the new span; the caller is responsible for ending the span (the integration / unit tests do so explicitly).
Run / step alignment: an event carrying a run_id produces a span attributed to that run. When ctx already carries a parent span (the run span), the new span is a child of it — so a step-granularity event type produces a child span under the run span and the trace tree mirrors the run/step hierarchy.
type TracerOption ¶
type TracerOption func(*tracerConfig)
TracerOption configures the Tracer at construction.
func WithExporterDriver ¶
func WithExporterDriver(name string) TracerOption
WithExporterDriver forces a specific registered exporter driver by name, regardless of cfg.OTelEndpoint. Used by tests to exercise the ErrExporterUnknown path deterministically and to select the noop driver explicitly. Production callers rely on the OTelEndpoint-based selection in NewTracer.
func WithSpanExporter ¶
func WithSpanExporter(e sdktrace.SpanExporter) TracerOption
WithSpanExporter injects a pre-built trace.SpanExporter, bypassing the driver registry. Test-only seam — the integration test uses an in-memory recorder so emitted spans are observable. Documented inline; no production caller uses it.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package cardinalitylint is the Harbor metrics-cardinality enforcement checker (RFC §6.14; brief 06 "metrics cardinality footgun").
|
Package cardinalitylint is the Harbor metrics-cardinality enforcement checker (RFC §6.14; brief 06 "metrics cardinality footgun"). |
|
drivers
|
|
|
noop
Package noop is the default telemetry.SpanExporter driver — it drops every span without error.
|
Package noop is the default telemetry.SpanExporter driver — it drops every span without error. |
|
otlp
Package otlp is the OTLP/gRPC telemetry.SpanExporter driver — it ships spans to an external OpenTelemetry collector (Jaeger, an OTLP collector, an observability vendor's endpoint).
|
Package otlp is the OTLP/gRPC telemetry.SpanExporter driver — it ships spans to an external OpenTelemetry collector (Jaeger, an OTLP collector, an observability vendor's endpoint). |
|
otlpmetric
Package otlpmetric is the OTLP/gRPC telemetry.MetricExporter driver — it ships metrics to an external OpenTelemetry collector (an OTLP collector, an observability vendor's endpoint).
|
Package otlpmetric is the OTLP/gRPC telemetry.MetricExporter driver — it ships metrics to an external OpenTelemetry collector (an OTLP collector, an observability vendor's endpoint). |
|
prometheus
Package prometheus is the built-in Prometheus telemetry.MetricExporter driver — it exposes Harbor's metrics on a pull /metrics endpoint in the Prometheus text exposition format.
|
Package prometheus is the built-in Prometheus telemetry.MetricExporter driver — it exposes Harbor's metrics on a pull /metrics endpoint in the Prometheus text exposition format. |
|
Package eventbus is the adapter that connects Phase 04's telemetry.BusEmitter seam to Phase 05's events.EventBus.
|
Package eventbus is the adapter that connects Phase 04's telemetry.BusEmitter seam to Phase 05's events.EventBus. |