otelprop

package
v2.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 21, 2026 License: Apache-2.0 Imports: 5 Imported by: 2

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

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.

Jump to

Keyboard shortcuts

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