182 lines
4.2 KiB
Go
182 lines
4.2 KiB
Go
|
|
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")
|
||
|
|
}
|
||
|
|
}
|