feat(telemetry): add NewConsole — logz-backed OTel exporters for local dev (v1.1.0)
NewConsole bootstraps the OTel SDK with three logz-backed exporters: - Trace: WithSyncer, one log line per closed span (immediate, no batch) - Metric: PeriodicReader (60s), flushed on shutdown - OTel log: BatchProcessor, for third-party libs using OTel log API ConsoleConfig requires only ServiceName — no OTLP endpoint needed. Adds logz v1.0.1 as direct dependency; module tier bumped 1 → 2.
This commit is contained in:
181
console_test.go
Normal file
181
console_test.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
|
||||
"code.nochebuena.dev/go/logz"
|
||||
)
|
||||
|
||||
// stubLogger captures Info/Warn/Error calls for test assertions.
|
||||
type stubLogger struct {
|
||||
mu sync.Mutex
|
||||
logs []stubLog
|
||||
}
|
||||
|
||||
type stubLog struct {
|
||||
level string
|
||||
msg string
|
||||
args []any
|
||||
}
|
||||
|
||||
func (s *stubLogger) Info(msg string, args ...any) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.logs = append(s.logs, stubLog{"info", msg, args})
|
||||
}
|
||||
|
||||
func (s *stubLogger) Warn(msg string, args ...any) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.logs = append(s.logs, stubLog{"warn", msg, args})
|
||||
}
|
||||
|
||||
func (s *stubLogger) Error(msg string, err error, args ...any) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.logs = append(s.logs, stubLog{"error", msg, args})
|
||||
}
|
||||
|
||||
func (s *stubLogger) Debug(msg string, args ...any) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.logs = append(s.logs, stubLog{"debug", msg, args})
|
||||
}
|
||||
|
||||
func (s *stubLogger) With(args ...any) logz.Logger { return s }
|
||||
func (s *stubLogger) WithContext(ctx context.Context) logz.Logger { return s }
|
||||
|
||||
func (s *stubLogger) find(msg string) (stubLog, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for _, l := range s.logs {
|
||||
if l.msg == msg {
|
||||
return l, true
|
||||
}
|
||||
}
|
||||
return stubLog{}, false
|
||||
}
|
||||
|
||||
func consoleCfg() ConsoleConfig {
|
||||
return ConsoleConfig{
|
||||
ServiceName: "test-svc",
|
||||
ServiceVersion: "0.0.1",
|
||||
Environment: "test",
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConsole_ShutdownCallable(t *testing.T) {
|
||||
logger := &stubLogger{}
|
||||
ctx := context.Background()
|
||||
|
||||
shutdown, err := NewConsole(ctx, logger, consoleCfg())
|
||||
if err != nil {
|
||||
t.Fatalf("NewConsole: %v", err)
|
||||
}
|
||||
if shutdown == nil {
|
||||
t.Fatal("shutdown is nil")
|
||||
}
|
||||
shutCtx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
|
||||
defer cancel()
|
||||
if err := shutdown(shutCtx); err != nil {
|
||||
t.Errorf("shutdown: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConsole_SetsGlobalTracerProvider(t *testing.T) {
|
||||
logger := &stubLogger{}
|
||||
ctx := context.Background()
|
||||
|
||||
shutdown, err := NewConsole(ctx, logger, consoleCfg())
|
||||
if err != nil {
|
||||
t.Fatalf("NewConsole: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
shutCtx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
|
||||
defer cancel()
|
||||
_ = shutdown(shutCtx)
|
||||
})
|
||||
|
||||
tp := otel.GetTracerProvider()
|
||||
if _, ok := tp.(*sdktrace.TracerProvider); !ok {
|
||||
t.Errorf("expected *sdktrace.TracerProvider, got %T", tp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConsole_SetsGlobalMeterProvider(t *testing.T) {
|
||||
logger := &stubLogger{}
|
||||
ctx := context.Background()
|
||||
|
||||
shutdown, err := NewConsole(ctx, logger, consoleCfg())
|
||||
if err != nil {
|
||||
t.Fatalf("NewConsole: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
shutCtx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
|
||||
defer cancel()
|
||||
_ = shutdown(shutCtx)
|
||||
})
|
||||
|
||||
mp := otel.GetMeterProvider()
|
||||
if _, ok := mp.(*sdkmetric.MeterProvider); !ok {
|
||||
t.Errorf("expected *sdkmetric.MeterProvider, got %T", mp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConsole_ExportsSpan(t *testing.T) {
|
||||
logger := &stubLogger{}
|
||||
ctx := context.Background()
|
||||
|
||||
shutdown, err := NewConsole(ctx, logger, consoleCfg())
|
||||
if err != nil {
|
||||
t.Fatalf("NewConsole: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
shutCtx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
|
||||
defer cancel()
|
||||
_ = shutdown(shutCtx)
|
||||
}()
|
||||
|
||||
tracer := otel.Tracer("test")
|
||||
_, span := tracer.Start(ctx, "test-span")
|
||||
span.End() // WithSyncer exports synchronously on End
|
||||
|
||||
if _, ok := logger.find("otel: span"); !ok {
|
||||
t.Error("expected logger to receive 'otel: span' after span.End()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConsole_ExportsMetric(t *testing.T) {
|
||||
logger := &stubLogger{}
|
||||
ctx := context.Background()
|
||||
|
||||
shutdown, err := NewConsole(ctx, logger, consoleCfg())
|
||||
if err != nil {
|
||||
t.Fatalf("NewConsole: %v", err)
|
||||
}
|
||||
|
||||
meter := otel.Meter("test")
|
||||
counter, err := meter.Int64Counter("test.counter")
|
||||
if err != nil {
|
||||
t.Fatalf("Int64Counter: %v", err)
|
||||
}
|
||||
counter.Add(ctx, 1)
|
||||
|
||||
// Force export by shutting down — PeriodicReader flushes on shutdown.
|
||||
shutCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
if err := shutdown(shutCtx); err != nil {
|
||||
t.Errorf("shutdown: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := logger.find("otel: metric"); !ok {
|
||||
t.Error("expected logger to receive 'otel: metric' after shutdown flush")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user