Documentation
¶
Overview ¶
Package wrapper binds contracts to runtime observability primitives.
A Cell registers an HTTP handler via runtime/auth.Mount or an outbox consumer via runtime/eventrouter with a ContractSpec — the spec carries the contract id, transport, method/path (or topic). wrapper.HTTPHandler contributes HTTP contract attributes to the runtime HTTP middleware's single request span; wrapper.WrapSubscriber owns the consumer delivery span and annotates it with gocell.contract.* plus messaging.destination/system. The consumer Tracer is injected by bootstrap, defaulting to NoopTracer.
ref: go-kratos/kratos middleware/tracing/tracing.go — decorator + Options ref: open-telemetry/opentelemetry-go-contrib otelhttp/config.go — Filter and SpanNameFormatter extensibility ref: riandyrn/otelchi middleware.go — chi RouteContext span-name fallback
Index ¶
- Constants
- func ContractIDFromContext(ctx context.Context) string
- func HTTPHandler(spec contractspec.ContractSpec, next http.Handler) (http.Handler, error)
- func SetSpanName(s Span, name string)
- func SpanIDFromContext(ctx context.Context) string
- func TraceIDFromContext(ctx context.Context) string
- func WithAttrCarrier(ctx context.Context, c *AttrCarrier) context.Context
- func WrapSubscriber(tr Tracer, spec contractspec.ContractSpec, fn outbox.SubscriberHandler) (outbox.SubscriberHandler, error)
- type Attr
- type AttrCarrier
- type ConsumerFunc
- type Disposition
- type Entry
- type HandleResult
- type NoopTracer
- type Span
- type SpanRenamer
- type StatusCode
- type Tracer
Constants ¶
const ( // DispositionAck re-exports outbox.DispositionAck. DispositionAck = outbox.DispositionAck // DispositionRequeue re-exports outbox.DispositionRequeue. DispositionRequeue = outbox.DispositionRequeue // DispositionReject re-exports outbox.DispositionReject. DispositionReject = outbox.DispositionReject )
Variables ¶
This section is empty.
Functions ¶
func ContractIDFromContext ¶
ContractIDFromContext returns the contract identifier carried on ctx. It is populated by HTTPHandler / WrapConsumer at request / event entry.
func HTTPHandler ¶
func HTTPHandler(spec contractspec.ContractSpec, next http.Handler) (http.Handler, error)
HTTPHandler wraps next with contract-id propagation and contract-derived span attribute contribution. It does NOT create its own trace span — the outer runtime/http/middleware.Tracing middleware owns the single request-owned span per the round-4 single-owner design. HTTPHandler's job is purely ctx-plumbing:
- write ctxkeys.ContractID so slog + downstream handlers can read it
- append contract base attributes (gocell.contract.id / kind / transport + http.method / http.route) to the AttrCarrier installed by the outer Tracing middleware; after next.ServeHTTP returns the middleware late-binds the collected attributes onto its span
When no AttrCarrier is present (unit tests that exercise HTTPHandler standalone, ad-hoc wiring without the outer Tracing chain), the attribute contribution is silently skipped — there is no span to decorate anyway, and ContractID is still written for slog + handler consumption.
Panic recovery: HTTPHandler does NOT install its own recover(). Panics propagate up to runtime/http/middleware.Recovery (error response) and runtime/http/middleware.Tracing (span status = error + RecordError on the outer span).
spec is validated at call time; invalid specs or nil handlers cause a non-nil error to be returned so the caller can choose between fail-fast composition time and graceful refusal at runtime registration.
ref: go-kratos/kratos middleware/tracing/tracing.go — the middleware is the single HTTP server span owner; handlers contribute attributes, not spans. ref: open-telemetry/opentelemetry-go-contrib otelhttp — "one middleware, one span" invariant; late-binding route metadata via chi RouteContext.
func SetSpanName ¶
SetSpanName invokes SetName if the span implements SpanRenamer. Spans that do not support rename are silently skipped.
func SpanIDFromContext ¶
SpanIDFromContext returns the span identifier carried on ctx, or "" if absent.
func TraceIDFromContext ¶
TraceIDFromContext returns the trace identifier carried on ctx, or "" if absent. It is a thin bridge over pkg/ctxkeys.TraceIDFrom so callers can depend on wrapper without pulling pkg/ctxkeys.
func WithAttrCarrier ¶
func WithAttrCarrier(ctx context.Context, c *AttrCarrier) context.Context
WithAttrCarrier returns a new context carrying c. A nil c is a no-op (returns ctx unchanged) so callers that do not need the carrier (tests, ad-hoc handler wiring without the outer HTTP Tracing middleware) need not guard at the call site.
func WrapSubscriber ¶
func WrapSubscriber(tr Tracer, spec contractspec.ContractSpec, fn outbox.SubscriberHandler) (outbox.SubscriberHandler, error)
WrapSubscriber wraps a SubscriberHandler with a contract delivery span whose status is resolved by the final broker settlement notification. It starts one span per delivery attempt, appends a SettlementObserver to the returned DeliveryOutcome, and ends the span when the subscriber calls outbox.NotifySettlement.
This differs from WrapConsumer: Consumer middleware only sees the business HandleResult before Commit/Ack/Nack, while this wrapper observes the Subscriber layer after settlement downgrades such as commit_failed.
WrapSubscriber relies on its caller (currently runtime/eventrouter.contractTracingSubscriber) to have validated the owning outbox.Subscription via Subscription.Validate before constructing the spec, and on registration-time spec validation in eventrouter.AddContractHandler. Both upstream layers run spec.Validate or its primitive subset; running it again here would be a redundant third pass without adding any safety margin (P2 #1 — single-source validation per Watermill / MassTransit pattern). Structural assertions (nil fn, kind!=event) remain because they catch programming errors that no upstream validate would have caught.
Types ¶
type Attr ¶
Attr is a key-value pair recorded on a Span. Value is any so adapters can handle type-specific conversions (OTel attribute.Key, slog fields, ...); producers should prefer string / int64 / bool for cross-adapter portability.
type AttrCarrier ¶
type AttrCarrier struct {
Attrs []Attr
}
AttrCarrier is a mutable holder for contract-derived span attributes. It is installed into the request context by the outer HTTP Tracing middleware before routing; HTTPHandler appends to it when the request reaches the contract-bound handler; Tracing reads the slice back after next.ServeHTTP returns and late-binds the attributes onto the single request-owned span.
The carrier pattern mirrors chi's RouteContext: a pointer is stored in ctx once at the outermost layer, and nested handlers mutate its target. This is the canonical Go idiom when data must propagate "upwards" from a nested handler to an outer middleware span, since context.WithValue only adds new values to derived children.
Rationale for putting the type + helpers in kernel/wrapper (not kernel/ctxkeys):
- the typed Attr slice is wrapper.Attr; keeping the carrier next to its payload type avoids cycles in the opposite direction.
- kernel/wrapper already depends on kernel/ctxkeys for ContractID, so the ctxkeys.ContractAttrs key can be referenced here directly.
ref: go-chi/chi/v5 middleware/middleware.go — RouteContext's mutable routeContext struct in ctx, propagated up via chi-internal pointer.
func AttrCarrierFrom ¶
func AttrCarrierFrom(ctx context.Context) (*AttrCarrier, bool)
AttrCarrierFrom returns the attribute carrier from ctx. Reports false when the key is absent or holds nil — in which case callers skip attribute contribution (e.g. unit tests exercise HTTPHandler directly without the Tracing middleware chain, producing no span and no carrier).
type ConsumerFunc ¶
type ConsumerFunc = outbox.EntryHandler
ConsumerFunc mirrors outbox.EntryHandler so Cells can wrap their existing handlers without changing signatures. It is re-exported here so wrapper callers do not need to import kernel/outbox for the type.
func WrapConsumer ¶
func WrapConsumer(tr Tracer, spec contractspec.ContractSpec, fn ConsumerFunc) (ConsumerFunc, error)
WrapConsumer wraps fn with a traced span + contract-id context derivation. The wrapper:
- starts a span named "CONSUME {topic}" using the supplied tracer
- sets gocell.contract.id / kind / transport, messaging.system / destination attrs
- SetStatus(Error) + RecordError for any Requeue / Reject disposition (the disposition itself is the authoritative control-flow signal — wrapper does not modify it)
- propagates contract id through the context passed to fn
- defers recoverAndFinish so a panic in fn ends the span before re-panicking
tr is the Tracer supplied by the runtime infrastructure (typically runtime/eventrouter.Router). A nil tr falls back to NoopTracer{} — spans are silently discarded rather than panicking.
spec must have Kind == "event" and Topic set; fn must be non-nil.
Error redaction is hardcoded at the sink (adapters/otel/span.go otelSpan.RecordError) so sensitive substrings (password=, token=, Authorization: Bearer …) never reach the trace backend. Callers pass the raw error; the otelSpan sink applies pkg/redaction.RedactError before forwarding to the OTel SDK. There is no caller-side opt-out — dev / test surfaces that need raw error text read it from slog structured fields. ref: hashicorp/vault audit/entry_formatter.go (log_raw=false default) ref: golang/go src/net/url/url.go URL.Redacted()
Returns a non-nil error when fn is nil, spec.Kind != "event", or spec.Validate fails. Callers that want to fail-fast at composition time should propagate the error to bootstrap; tests use the local wrapOrFatal helper in wrapper_test (no production Must variant exists).
type Disposition ¶
type Disposition = outbox.Disposition
Disposition is an alias for outbox.Disposition.
type HandleResult ¶
type HandleResult = outbox.HandleResult
HandleResult is an alias for outbox.HandleResult.
type NoopTracer ¶
type NoopTracer struct{}
NoopTracer is a zero-allocation Tracer. Used by WrapConsumer as the fallback when no runtime tracer is wired (nil Tracer argument), and by tests that exercise the wrapper path without an adapter dependency.
HTTPHandler no longer creates spans (the outer HTTP Tracing middleware owns the single request span), so it does not reference Tracer at all — NoopTracer is only used on the consumer side (WrapConsumer) where the event router is the span owner and bootstrap is the tracer injector.
type Span ¶
type Span interface {
// SetAttributes records key-value pairs on the span.
SetAttributes(attrs ...Attr)
// RecordError attaches an error event to the span. Recording an error
// does not by itself mark the span as failed — callers should also call
// SetStatus(StatusError, "...") when the error is terminal.
RecordError(err error)
// SetStatus records the final success/failure status for the span. The
// last SetStatus call wins. Description is free-form; adapters MAY
// truncate long values.
SetStatus(code StatusCode, description string)
// End finalizes the span. Calls after End are no-ops; implementations
// SHOULD ignore post-End mutations silently.
End()
}
Span represents a single unit of work in a trace. Implementations MUST be safe for concurrent use — middleware and handlers running on the same request may emit attributes from multiple goroutines (ResponseWriter observers, error recorders, etc.).
type SpanRenamer ¶
type SpanRenamer interface {
SetName(name string)
}
SpanRenamer is an optional interface that a Span implementation MAY support when the final span name is only known after the handler / routing completes (e.g. chi matches the path template post-ServeHTTP). Helpers that want to stay compatible with Span implementations lacking rename support should use SetSpanName instead of a direct type assertion.
NoopTracer's span implements SetName as a silent no-op, so callers can invoke SetSpanName unconditionally regardless of whether a real tracer is wired — there is no "rename unsupported" branch to worry about when tracing is disabled.
ref: riandyrn/otelchi middleware.go — chi routes are known after ServeHTTP so span.name is adjusted in two phases.
type StatusCode ¶
type StatusCode int
StatusCode is the span-level success / failure marker. Adapters translate these ordinals into backend-specific status codes (OTel `codes.Ok`/`codes.Error`, Zipkin error tags, etc.).
const ( // StatusOK indicates the span completed successfully. It is the zero value // so Span implementations default to OK unless SetStatus is called. StatusOK StatusCode = 0 // StatusError marks the span as failed; adapters SHOULD surface this as // an error-level classification in their backend. StatusError StatusCode = 1 )
type Tracer ¶
type Tracer interface {
Start(ctx context.Context, spanName string, attrs ...Attr) (context.Context, Span)
}
Tracer creates spans. The returned context carries the span so downstream code may read the active trace identity via TraceIDFromContext / SpanIDFromContext.
Implementations that continue a trace present on the input context (e.g. an OTel SDK) SHOULD do so; the NoopTracer accepts any context unchanged.