telemetry API

telemetry

package

API reference for the telemetry package.

S
struct

OTLPExporter

OTLPExporter sends spans and metrics to an OTLP-compatible endpoint over HTTP/JSON.

pkg/telemetry/exporters.go:18-27
type OTLPExporter struct

Methods

ExportSpan
Method

Parameters

span otlpSpan
func (*OTLPExporter) ExportSpan(span otlpSpan)
{
	e.batchMu.Lock()
	e.spans = append(e.spans, span)
	shouldFlush := len(e.spans) >= e.maxBatch
	e.batchMu.Unlock()
	if shouldFlush {
		e.Flush(context.Background())
	}
}
ExportMetric
Method

Parameters

func (*OTLPExporter) ExportMetric(m otlpMetric)
{
	e.batchMu.Lock()
	e.metrics = append(e.metrics, m)
	shouldFlush := len(e.metrics) >= e.maxBatch
	e.batchMu.Unlock()
	if shouldFlush {
		e.Flush(context.Background())
	}
}
Flush
Method

Parameters

Returns

error
func (*OTLPExporter) Flush(ctx context.Context) error
{
	e.batchMu.Lock()
	spans := e.spans
	metrics := e.metrics
	e.spans = nil
	e.metrics = nil
	e.batchMu.Unlock()

	if len(spans) == 0 && len(metrics) == 0 {
		return nil
	}

	payload := map[string]any{
		"resourceSpans": []map[string]any{
			{
				"scopeSpans": []map[string]any{
					{"spans": spans},
				},
			},
		},
		"resourceMetrics": []map[string]any{
			{
				"scopeMetrics": []map[string]any{
					{"metrics": metrics},
				},
			},
		},
	}

	data, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("otlp marshal: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "POST", e.endpoint, bytes.NewReader(data))
	if err != nil {
		return fmt.Errorf("otlp request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := e.client.Do(req)
	if err != nil {
		return fmt.Errorf("otlp send: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 300 {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("otlp status %d: %s", resp.StatusCode, string(body))
	}
	return nil
}
loop
Method
func (*OTLPExporter) loop()
{
	ticker := time.NewTicker(e.flushMs)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			e.Flush(context.Background())
		case <-e.stopCh:
			e.Flush(context.Background())
			return
		}
	}
}
Close
Method
func (*OTLPExporter) Close()
{
	close(e.stopCh)
}

Fields

Name Type Description
endpoint string
client *http.Client
batchMu sync.Mutex
spans []otlpSpan
metrics []otlpMetric
maxBatch int
flushMs time.Duration
stopCh chan struct{}
S
struct

otlpSpan

pkg/telemetry/exporters.go:29-36
type otlpSpan struct

Fields

Name Type Description
TraceID string json:"traceId"
SpanID string json:"spanId"
Name string json:"name"
StartTime string json:"startTimeUnixNano"
EndTime string json:"endTimeUnixNano"
Attrs map[string]any json:"attributes,omitempty"
S
struct

otlpMetric

pkg/telemetry/exporters.go:38-42
type otlpMetric struct

Fields

Name Type Description
Name string json:"name"
Kind string json:"kind"
Value float64 json:"value"
T
type

OTLPOption

OTLPOption configures an OTLPExporter.

pkg/telemetry/exporters.go:45-45
type OTLPOption func(*OTLPExporter)
F
function

WithOTLPEndpoint

WithOTLPEndpoint sets the OTLP receiver endpoint URL.

Parameters

url
string

Returns

pkg/telemetry/exporters.go:48-50
func WithOTLPEndpoint(url string) OTLPOption

{
	return func(e *OTLPExporter) { e.endpoint = url }
}
F
function

WithOTLPBatchSize

WithOTLPBatchSize sets the maximum batch size before flushing.

Parameters

n
int

Returns

pkg/telemetry/exporters.go:53-55
func WithOTLPBatchSize(n int) OTLPOption

{
	return func(e *OTLPExporter) { e.maxBatch = n }
}
F
function

NewOTLPExporter

NewOTLPExporter creates a new OTLPExporter with the given options.

Parameters

opts
...OTLPOption

Returns

pkg/telemetry/exporters.go:58-71
func NewOTLPExporter(opts ...OTLPOption) *OTLPExporter

{
	e := &OTLPExporter{
		endpoint: "http://localhost:4318/v1/traces",
		client:   &http.Client{Timeout: 5 * time.Second},
		maxBatch: 100,
		flushMs:  5000 * time.Millisecond,
		stopCh:   make(chan struct{}),
	}
	for _, opt := range opts {
		opt(e)
	}
	go e.loop()
	return e
}
S
struct

PrometheusExporter

pkg/telemetry/exporters.go:165-170
type PrometheusExporter struct

Methods

IncCounter
Method

Parameters

name string
delta int64
func (*PrometheusExporter) IncCounter(name string, delta int64)
{
	p.mu.Lock()
	defer p.mu.Unlock()
	p.counters[name] += delta
}
SetGauge
Method

Parameters

name string
value float64
func (*PrometheusExporter) SetGauge(name string, value float64)
{
	p.mu.Lock()
	defer p.mu.Unlock()
	p.gauges[name] = value
}

Parameters

name string
value float64
buckets []float64
func (*PrometheusExporter) ObserveHistogram(name string, value float64, buckets []float64)
{
	p.mu.Lock()
	defer p.mu.Unlock()
	h, ok := p.histos[name]
	if !ok {
		h = &promHistogram{buckets: make(map[float64]int64), sum: 0, count: 0}
		for _, b := range buckets {
			h.buckets[b] = 0
		}
		p.histos[name] = h
	}
	h.count++
	h.sum += value
	for _, b := range buckets {
		if value <= b {
			h.buckets[b]++
		}
	}
}
WriteText
Method

Parameters

func (*PrometheusExporter) WriteText(w io.Writer)
{
	p.mu.RLock()
	defer p.mu.RUnlock()

	for name, val := range p.counters {
		fmt.Fprintf(w, "# TYPE %s counter\n%s %d\n", name, name, val)
	}
	for name, val := range p.gauges {
		fmt.Fprintf(w, "# TYPE %s gauge\n%s %g\n", name, name, val)
	}
	for name, h := range p.histos {
		fmt.Fprintf(w, "# TYPE %s histogram\n", name)
		for _, b := range sortedKeys(h.buckets) {
			fmt.Fprintf(w, "%s_bucket{le=\"%g\"} %d\n", name, b, h.buckets[b])
		}
		fmt.Fprintf(w, "%s_bucket{le=\"+Inf\"} %d\n", name, h.count)
		fmt.Fprintf(w, "%s_sum %g\n", name, h.sum)
		fmt.Fprintf(w, "%s_count %d\n", name, h.count)
	}
}

Fields

Name Type Description
mu sync.RWMutex
counters map[string]int64
gauges map[string]float64
histos map[string]*promHistogram
S
struct

promHistogram

pkg/telemetry/exporters.go:172-176
type promHistogram struct

Fields

Name Type Description
buckets map[float64]int64
sum float64
count int64
F
function

NewPrometheusExporter

NewPrometheusExporter creates a new PrometheusExporter.

pkg/telemetry/exporters.go:179-185
func NewPrometheusExporter() *PrometheusExporter

{
	return &PrometheusExporter{
		counters: make(map[string]int64),
		gauges:  make(map[string]float64),
		histos:  make(map[string]*promHistogram),
	}
}
F
function

sortedKeys

Parameters

m
map[float64]int64

Returns

[]float64
pkg/telemetry/exporters.go:240-247
func sortedKeys(m map[float64]int64) []float64

{
	keys := make([]float64, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sortFloat64s(keys)
	return keys
}
F
function

sortFloat64s

Parameters

a
[]float64
pkg/telemetry/exporters.go:249-255
func sortFloat64s(a []float64)

{
	for i := 1; i < len(a); i++ {
		for j := i; j > 0 && a[j] < a[j-1]; j-- {
			a[j], a[j-1] = a[j-1], a[j]
		}
	}
}
S
struct

TraceContext

pkg/telemetry/exporters.go:259-263
type TraceContext struct

Methods

Encode
Method

Returns

string
func (*TraceContext) Encode() string
{
	return fmt.Sprintf("00-%s-%s-%s", tc.TraceID, tc.ParentID, tc.TraceFlags)
}

Fields

Name Type Description
TraceID string
ParentID string
TraceFlags string
F
function

ParseTraceparent

ParseTraceparent parses a W3C traceparent header.

Parameters

header
string

Returns

error
pkg/telemetry/exporters.go:266-286
func ParseTraceparent(header string) (*TraceContext, error)

{
	parts := strings.Split(header, "-")
	if len(parts) != 4 {
		return nil, fmt.Errorf("invalid traceparent: expected 4 parts, got %d", len(parts))
	}
	if parts[0] != "00" {
		return nil, fmt.Errorf("unsupported traceparent version: %s", parts[0])
	}
	tc := &TraceContext{
		TraceID:    parts[1],
		ParentID:   parts[2],
		TraceFlags: parts[3],
	}
	if len(tc.TraceID) != 32 {
		return nil, fmt.Errorf("invalid trace ID length: %d", len(tc.TraceID))
	}
	if len(tc.ParentID) != 16 {
		return nil, fmt.Errorf("invalid parent ID length: %d", len(tc.ParentID))
	}
	return tc, nil
}
S
struct

TelemetryMiddleware

pkg/telemetry/exporters.go:294-296
type TelemetryMiddleware struct

Methods

WrapHTTP
Method

SrvMiddleware returns a middleware function compatible with the srv package. It accepts a HandlerFunc type to avoid circular import - use via adapter.

Parameters

Returns

func (*TelemetryMiddleware) WrapHTTP(next http.Handler) http.Handler
{
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		traceCtx, _ := ParseTraceparent(r.Header.Get("traceparent"))

		var span Span
		ctx := r.Context()
		if tm.Provider != nil && tm.Provider.Tracer != nil {
			attrs := []Attribute{
				{Key: "http.method", Value: r.Method},
				{Key: "http.url", Value: r.URL.String()},
			}
			if traceCtx != nil {
				attrs = append(attrs,
					Attribute{Key: "trace.parent_id", Value: traceCtx.ParentID},
				)
			}
			span, ctx = tm.Provider.Tracer.Start(ctx, r.Method+" "+r.URL.Path, attrs...)
		}

		rw := &telemetryResponseWriter{ResponseWriter: w, statusCode: 200}

		next.ServeHTTP(rw, r.WithContext(ctx))

		if span != nil {
			span.SetAttributes(
				Attribute{Key: "http.status_code", Value: rw.statusCode},
				Attribute{Key: "http.duration_ms", Value: time.Since(start).Milliseconds()},
			)
			span.End()
		}

		if tm.Provider != nil && tm.Provider.Meter != nil {
			counter := tm.Provider.Meter.Counter("http_requests_total",
				Attribute{Key: "method", Value: r.Method},
				Attribute{Key: "path", Value: r.URL.Path},
			)
			counter.Add(ctx, 1)

			hist := tm.Provider.Meter.Histogram("http_request_duration_ms",
				Attribute{Key: "method", Value: r.Method},
			)
			hist.Record(ctx, float64(time.Since(start).Milliseconds()))
		}

		if traceCtx != nil {
			w.Header().Set("traceparent", traceCtx.Encode())
		}
	})
}

Fields

Name Type Description
Provider *Provider
F
function

NewTelemetryMiddleware

NewTelemetryMiddleware creates a new TelemetryMiddleware wrapping the given provider.

Parameters

provider
pkg/telemetry/exporters.go:299-301
func NewTelemetryMiddleware(provider *Provider) *TelemetryMiddleware

{
	return &TelemetryMiddleware{Provider: provider}
}
S
struct

telemetryResponseWriter

pkg/telemetry/exporters.go:356-359
type telemetryResponseWriter struct

Methods

WriteHeader
Method

Parameters

code int
func (*telemetryResponseWriter) WriteHeader(code int)
{
	w.statusCode = code
	w.ResponseWriter.WriteHeader(code)
}

Fields

Name Type Description
statusCode int
I
interface

Tracer

Tracer creates and manages spans.

pkg/telemetry/telemetry.go:13-15
type Tracer interface

Methods

Start
Method

Parameters

name string
attrs ...Attribute
func Start(...)
I
interface

Span

Span represents an active span in a trace.

pkg/telemetry/telemetry.go:18-21
type Span interface

Methods

SetAttributes
Method

Parameters

attrs ...Attribute
func SetAttributes(...)
End
Method
func End(...)
I
interface

Meter

Meter creates and records metrics.

pkg/telemetry/telemetry.go:24-28
type Meter interface

Methods

Counter
Method

Parameters

name string
attrs ...Attribute

Returns

func Counter(...)
Histogram
Method

Parameters

name string
attrs ...Attribute

Returns

func Histogram(...)
Gauge
Method

Parameters

name string
attrs ...Attribute

Returns

func Gauge(...)
I
interface

Counter

Counter records monotonically increasing values.

pkg/telemetry/telemetry.go:31-33
type Counter interface

Methods

Add
Method

Parameters

delta int64
func Add(...)
I
interface

Histogram

Histogram records distribution of values.

pkg/telemetry/telemetry.go:36-38
type Histogram interface

Methods

Record
Method

Parameters

value float64
func Record(...)
I
interface

Gauge

Gauge records current values.

pkg/telemetry/telemetry.go:41-43
type Gauge interface

Methods

Set
Method

Parameters

value float64
func Set(...)
S
struct

Attribute

Attribute is a key-value pair for spans and metrics.

pkg/telemetry/telemetry.go:46-49
type Attribute struct

Fields

Name Type Description
Key string
Value any
S
struct

Provider

Provider is the central telemetry hub.

pkg/telemetry/telemetry.go:52-56
type Provider struct

Methods

Shutdown
Method

Shutdown cleans up all providers.

func (*Provider) Shutdown()
{
	for _, f := range p.shutdown {
		f()
	}
}

Fields

Name Type Description
Tracer Tracer
Meter Meter
shutdown []func()
T
type

Option

Option configures a Provider.

pkg/telemetry/telemetry.go:59-59
type Option options.Option[Provider]
F
function

NewProvider

NewProvider creates a telemetry provider with noop defaults.

Parameters

opts
...Option

Returns

pkg/telemetry/telemetry.go:62-71
func NewProvider(opts ...Option) *Provider

{
	p := &Provider{
		Tracer: noopTracerInst,
		Meter:  noopMeterInst,
	}
	for _, opt := range opts {
		opt(p)
	}
	return p
}
F
function

WithTracer

WithTracer sets the tracer implementation.

Parameters

t

Returns

pkg/telemetry/telemetry.go:74-76
func WithTracer(t Tracer) Option

{
	return func(p *Provider) { p.Tracer = t }
}
F
function

WithMeter

WithMeter sets the meter implementation.

Parameters

m

Returns

pkg/telemetry/telemetry.go:79-81
func WithMeter(m Meter) Option

{
	return func(p *Provider) { p.Meter = m }
}
F
function

Timed

Timed measures and records the duration of fn as a metric.

Parameters

fn
func()

Returns

pkg/telemetry/telemetry.go:91-97
func Timed(ctx context.Context, h Histogram, fn func()) time.Duration

{
	start := time.Now()
	fn()
	dur := time.Since(start)
	h.Record(ctx, dur.Seconds())
	return dur
}
S
struct
Implements: Tracer

noopTracer

pkg/telemetry/telemetry.go:106-106
type noopTracer struct

Methods

Start
Method

Parameters

name string
attrs ...Attribute
func (*noopTracer) Start(ctx context.Context, name string, attrs ...Attribute) (Span, context.Context)
{
	return &noopSpan{}, ctx
}
S
struct
Implements: Span

noopSpan

pkg/telemetry/telemetry.go:112-112
type noopSpan struct

Methods

SetAttributes
Method

Parameters

attrs ...Attribute
func (*noopSpan) SetAttributes(attrs ...Attribute)
{}
End
Method
func (*noopSpan) End()
{}
S
struct
Implements: Meter

noopMeter

pkg/telemetry/telemetry.go:117-117
type noopMeter struct

Methods

Counter
Method

Parameters

name string
attrs ...Attribute

Returns

func (*noopMeter) Counter(name string, attrs ...Attribute) Counter
{ return &noopCounter{} }
Histogram
Method

Parameters

name string
attrs ...Attribute

Returns

func (*noopMeter) Histogram(name string, attrs ...Attribute) Histogram
{ return &noopHistogram{} }
Gauge
Method

Parameters

name string
attrs ...Attribute

Returns

func (*noopMeter) Gauge(name string, attrs ...Attribute) Gauge
{ return &noopGauge{} }
S
struct
Implements: Counter

noopCounter

pkg/telemetry/telemetry.go:123-123
type noopCounter struct

Methods

Add
Method

Parameters

delta int64
func (*noopCounter) Add(ctx context.Context, delta int64)
{}
S
struct
Implements: Histogram

noopHistogram

pkg/telemetry/telemetry.go:127-127
type noopHistogram struct

Methods

Record
Method

Parameters

value float64
func (*noopHistogram) Record(ctx context.Context, value float64)
{}
S
struct
Implements: Gauge

noopGauge

pkg/telemetry/telemetry.go:131-131
type noopGauge struct

Methods

Set
Method

Parameters

value float64
func (*noopGauge) Set(ctx context.Context, value float64)
{}
S
struct
Implements: Tracer

SimpleTracer

SimpleTracer is a basic tracer that logs span starts and ends to stderr.

pkg/telemetry/telemetry.go:136-138
type SimpleTracer struct

Methods

Start
Method

Parameters

name string
attrs ...Attribute
func (*SimpleTracer) Start(ctx context.Context, name string, attrs ...Attribute) (Span, context.Context)
{
	span := &simpleSpan{name: name, start: time.Now(), attrs: attrs}
	return span, ctx
}

Fields

Name Type Description
mu sync.Mutex
F
function

NewSimpleTracer

NewSimpleTracer creates a SimpleTracer.

Returns

pkg/telemetry/telemetry.go:141-141
func NewSimpleTracer() *SimpleTracer

{ return &SimpleTracer{} }
S
struct
Implements: Span

simpleSpan

pkg/telemetry/telemetry.go:148-152
type simpleSpan struct

Methods

SetAttributes
Method

Parameters

attrs ...Attribute
func (*simpleSpan) SetAttributes(attrs ...Attribute)
{
	s.attrs = append(s.attrs, attrs...)
}
End
Method
func (*simpleSpan) End()
{
	fmt.Printf("[TRACE] %s duration=%v attrs=%v\n", s.name, time.Since(s.start), s.attrs)
}

Fields

Name Type Description
name string
start time.Time
attrs []Attribute
S
struct
Implements: Meter

SimpleMeter

SimpleMeter is a basic meter that stores counters in memory.

pkg/telemetry/telemetry.go:163-168
type SimpleMeter struct

Methods

Counter
Method

Parameters

name string
attrs ...Attribute

Returns

func (*SimpleMeter) Counter(name string, attrs ...Attribute) Counter
{
	return &simpleCounter{meter: m, name: name}
}
Histogram
Method

Parameters

name string
attrs ...Attribute

Returns

func (*SimpleMeter) Histogram(name string, attrs ...Attribute) Histogram
{
	return &simpleHistogram{meter: m, name: name}
}
Gauge
Method

Parameters

name string
attrs ...Attribute

Returns

func (*SimpleMeter) Gauge(name string, attrs ...Attribute) Gauge
{
	return &simpleGauge{meter: m, name: name}
}
GetCounter
Method

GetCounter returns the current counter value by name.

Parameters

name string

Returns

int64
func (*SimpleMeter) GetCounter(name string) int64
{
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.counters[name]
}
GetGauge
Method

GetGauge returns the current gauge value by name.

Parameters

name string

Returns

float64
func (*SimpleMeter) GetGauge(name string) float64
{
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.gauges[name]
}

Fields

Name Type Description
mu sync.Mutex
counters map[string]int64
histogram map[string][]float64
gauges map[string]float64
F
function

NewSimpleMeter

NewSimpleMeter creates a SimpleMeter.

Returns

pkg/telemetry/telemetry.go:171-177
func NewSimpleMeter() *SimpleMeter

{
	return &SimpleMeter{
		counters:  make(map[string]int64),
		histogram: make(map[string][]float64),
		gauges:    make(map[string]float64),
	}
}
S
struct
Implements: Counter

simpleCounter

pkg/telemetry/telemetry.go:205-208
type simpleCounter struct

Methods

Add
Method

Parameters

delta int64
func (*simpleCounter) Add(ctx context.Context, delta int64)
{
	c.meter.mu.Lock()
	c.meter.counters[c.name] += delta
	c.meter.mu.Unlock()
}

Fields

Name Type Description
meter *SimpleMeter
name string
S
struct
Implements: Histogram

simpleHistogram

pkg/telemetry/telemetry.go:216-219
type simpleHistogram struct

Methods

Record
Method

Parameters

value float64
func (*simpleHistogram) Record(ctx context.Context, value float64)
{
	h.meter.mu.Lock()
	h.meter.histogram[h.name] = append(h.meter.histogram[h.name], value)
	h.meter.mu.Unlock()
}

Fields

Name Type Description
meter *SimpleMeter
name string
S
struct
Implements: Gauge

simpleGauge

pkg/telemetry/telemetry.go:227-230
type simpleGauge struct

Methods

Set
Method

Parameters

value float64
func (*simpleGauge) Set(ctx context.Context, value float64)
{
	g.meter.mu.Lock()
	g.meter.gauges[g.name] = value
	g.meter.mu.Unlock()
}

Fields

Name Type Description
meter *SimpleMeter
name string
F
function

TestProvider_NoopDefault

Parameters

pkg/telemetry/telemetry_test.go:13-29
func TestProvider_NoopDefault(t *testing.T)

{
	p := NewProvider()
	if p.Tracer == nil {
		t.Error("Tracer should not be nil (noop)")
	}
	if p.Meter == nil {
		t.Error("Meter should not be nil (noop)")
	}

	span, ctx := p.Tracer.Start(context.Background(), "test")
	span.SetAttributes(Attribute{Key: "k", Value: "v"})
	span.End()

	p.Meter.Counter("c").Add(ctx, 1)
	p.Meter.Histogram("h").Record(ctx, 3.14)
	p.Meter.Gauge("g").Set(ctx, 42)
}
F
function

TestSimpleTracer

Parameters

pkg/telemetry/telemetry_test.go:31-35
func TestSimpleTracer(t *testing.T)

{
	tracer := NewSimpleTracer()
	span, _ := tracer.Start(context.Background(), "operation")
	span.End()
}
F
function

TestSimpleMeter

Parameters

pkg/telemetry/telemetry_test.go:37-54
func TestSimpleMeter(t *testing.T)

{
	meter := NewSimpleMeter()
	ctx := context.Background()

	counter := meter.Counter("requests")
	counter.Add(ctx, 5)
	counter.Add(ctx, 3)

	if v := meter.GetCounter("requests"); v != 8 {
		t.Errorf("counter: got %d, want 8", v)
	}

	gauge := meter.Gauge("cpu")
	gauge.Set(ctx, 75.5)
	if v := meter.GetGauge("cpu"); v != 75.5 {
		t.Errorf("gauge: got %f, want 75.5", v)
	}
}
F
function

TestTimed

Parameters

pkg/telemetry/telemetry_test.go:56-72
func TestTimed(t *testing.T)

{
	meter := NewSimpleMeter()
	h := meter.Histogram("duration")
	ctx := context.Background()

	called := false
	dur := Timed(ctx, h, func() {
		called = true
	})

	if !called {
		t.Error("Timed should call fn")
	}
	if dur <= 0 {
		t.Error("Timed should return positive duration")
	}
}
F
function

TestProvider_WithTracer

Parameters

pkg/telemetry/telemetry_test.go:74-80
func TestProvider_WithTracer(t *testing.T)

{
	custom := NewSimpleTracer()
	p := NewProvider(WithTracer(custom))
	if p.Tracer != custom {
		t.Error("WithTracer should set custom tracer")
	}
}
F
function

TestProvider_Shutdown

Parameters

pkg/telemetry/telemetry_test.go:82-90
func TestProvider_Shutdown(t *testing.T)

{
	called := false
	p := NewProvider()
	p.shutdown = append(p.shutdown, func() { called = true })
	p.Shutdown()
	if !called {
		t.Error("Shutdown should call cleanup functions")
	}
}
F
function

TestOTLPExporter_ExportFlush

Parameters

pkg/telemetry/telemetry_test.go:94-122
func TestOTLPExporter_ExportFlush(t *testing.T)

{
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	e := NewOTLPExporter(WithOTLPBatchSize(100))
	defer e.Close()
	e.endpoint = server.URL + "/v1/traces"

	e.ExportSpan(otlpSpan{
		TraceID: "0af7651916cd43dd8448eb211c80319c",
		SpanID:  "b7ad6b7169203331",
		Name:    "test-span",
	})

	time.Sleep(100 * time.Millisecond)

	e.ExportMetric(otlpMetric{
		Name:  "http_requests",
		Kind:  "COUNTER",
		Value: 42,
	})

	err := e.Flush(context.Background())
	if err != nil {
		t.Errorf("Flush: %v", err)
	}
}
F
function

TestPrometheusExporter_CounterGauge

Parameters

pkg/telemetry/telemetry_test.go:124-140
func TestPrometheusExporter_CounterGauge(t *testing.T)

{
	p := NewPrometheusExporter()
	p.IncCounter("http_requests_total", 5)
	p.IncCounter("http_requests_total", 3)
	p.SetGauge("cpu_usage", 75.5)

	var buf bytes.Buffer
	p.WriteText(&buf)

	output := buf.String()
	if !strings.Contains(output, "http_requests_total 8") {
		t.Errorf("expected counter in output, got: %s", output)
	}
	if !strings.Contains(output, "cpu_usage 75.5") {
		t.Errorf("expected gauge in output, got: %s", output)
	}
}
F
function

TestPrometheusExporter_Histogram

Parameters

pkg/telemetry/telemetry_test.go:142-160
func TestPrometheusExporter_Histogram(t *testing.T)

{
	p := NewPrometheusExporter()
	buckets := []float64{0.1, 0.5, 1.0}
	p.ObserveHistogram("request_duration", 0.05, buckets)
	p.ObserveHistogram("request_duration", 0.25, buckets)
	p.ObserveHistogram("request_duration", 0.75, buckets)
	p.ObserveHistogram("request_duration", 1.5, buckets)

	var buf bytes.Buffer
	p.WriteText(&buf)

	output := buf.String()
	if !strings.Contains(output, "request_duration_bucket") {
		t.Errorf("expected histogram buckets in output, got: %s", output)
	}
	if !strings.Contains(output, "request_duration_sum") {
		t.Errorf("expected histogram sum in output, got: %s", output)
	}
}
F
function

TestTraceparent

Parameters

pkg/telemetry/telemetry_test.go:162-181
func TestTraceparent(t *testing.T)

{
	tc, err := ParseTraceparent("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01")
	if err != nil {
		t.Fatalf("ParseTraceparent: %v", err)
	}
	if tc.TraceID != "0af7651916cd43dd8448eb211c80319c" {
		t.Errorf("TraceID = %q, want full trace ID", tc.TraceID)
	}
	if tc.ParentID != "b7ad6b7169203331" {
		t.Errorf("ParentID = %q", tc.ParentID)
	}
	if tc.TraceFlags != "01" {
		t.Errorf("TraceFlags = %q", tc.TraceFlags)
	}

	encoded := tc.Encode()
	if encoded != "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" {
		t.Errorf("Encode = %q", encoded)
	}
}
F
function

TestTraceparent_Invalid

Parameters

pkg/telemetry/telemetry_test.go:183-193
func TestTraceparent_Invalid(t *testing.T)

{
	_, err := ParseTraceparent("invalid")
	if err == nil {
		t.Error("expected error for invalid traceparent")
	}

	_, err = ParseTraceparent("01-abc-def-01")
	if err == nil {
		t.Error("expected error for unsupported version")
	}
}
F
function

TestTelemetryMiddleware_WrapHTTP

Parameters

pkg/telemetry/telemetry_test.go:195-219
func TestTelemetryMiddleware_WrapHTTP(t *testing.T)

{
	meter := NewSimpleMeter()
	provider := NewProvider(WithMeter(meter))

	mw := NewTelemetryMiddleware(provider)

	handler := mw.WrapHTTP(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("ok"))
	}))

	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01")
	w := httptest.NewRecorder()

	handler.ServeHTTP(w, req)

	if w.Code != 200 {
		t.Errorf("status = %d, want 200", w.Code)
	}

	if v := meter.GetCounter("http_requests_total"); v == 0 {
		t.Error("expected counter to be incremented")
	}
}