Documentation
¶
Overview ¶
Package trace produces distributed traces for messages moving through a channel, and exports them over OTLP/HTTP with the JSON encoding.
Why this exists rather than the OpenTelemetry SDK: the SDK plus an OTLP exporter pulls in roughly sixty modules and adds about as much to the binary as the whole of the rest of this program. What is actually needed here is narrow - W3C trace context propagation, a span model, head sampling, and a batched HTTP POST - and OTLP/JSON is a documented stable encoding that can be produced with the standard library alone. Same trade as choosing lib/pq over pgx: take the small thing when only part of the big thing is wanted.
The consequence worth stating: there is no auto-instrumentation and no vendor-specific exporter. Spans are the ones this program creates deliberately, and they go to any OTLP endpoint - a collector, Jaeger, Grafana Tempo, Honeycomb, Datadog's OTLP ingest.
The property that matters more than the spans ¶
A message frequently arrives carrying a traceparent header from the system that sent it, and leaves toward a system that will record one of its own. Continuing that trace rather than starting a fresh one is what makes a single view of "the EHR sent this, the engine transformed it, the registry rejected it" possible at all. Interface problems are almost always reported as "we sent it and nothing happened", and that question is unanswerable without an identifier that survives the hop.
Tracing must never affect delivery ¶
Every operation here is best-effort. Spans are dropped when the buffer is full, export failures are counted and never retried into the message path, and no call blocks on the network. A dropped span is an inconvenience. A message delayed or lost because the tracing backend was slow is a clinical incident, so the trade is never in question.
Index ¶
Constants ¶
const TraceparentHeader = "traceparent"
TraceparentHeader is the W3C header name.
Variables ¶
This section is empty.
Functions ¶
func Traceparent ¶
func Traceparent(sc SpanContext) string
Traceparent formats a span context as a W3C traceparent value.
Returns "" for an invalid context rather than emitting a malformed header, because a malformed traceparent is worse than none: a compliant receiver must discard it and start a new trace, and the resulting broken link is far harder to diagnose than an absent one.
func WithRemote ¶
func WithRemote(ctx context.Context, sc SpanContext) context.Context
WithRemote attaches an inbound span context to a context.
Separate from WithSpan because they mean different things: WithSpan carries a span this process created and is a parent, while WithRemote carries what another system told us and is a parent only if we have nothing closer. A source parses the header, and the channel decides what to do with it - which keeps header formats out of the engine and channel semantics out of the transports.
Types ¶
type Config ¶
type Config struct {
// Endpoint is the OTLP/HTTP base URL, for example http://localhost:4318.
// Empty disables tracing entirely.
Endpoint string
// ServiceName identifies this instance in the trace backend.
ServiceName string
// ServiceVersion is reported as a resource attribute.
ServiceVersion string
// Instance distinguishes replicas. Without it, spans from three pods are
// indistinguishable, which defeats the point of tracing a scaled deployment.
Instance string
// SampleRatio is the fraction of new traces recorded, 0 to 1.
//
// Head sampling, decided once at the root and propagated, because the
// alternative - deciding per span - produces traces with holes in them.
SampleRatio float64
// Headers are added to every export request, for backends that authenticate
// with one.
Headers map[string]string
// Timeout bounds a single export request.
Timeout time.Duration
// MaxQueue bounds spans held awaiting export.
MaxQueue int
// BatchSize is how many spans go in one request.
BatchSize int
// FlushInterval is how often a partial batch is sent anyway, so a quiet
// system still reports promptly rather than holding spans until the batch
// fills.
FlushInterval time.Duration
}
Config configures tracing.
type Kind ¶
type Kind int
Kind describes a span's role, using the OTLP numeric values directly so no mapping table is needed at export.
type Span ¶
type Span struct {
// contains filtered or unexported fields
}
Span is one timed operation.
A nil *Span is valid and every method on it is a no-op, so instrumentation can be written without checking whether tracing is enabled. That matters because the alternative is an `if tracer != nil` around every call site, which is where somebody eventually forgets and panics on a message.
func FromContext ¶
FromContext returns the span in the context, or nil.
nil is a working span, so callers do not need to check.
func (*Span) Context ¶
func (s *Span) Context() SpanContext
Context returns the span's own context, for propagation.
func (*Span) End ¶
func (s *Span) End()
End closes the span and hands it to the exporter.
Safe to call twice; the second call does nothing. That matters because the natural way to write this is a defer plus an explicit End on an early return, and a double-End should not produce a duplicate span.
func (*Span) SetError ¶
SetError marks the span failed and records the message.
The error text is clipped and comes from this program's own errors, not from message content.
func (*Span) SetString ¶
SetString records a string attribute.
Deliberately no method taking arbitrary values: an attribute set from message content is how patient data ends up in a third-party observability platform that has no agreement covering it. Callers must pass something they have decided is safe, and the type signature is the reminder.
type SpanContext ¶
type SpanContext struct {
TraceID TraceID
SpanID SpanID
// Sampled carries the sampling decision onward. Once a trace is sampled every
// participant should sample it, or the resulting trace has holes exactly
// where the interesting hop was.
Sampled bool
// Remote marks a context parsed from an inbound header rather than created
// here, which is worth knowing when deciding whether to trust its sampling
// decision.
Remote bool
}
SpanContext is the part of a span that crosses process boundaries.
func ParseTraceparent ¶
func ParseTraceparent(v string) SpanContext
ParseTraceparent reads a W3C traceparent value.
Unparseable input yields an invalid context and no error, deliberately. A caller's only sensible response to a malformed inbound header is to start a fresh trace, and making that an error invites somebody to reject the message over a header problem.
func RemoteFromContext ¶
func RemoteFromContext(ctx context.Context) SpanContext
RemoteFromContext returns the inbound span context, if a source recorded one.
func (SpanContext) Valid ¶
func (sc SpanContext) Valid() bool
Valid reports whether the context can parent a span.
type Stats ¶
type Stats struct {
Exported int64 `json:"exported"`
Dropped int64 `json:"dropped"`
Failures int64 `json:"failures"`
Queued int `json:"queued"`
}
Stats reports export counters, so tracing's own health is visible. A tracing system that silently stops working is worse than none, because decisions get made on the assumption that an absent span means an absent operation.
type TraceID ¶
type TraceID [16]byte
TraceID is a 16-byte identifier shared by every span in one trace.
type Tracer ¶
type Tracer struct {
// contains filtered or unexported fields
}
Tracer creates spans and exports them.
A nil *Tracer is usable and produces nil spans, so a caller never needs to know whether tracing is configured.
func New ¶
New starts a tracer. A Config with no Endpoint returns nil, which is a working no-op tracer.
func (*Tracer) Start ¶
func (t *Tracer) Start(ctx context.Context, name string, kind Kind, remote SpanContext) (context.Context, *Span)
Start begins a span.
The parent is taken from the context when one is present. Otherwise a new trace begins, unless remote carries a valid inbound context - which is the case that makes a trace span two systems.