bench_test.go

package lambdalog

import (
	"context"
	"io"
	"testing"
	"time"
)

// BenchmarkLogger_InfoNoAttrs measures the cost of a single Info call with
// no attributes. The goal of this benchmark is to keep the allocation count
// at or below one per call -- one for the encoded line.
func BenchmarkLogger_InfoNoAttrs(b *testing.B) {
	l := New(io.Discard)
	l.now = func() time.Time { return time.Unix(0, 0) }

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		l.Info("ping")
	}
}

// BenchmarkLogger_InfoWithAttrs logs a record that carries four attributes.
// This covers the common Lambda shape: rid + user id + op name + latency.
func BenchmarkLogger_InfoWithAttrs(b *testing.B) {
	l := New(io.Discard).With("svc", "checkout")
	l.now = func() time.Time { return time.Unix(0, 0) }

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		l.Info("order placed",
			"rid", "req-1",
			"user", 42,
			"op", "create",
			"latency_ms", 17,
		)
	}
}

// BenchmarkLogger_SampledHotPath exercises the fast-reject branch of the
// sampler: nearly every call should be discarded. The allocation count
// should be zero because we never reach the encoder.
func BenchmarkLogger_SampledHotPath(b *testing.B) {
	l := New(io.Discard).Sampled("hot", 1000)

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		l.Info("hot path")
	}
}

// BenchmarkLogger_100K emits 100k records per iteration. The test harness
// uses it to produce a stable baseline for regression detection.
func BenchmarkLogger_100K(b *testing.B) {
	l := New(io.Discard)
	l.now = func() time.Time { return time.Unix(0, 0) }

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100_000; j++ {
			l.Info("hello", "seq", j)
		}
	}
}

// BenchmarkEncodeRecord isolates the encoder from the writer/mutex path so
// the numbers reflect encoding cost alone.
func BenchmarkEncodeRecord(b *testing.B) {
	attrs := []attr{
		{Key: "svc", Value: "checkout"},
		{Key: "rid", Value: "req-1"},
	}
	extra := []any{"user", 42, "op", "create"}

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = encodeRecord(LevelInfo, "order placed", attrs, extra)
	}
}

// BenchmarkFromContext covers the request-ID extraction path. The benchmark
// uses a pre-built context that already has an override set.
func BenchmarkFromContext(b *testing.B) {
	l := New(io.Discard)
	ctx := WithRequestID(context.Background(), "req-xyz")

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = l.FromContext(ctx)
	}
}

// BenchmarkParseLevel measures the cost of parsing a level string -- low
// enough that it can live on the hot path of a per-request logger builder.
func BenchmarkParseLevel(b *testing.B) {
	inputs := []string{"info", "DEBUG", "warn", "3", "i"}

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = ParseLevel(inputs[i%len(inputs)])
	}
}