wrapper

package
v0.1.0-develop.2026061... Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 11, 2026 License: MIT Imports: 11 Imported by: 0

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

View Source
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

func ContractIDFromContext(ctx context.Context) string

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:

  1. write ctxkeys.ContractID so slog + downstream handlers can read it
  2. 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

func SetSpanName(s Span, name string)

SetSpanName invokes SetName if the span implements SpanRenamer. Spans that do not support rename are silently skipped.

func SpanIDFromContext

func SpanIDFromContext(ctx context.Context) string

SpanIDFromContext returns the span identifier carried on ctx, or "" if absent.

func TraceIDFromContext

func TraceIDFromContext(ctx context.Context) string

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

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

type Attr struct {
	Key   string
	Value any
}

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 Entry

type Entry = outbox.Entry

Entry is an alias for outbox.Entry. Use wrapper.Entry in handler signatures.

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.

func (NoopTracer) Start

func (NoopTracer) Start(ctx context.Context, _ string, _ ...Attr) (context.Context, Span)

Start returns the context unchanged and a noopSpan singleton — zero allocations in the hot path.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL