Documentation
¶
Overview ¶
Package otel adapts the OpenTelemetry Go SDK (go.opentelemetry.io/otel) to mcpkit's dependency-free core.TracerProvider contract. Wire a Provider constructed from a real otelsdk.TracerProvider into the server via server.WithTracerProvider, and the SEP-414 P2 trace middleware will emit inbound spans on every JSON-RPC dispatch plus propagate W3C trace context on every outbound notification / server-to-client request.
Phase 4 deliverable for SEP-414 (issue 312). Phase 2 (PR 649) shipped the dispatch-path wiring; this package is the SDK-backed adapter that turns those spans into something an exporter (stdout, OTLP, Jaeger, ...) can publish.
Phase 3 (client-side spans) and Phase 5 (the polished examples/otel/ walkthrough doc) are tracked separately on issue 312.
Index ¶
- func NewTracerProvider(exporter sdktrace.SpanExporter, opts ...TracerProviderOption) *sdktrace.TracerProvider
- type MeterOption
- type MeterProvider
- func (p *MeterProvider) Float64Histogram(name string, opts ...core.InstrumentOption) core.Float64Histogram
- func (p *MeterProvider) Int64Counter(name string, opts ...core.InstrumentOption) core.Int64Counter
- func (p *MeterProvider) Int64UpDownCounter(name string, opts ...core.InstrumentOption) core.Int64UpDownCounter
- func (p *MeterProvider) OTelMeterProvider() otelmetric.MeterProvider
- type Option
- type Provider
- func (p *Provider) OTelTracerProvider() oteltrace.TracerProvider
- func (p *Provider) StartSpan(ctx context.Context, name string, attrs ...core.Attribute) (context.Context, core.Span)
- func (p *Provider) StartSpanLinked(ctx context.Context, name string, links []core.Link, attrs ...core.Attribute) (context.Context, core.Span)
- type Span
- type TracerProviderOption
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewTracerProvider ¶
func NewTracerProvider(exporter sdktrace.SpanExporter, opts ...TracerProviderOption) *sdktrace.TracerProvider
NewTracerProvider builds an `*sdktrace.TracerProvider` with the supplied exporter and options. The returned TracerProvider is ready to hand to mcpotel.NewProvider(otelTP) for wiring into server.WithTracerProvider or client.WithTracerProvider.
Panics if exporter is nil — silently constructing a TracerProvider that emits no spans loses signals without surfacing the misconfiguration. Same fail-fast convention as mcpotel.NewProvider(nil otelTP).
The default span processor is batched (production-aligned). Pass WithSyncer to switch to sync exporting. The default Resource is the SDK default (no service.name); pass WithServiceName to set it.
Future options (`WithDeploymentEnvironment`, `WithServiceVersion`, `WithResource` escape hatch) compose against the same internal config — they will land when a real consumer asks. Issue 663 (P6 umbrella) tracks the SEP-414 surface work that consumes this helper.
Types ¶
type MeterOption ¶
type MeterOption func(*meterProviderConfig)
MeterOption mutates a meterProviderConfig during NewMeterProvider. Exported so user-side libraries can layer their own helpers without depending on the unexported config shape — matches the trace-side Option pattern.
func WithMeterInstrumentationName ¶
func WithMeterInstrumentationName(name string) MeterOption
WithMeterInstrumentationName overrides the OTel instrumentation library name used when constructing the Meter from the OTel MeterProvider. Backends index metrics by this name; leave the default unless your server embeds mcpkit inside a larger SDK and you want a more specific identifier.
Empty name reverts to the package default.
type MeterProvider ¶
type MeterProvider struct {
// contains filtered or unexported fields
}
MeterProvider wraps an OpenTelemetry MeterProvider and exposes it through the dependency-free core.MeterProvider contract. Construct via NewMeterProvider; pair with the OTel SDK side (sdk/metric).
MeterProvider is safe for concurrent use. The internal Meter is created once at construction so subsequent Int64Counter / Float64Histogram / Int64UpDownCounter factory calls never re-look up the Meter on the hot path.
func NewMeterProvider ¶
func NewMeterProvider(otelMP otelmetric.MeterProvider, opts ...MeterOption) *MeterProvider
NewMeterProvider constructs a MeterProvider backed by the given OTel MeterProvider. Panics if otelMP is nil — a wrapper without a backing provider would silently lose measurements, so the check fails fast at wiring time. Matches the NewProvider trace-side contract.
Typical wiring:
import (
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
mcpotel "github.com/panyam/mcpkit/ext/otel"
)
exp, _ := otlpmetricgrpc.New(ctx)
otelMP := sdkmetric.NewMeterProvider(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp)))
defer otelMP.Shutdown(ctx)
srv := server.NewServer(info, server.WithMeterProvider(mcpotel.NewMeterProvider(otelMP)))
func (*MeterProvider) Float64Histogram ¶
func (p *MeterProvider) Float64Histogram(name string, opts ...core.InstrumentOption) core.Float64Histogram
Float64Histogram implements core.MeterProvider. See Int64Counter for the construction-failure handling.
func (*MeterProvider) Int64Counter ¶
func (p *MeterProvider) Int64Counter(name string, opts ...core.InstrumentOption) core.Int64Counter
Int64Counter implements core.MeterProvider. Each call constructs a fresh OTel instrument; instrument-creation errors fall back to a no-op counter so a misconfigured Meter never crashes the dispatch install — matches the trace-side defensive contract on Provider. The fallback path logs no warning because OTel instrument creation only fails on duplicate-with-conflicting-config errors that the SDK already loudly surfaces via its own logger.
func (*MeterProvider) Int64UpDownCounter ¶
func (p *MeterProvider) Int64UpDownCounter(name string, opts ...core.InstrumentOption) core.Int64UpDownCounter
Int64UpDownCounter implements core.MeterProvider. See Int64Counter for the construction-failure handling.
func (*MeterProvider) OTelMeterProvider ¶
func (p *MeterProvider) OTelMeterProvider() otelmetric.MeterProvider
OTelMeterProvider returns the underlying OpenTelemetry MeterProvider the wrapper was constructed with. Used by callers that need to hand the same MeterProvider to downstream libraries which take an OTel MeterProvider directly — so the whole process shares one metrics pipeline.
The returned value is the same pointer NewMeterProvider was called with; nil-checks on the caller side are unnecessary (NewMeterProvider panics on nil).
type Option ¶
type Option func(*providerConfig)
Option mutates a providerConfig during NewProvider. The Option type is exported so user-side libraries can layer their own helpers (e.g., reading instrumentation name from a config struct) without depending on the unexported config shape.
func WithInstrumentationName ¶
WithInstrumentationName overrides the OTel instrumentation library name used when constructing the Tracer from the OTel TracerProvider. The instrumentation name is what observability backends use to group spans by emitting library — leave the default unless your server embeds mcpkit inside a larger SDK and you want a more specific identifier.
Empty name reverts to the package default.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider wraps an OpenTelemetry TracerProvider and exposes it through the dependency-free core.TracerProvider contract that mcpkit's dispatch path expects. One Provider per server is the common case; the underlying OTel TracerProvider is the unit of exporter configuration and span batching, so multiple Providers backed by the same OTel TracerProvider share the same exporter pipeline.
Provider is safe for concurrent use. The internal Tracer is created once at construction time so StartSpan never pays the Tracer-lookup cost on the hot path.
func NewProvider ¶
func NewProvider(otelTP oteltrace.TracerProvider, opts ...Option) *Provider
NewProvider constructs a Provider backed by the given OTel TracerProvider. Panics if otelTP is nil — a Provider without a real backing TracerProvider would silently lose spans, so the check fails fast at wiring time.
Typical wiring:
import (
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
mcpotel "github.com/panyam/mcpkit/ext/otel"
)
exp, _ := stdouttrace.New()
otelTP := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp))
defer otelTP.Shutdown(ctx)
srv := server.NewServer(info, server.WithTracerProvider(mcpotel.NewProvider(otelTP)))
func (*Provider) OTelTracerProvider ¶
func (p *Provider) OTelTracerProvider() oteltrace.TracerProvider
OTelTracerProvider returns the underlying OpenTelemetry TracerProvider the Provider was constructed with. Used by callers that need to hand the same TracerProvider to downstream libraries which take an OTel TracerProvider directly — oneauth's keys.WithTracerProvider, future grpc/http instrumentation, etc. — so all instrumentation in the process shares one span pipeline.
The returned value is the same pointer NewProvider was called with; nil-checks on the caller side are unnecessary (NewProvider panics on nil).
func (*Provider) StartSpan ¶
func (p *Provider) StartSpan(ctx context.Context, name string, attrs ...core.Attribute) (context.Context, core.Span)
StartSpan implements core.TracerProvider. The returned context carries BOTH the OTel span context (so any OTel-instrumented library called by the handler sees the new span as parent) AND a mcpkit core.TraceContext updated to reflect the new span (so the SEP-414 P2 outbound _meta injection wraps stamp the wire with the child span's traceparent rather than the inbound parent's).
Inbound trace context resolution:
- If ctx already carries a non-zero core.TraceContext (attached by the server's traceMiddleware after _meta extraction or the SEP-2028 HTTP header bridge), it is parsed into an OTel SpanContext and installed as the parent via trace.ContextWithSpanContext.
- Otherwise the OTel SDK's default propagation behavior applies (the span starts a fresh trace).
Attribute mapping: each core.Attribute becomes an attribute.String key. P1's contract scopes attributes to string/string; typed attributes arrive when the core surface widens.
func (*Provider) StartSpanLinked ¶
func (p *Provider) StartSpanLinked(ctx context.Context, name string, links []core.Link, attrs ...core.Attribute) (context.Context, core.Span)
StartSpanLinked implements core.LinkedTracerProvider — the option-at-start variant of StartSpan that attaches one or more causal Links to the new span (in addition to any parent extracted from ctx). Each Link entry whose TraceContext is zero or fails W3C validation is silently dropped (caller does not need to pre-filter), so callers can build the slice from raw inputs of varying validity. Per-link Attributes are mapped through as attribute.String entries on the resulting OTel link.
Implementation: routes the same parent-install + span-start + active-span publish dance as StartSpan, with a WithLinks option appended to the startOpts slice. Empty / nil links behaves identically to StartSpan.
type Span ¶
type Span struct {
// contains filtered or unexported fields
}
Span implements core.Span by delegating to an underlying go.opentelemetry.io/otel/trace.Span. The wrapper exists for two reasons: to narrow OTel's broader Span surface to the three-method contract mcpkit middleware consumes, and to enforce the "End is exactly-once" invariant from core/trace.go even though OTel's implementations log a noisy warning rather than treating double-End as a contract violation.
Safe for concurrent SetAttribute / RecordError calls (the underlying OTel SDK Span is thread-safe). End uses a CAS to guarantee the underlying End() runs at most once.
func (*Span) AddLink ¶
AddLink attaches a causal Link to the span mid-flight. Delegates to the underlying OTel SDK Span.AddLink (available since OTel Go v1.30). No-op after End, matching the contract on every other Span method — the OTel SDK's "AddLink must happen before the span is read" guarantee is satisfied by the ended-guard here PLUS the underlying SDK's own enforcement.
Invalid Link entries (zero or malformed TraceContext) are silently dropped, matching the core.Span.AddLink contract — defensive call sites can build Link slices from raw inputs without pre-filtering.
Per-link Attributes flow through as attribute.String entries on the OTel link, where observability backends render them as link metadata (Jaeger / Tempo / Honeycomb show these specially in their link UI panels, separate from span attributes).
func (*Span) End ¶
func (s *Span) End()
End closes the span. Subsequent calls are no-ops per the core.Span contract — the wrapper short-circuits a second call before reaching the OTel SDK, preventing the SDK's "span already ended" log warning from firing in test loops or unusual handler unwinding paths.
func (*Span) RecordError ¶
RecordError attaches err to the span via OTel's RecordError (which emits an "exception" event) AND sets the span status to codes.Error. Mirrors the OTel idiom that an unhandled error should both surface as an event and degrade the span's status — observability backends use Status.Code to filter / count error spans.
Nil err is a no-op (matches the core.Span contract).
func (*Span) SetAttribute ¶
SetAttribute records a string-valued attribute on the underlying OTel span. No-op after End. Mirrors the core.Span contract; numeric and boolean attributes are outside the P1 surface — callers stringify upstream.
type TracerProviderOption ¶
type TracerProviderOption func(*tracerProviderConfig)
TracerProviderOption mutates the SDK TracerProvider construction performed by NewTracerProvider. Pass any combination of the With... options below; later options override earlier ones when they touch the same config field.
func WithServiceName ¶
func WithServiceName(name string) TracerProviderOption
WithServiceName sets the OpenTelemetry `service.name` Resource attribute on the constructed TracerProvider. Observability backends (Grafana / Tempo / Jaeger / Honeycomb) index spans by this attribute as the primary axis — without it, traces appear under `unknown_service:<binary>`.
An empty name is treated as "leave default" — the caller may pass through a config-file value without branching on whether the field is populated. The SDK's default Resource still applies in that case (which usually means `unknown_service:<binary>` in Grafana, but at least the call site doesn't crash).
func WithSyncer ¶
func WithSyncer() TracerProviderOption
WithSyncer switches the TracerProvider from the default batched span processor to a synchronous one: every span ships immediately on End(), no batch-flush window. Slightly slower per-span at the transport layer (gRPC for OTLP, line write for stdout), but the trade-off is the right one for teaching demos and tests where an operator who kills the process seconds after `tools/call` returns must see their spans in the backend, not wait for a batch flush that never happens.
Real high-throughput servers should stay on the default (WithBatcher) and handle SIGTERM with an explicit ForceFlush + Shutdown sequence.