Documentation
¶
Overview ¶
Package otelprop propagates OpenTelemetry context on http-kit requests.
It lives in its own package so that importing the root package does not drag OpenTelemetry -- go.opentelemetry.io/otel, its metric and trace modules and go.opentelemetry.io/auto/sdk, plus go-logr and cespare/xxhash behind them -- into binaries that never emit a span. A service that only wants a retrying, mTLS-capable HTTP client pays nothing for tracing support existing; only importing this package links it in.
client, err := httpkit.NewClient(&httpkit.Options{
BaseURL: "https://api.example.com",
Propagator: otelprop.Global(),
})
From there every request the client sends carries the headers the configured propagator writes. Nothing else has to be remembered at the call site, which is what httpkit.Client.InjectTraceContext required and what made a missing traceparent a silent failure.
The package is a translation layer and nothing more: which propagators are installed, what a span is and whether anything is sampled all remain OpenTelemetry's business.
Example ¶
Set the propagator once, on the client, and every request it sends carries the trace headers. The former Client.InjectTraceContext had to be called at each call site, and a forgotten one broke the trace with no error anywhere.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
httpkit "github.com/soulteary/http-kit/v2"
"github.com/soulteary/http-kit/v2/otelprop"
)
func main() {
// Normally done once in main.
otel.SetTextMapPropagator(propagation.TraceContext{})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("traceparent:", r.Header.Get("Traceparent"))
}))
defer srv.Close()
client, err := httpkit.NewClient(&httpkit.Options{
BaseURL: srv.URL,
Propagator: otelprop.Global(),
})
if err != nil {
panic(err)
}
req, err := client.NewRequest(exampleSpanContext(), http.MethodGet, "/data", nil)
if err != nil {
panic(err)
}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
_ = resp.Body.Close()
}
// exampleSpanContext stands in for a context carrying a live span. A
// propagator writes nothing without one.
func exampleSpanContext() context.Context {
traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7")
return trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
TraceFlags: trace.FlagsSampled,
}))
}
Output: traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Global ¶
func Global() httpkit.Propagator
Global returns a propagator that resolves otel.GetTextMapPropagator at each injection, which is what httpkit.Client.InjectTraceContext did.
Resolving late is the part that matters: otel.SetTextMapPropagator is usually called from main, and a client built during package initialisation or by a constructor that runs earlier must still pick it up. A propagator captured at construction time would freeze OpenTelemetry's no-op default and inject nothing, for the whole life of the process, with no error anywhere.
func New ¶
func New(p propagation.TextMapPropagator) httpkit.Propagator
New returns a propagator backed by a specific TextMapPropagator, for a service that configures propagation per client rather than globally:
otelprop.New(propagation.TraceContext{})
otelprop.New(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{},
))
A nil p yields nil, which httpkit.Client treats as "no propagation" rather than as an error -- and never as a panic on the first request, which is what handing a nil interface straight through would cost.
Example ¶
New pins a propagator per client, for a service that does not configure one globally. MultiPropagator composes it with the service's own headers.
package main
import (
"context"
"fmt"
"net/http"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
httpkit "github.com/soulteary/http-kit/v2"
"github.com/soulteary/http-kit/v2/otelprop"
)
func main() {
p := httpkit.MultiPropagator(
otelprop.New(propagation.TraceContext{}),
httpkit.PropagatorFunc(func(_ context.Context, h http.Header) {
h.Set("X-Request-ID", "req-1")
}),
)
h := http.Header{}
p.Inject(exampleSpanContext(), h)
fmt.Println(h.Get("Traceparent"))
fmt.Println(h.Get("X-Request-ID"))
}
// exampleSpanContext stands in for a context carrying a live span. A
// propagator writes nothing without one.
func exampleSpanContext() context.Context {
traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7")
return trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
TraceFlags: trace.FlagsSampled,
}))
}
Output: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 req-1
Types ¶
This section is empty.