logger_test API

logger_test

package

API reference for the logger_test package.

F
function

TestCLEFSinkInformationOmitsLevel

Parameters

core/logger/clef_sink_test.go:13-41
func TestCLEFSinkInformationOmitsLevel(t *testing.T)

{
	buf := &bytes.Buffer{}
	sink := logger.NewCLEFSink(buf)

	err := sink.Log(logger.Entry{
		Level:  "info",
		Time:   time.Now().UTC(),
		Msg:    "service started",
		Fields: map[string]interface{}{"port": 8080},
	})
	if err != nil {
		t.Fatalf("Log() error = %v", err)
	}

	var m map[string]any
	if err := json.Unmarshal([]byte(strings.TrimSpace(buf.String())), &m); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}

	if _, ok := m["@l"]; ok {
		t.Fatalf("@l should be omitted for Information level, got %v", m["@l"])
	}
	if m["@m"] != "service started" {
		t.Fatalf("@m = %v, want %q", m["@m"], "service started")
	}
	if m["port"] == nil {
		t.Fatalf("structured field port missing from root level")
	}
}
F
function

TestCLEFSinkLevelMapping

Parameters

core/logger/clef_sink_test.go:43-67
func TestCLEFSinkLevelMapping(t *testing.T)

{
	cases := []struct {
		in   string
		want string
	}{
		{"debug", "Debug"},
		{"warn", "Warning"},
		{"error", "Error"},
	}

	for _, tc := range cases {
		buf := &bytes.Buffer{}
		sink := logger.NewCLEFSink(buf)

		_ = sink.Log(logger.Entry{Level: tc.in, Time: time.Now().UTC(), Msg: "msg"})

		var m map[string]any
		if err := json.Unmarshal([]byte(strings.TrimSpace(buf.String())), &m); err != nil {
			t.Fatalf("level=%q: json.Unmarshal() error = %v", tc.in, err)
		}
		if m["@l"] != tc.want {
			t.Fatalf("level=%q: @l = %v, want %q", tc.in, m["@l"], tc.want)
		}
	}
}
F
function

TestCLEFSinkFieldsFlattened

Parameters

core/logger/clef_sink_test.go:69-95
func TestCLEFSinkFieldsFlattened(t *testing.T)

{
	buf := &bytes.Buffer{}
	sink := logger.NewCLEFSink(buf)

	_ = sink.Log(logger.Entry{
		Level: "info",
		Time:  time.Now().UTC(),
		Msg:   "request handled",
		Fields: map[string]interface{}{
			"request_id": "abc-123",
			"status":     200,
		},
	})

	var m map[string]any
	if err := json.Unmarshal([]byte(strings.TrimSpace(buf.String())), &m); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}

	// Fields must be at root, not nested inside a "fields" key.
	if _, nested := m["fields"]; nested {
		t.Fatalf("fields should be flat at root, not nested under 'fields'")
	}
	if m["request_id"] != "abc-123" {
		t.Fatalf("request_id = %v, want %q", m["request_id"], "abc-123")
	}
}
F
function

TestCLEFSinkTimestampFormat

Parameters

core/logger/clef_sink_test.go:97-116
func TestCLEFSinkTimestampFormat(t *testing.T)

{
	buf := &bytes.Buffer{}
	sink := logger.NewCLEFSink(buf)
	ts := time.Date(2026, 3, 13, 9, 0, 0, 0, time.UTC)

	_ = sink.Log(logger.Entry{Level: "info", Time: ts, Msg: "ts check"})

	var m map[string]any
	if err := json.Unmarshal([]byte(strings.TrimSpace(buf.String())), &m); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}

	got, ok := m["@t"].(string)
	if !ok {
		t.Fatalf("@t is not a string: %v", m["@t"])
	}
	if !strings.HasPrefix(got, "2026-03-13T09:00:00") {
		t.Fatalf("@t = %q, want RFC3339Nano starting with 2026-03-13T09:00:00", got)
	}
}
F
function

TestCLEFSinkViaLogger

Parameters

core/logger/clef_sink_test.go:118-137
func TestCLEFSinkViaLogger(t *testing.T)

{
	buf := &bytes.Buffer{}
	// Default logger has ConsoleSink; replace with CLEFSink only.
	lg := logger.New(logger.WithSink(logger.NewCLEFSink(buf)))
	lg.Warn("disk space low", logger.Field{Key: "free_gb", Value: 2})

	var m map[string]any
	if err := json.Unmarshal([]byte(strings.TrimSpace(buf.String())), &m); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if m["@l"] != "Warning" {
		t.Fatalf("@l = %v, want Warning", m["@l"])
	}
	if m["@m"] != "disk space low" {
		t.Fatalf("@m = %v, want 'disk space low'", m["@m"])
	}
	if m["free_gb"] == nil {
		t.Fatalf("free_gb missing from CLEF output")
	}
}
F
function

TestConsoleSinkJSON

Parameters

core/logger/logger_test.go:15-39
func TestConsoleSinkJSON(t *testing.T)

{
	buf := &bytes.Buffer{}
	sink := logger.NewConsoleSink(buf)
	lg := logger.New(logger.WithSink(sink), logger.WithLevel(logger.DebugLevel))
	lg.Info("hello", logger.Field{Key: "k", Value: "v"})

	line, err := buf.ReadString('\n')
	if err != nil {
		t.Fatalf("ReadString() error = %v", err)
	}

	var entry logger.Entry
	if err := json.Unmarshal([]byte(strings.TrimSpace(line)), &entry); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if entry.Msg != "hello" {
		t.Fatalf("entry.Msg = %q, want %q", entry.Msg, "hello")
	}
	if entry.Level != "info" {
		t.Fatalf("entry.Level = %q, want %q", entry.Level, "info")
	}
	if got, ok := entry.Fields["k"]; !ok || got != "v" {
		t.Fatalf("entry.Fields = %v, want key k=v", entry.Fields)
	}
}
S
struct

collectingSink

core/logger/logger_test.go:41-44
type collectingSink struct

Methods

Log
Method

Parameters

entry logger.Entry

Returns

error
func (*collectingSink) Log(entry logger.Entry) error
{
	s.mu.Lock()
	s.entries = append(s.entries, entry)
	s.mu.Unlock()
	return nil
}

Fields

Name Type Description
mu sync.Mutex
entries []logger.Entry
F
function

TestAsyncLoggerCloseFlushesAllEntries

Parameters

core/logger/logger_test.go:53-75
func TestAsyncLoggerCloseFlushesAllEntries(t *testing.T)

{
	sink := &collectingSink{}
	lg := logger.New(
		logger.WithoutDefaultSink(),
		logger.WithSink(sink),
		logger.WithAsync(100),
	)
	for i := 0; i < 100; i++ {
		lg.Info("entry")
	}
	if err := lg.Shutdown(context.Background()); err != nil {
		t.Fatal(err)
	}
	if err := lg.Close(); err != nil {
		t.Fatal(err)
	}
	sink.mu.Lock()
	count := len(sink.entries)
	sink.mu.Unlock()
	if count != 100 {
		t.Fatalf("flushed entries = %d, want 100", count)
	}
}
S
struct

reentrantSink

core/logger/logger_test.go:77-81
type reentrantSink struct

Methods

Log
Method

Parameters

Returns

error
func (*reentrantSink) Log(logger.Entry) error
{
	s.once.Do(func() {
		s.log.Info("nested")
		close(s.nested)
	})
	return nil
}

Fields

Name Type Description
once sync.Once
log logger.Logger
nested chan struct{}
F
function

TestAsyncLoggerAllowsReentrantLogging

Parameters

core/logger/logger_test.go:91-120
func TestAsyncLoggerAllowsReentrantLogging(t *testing.T)

{
	sink := &reentrantSink{nested: make(chan struct{})}
	collector := &collectingSink{}
	log := logger.New(
		logger.WithoutDefaultSink(),
		logger.WithSink(sink),
		logger.WithSink(collector),
		logger.WithAsync(1),
	)
	sink.log = log
	log.Info("outer")

	select {
	case <-sink.nested:
	case <-time.After(time.Second):
		t.Fatal("reentrant log call deadlocked")
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	if err := log.Shutdown(ctx); err != nil {
		t.Fatalf("Shutdown: %v", err)
	}

	collector.mu.Lock()
	count := len(collector.entries)
	collector.mu.Unlock()
	if count != 2 {
		t.Fatalf("collected entries = %d, want 2", count)
	}
}
S
struct

closingSink

core/logger/logger_test.go:122-125
type closingSink struct

Methods

Log
Method

Parameters

Returns

error
func (*closingSink) Log(logger.Entry) error
{
	s.log.CloseAsync()
	close(s.called)
	return nil
}

Fields

Name Type Description
log logger.Logger
called chan struct{}
F
function

TestAsyncLoggerAllowsCloseAsyncFromSink

Parameters

core/logger/logger_test.go:133-153
func TestAsyncLoggerAllowsCloseAsyncFromSink(t *testing.T)

{
	sink := &closingSink{called: make(chan struct{})}
	log := logger.New(
		logger.WithoutDefaultSink(),
		logger.WithSink(sink),
		logger.WithAsync(1),
	)
	sink.log = log
	log.Info("close")

	select {
	case <-sink.called:
	case <-time.After(time.Second):
		t.Fatal("sink Close deadlocked")
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	if err := log.Shutdown(ctx); err != nil {
		t.Fatalf("Shutdown: %v", err)
	}
}
F
function

TestDerivedLoggerDoesNotOwnParentLifecycle

Parameters

core/logger/logger_test.go:155-181
func TestDerivedLoggerDoesNotOwnParentLifecycle(t *testing.T)

{
	collector := &collectingSink{}
	parent := logger.New(
		logger.WithoutDefaultSink(),
		logger.WithSink(collector),
		logger.WithAsync(2),
	)
	child := parent.With(logger.Field{Key: "child", Value: true})
	if err := child.Close(); err != nil {
		t.Fatalf("child Close: %v", err)
	}
	child.CloseAsync()
	if err := child.Shutdown(context.Background()); err != nil {
		t.Fatalf("child Shutdown: %v", err)
	}

	parent.Info("still open")
	if err := parent.Close(); err != nil {
		t.Fatalf("parent Close: %v", err)
	}
	collector.mu.Lock()
	count := len(collector.entries)
	collector.mu.Unlock()
	if count != 1 {
		t.Fatalf("collected entries = %d, want 1", count)
	}
}
F
function

TestWithAsyncRejectsInvalidConfiguration

Parameters

core/logger/logger_test.go:183-197
func TestWithAsyncRejectsInvalidConfiguration(t *testing.T)

{
	for name, options := range map[string][]logger.Option{
		"zero buffer": {logger.WithAsync(0)},
		"duplicate":   {logger.WithAsync(1), logger.WithAsync(2)},
	} {
		t.Run(name, func(t *testing.T) {
			defer func() {
				if recover() == nil {
					t.Fatal("New() accepted invalid async configuration")
				}
			}()
			logger.New(options...)
		})
	}
}
S
struct

blockingSink

core/logger/logger_test.go:199-203
type blockingSink struct

Methods

Log
Method

Parameters

Returns

error
func (*blockingSink) Log(logger.Entry) error
{
	s.once.Do(func() {
		close(s.started)
		<-s.release
	})
	return nil
}

Fields

Name Type Description
started chan struct{}
release chan struct{}
once sync.Once
F
function

TestAsyncLoggerRemainsBoundedWhileSinkIsBlocked

Parameters

core/logger/logger_test.go:213-247
func TestAsyncLoggerRemainsBoundedWhileSinkIsBlocked(t *testing.T)

{
	sink := &blockingSink{
		started: make(chan struct{}),
		release: make(chan struct{}),
	}
	log := logger.New(
		logger.WithoutDefaultSink(),
		logger.WithSink(sink),
		logger.WithAsync(1),
	)
	log.Info("first")
	select {
	case <-sink.started:
	case <-time.After(time.Second):
		t.Fatal("sink did not start")
	}

	done := make(chan struct{})
	go func() {
		for index := 0; index < 1000; index++ {
			log.Info("queued")
		}
		close(done)
	}()
	select {
	case <-done:
	case <-time.After(time.Second):
		t.Fatal("logging blocked while the async queue was full")
	}

	close(sink.release)
	if err := log.Close(); err != nil {
		t.Fatalf("Close: %v", err)
	}
}
F
function

TestLevelFiltering

Parameters

core/logger/logger_test.go:249-262
func TestLevelFiltering(t *testing.T)

{
	buf := &bytes.Buffer{}
	sink := logger.NewConsoleSink(buf)
	lg := logger.New(logger.WithSink(sink), logger.WithLevel(logger.WarnLevel))
	lg.Info("should be filtered")
	if buf.Len() != 0 {
		t.Fatalf("buffer length = %d, want 0", buf.Len())
	}

	lg.Error("should appear")
	if buf.Len() == 0 {
		t.Fatalf("buffer length = 0, want > 0")
	}
}
F
function

TestWithBindsContextFields

Parameters

core/logger/logger_test.go:264-287
func TestWithBindsContextFields(t *testing.T)

{
	buf := &bytes.Buffer{}
	sink := logger.NewConsoleSink(buf)
	lg := logger.New(logger.WithSink(sink), logger.WithFields(logger.Field{Key: "service", Value: "api"}))

	requestLogger := lg.With(logger.Field{Key: "request_id", Value: "abc"})
	requestLogger.Info("serving")

	line, err := buf.ReadString('\n')
	if err != nil {
		t.Fatalf("ReadString() error = %v", err)
	}

	var entry logger.Entry
	if err := json.Unmarshal([]byte(strings.TrimSpace(line)), &entry); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if entry.Fields["service"] != "api" {
		t.Fatalf("service field = %v, want api", entry.Fields["service"])
	}
	if entry.Fields["request_id"] != "abc" {
		t.Fatalf("request_id field = %v, want abc", entry.Fields["request_id"])
	}
}
F
function

TestDerivedLoggerSharesRuntimeConfiguration

Parameters

core/logger/logger_test.go:289-314
func TestDerivedLoggerSharesRuntimeConfiguration(t *testing.T)

{
	parentSink := &collectingSink{}
	lateSink := &collectingSink{}
	parent := logger.New(
		logger.WithoutDefaultSink(),
		logger.WithSink(parentSink),
		logger.WithLevel(logger.ErrorLevel),
	)
	child := parent.With(logger.Field{Key: "child", Value: true})
	parent.RegisterSink(lateSink)
	parent.SetLevel(logger.InfoLevel)

	child.Info("shared")

	for name, sink := range map[string]*collectingSink{
		"parent": parentSink,
		"late":   lateSink,
	} {
		sink.mu.Lock()
		count := len(sink.entries)
		sink.mu.Unlock()
		if count != 1 {
			t.Fatalf("%s sink entries = %d, want 1", name, count)
		}
	}
}
S
struct

panicSink

core/logger/logger_test.go:316-316
type panicSink struct

Methods

Log
Method

Parameters

Returns

error
func (*panicSink) Log(logger.Entry) error
{
	panic("sink failed")
}
F
function

TestAsyncLoggerSurvivesSinkPanic

Parameters

core/logger/logger_test.go:322-341
func TestAsyncLoggerSurvivesSinkPanic(t *testing.T)

{
	sink := &collectingSink{}
	log := logger.New(
		logger.WithoutDefaultSink(),
		logger.WithSink(&panicSink{}),
		logger.WithSink(sink),
		logger.WithAsync(2),
	)
	log.Info("first")
	log.Info("second")
	if err := log.Shutdown(context.Background()); err != nil {
		t.Fatal(err)
	}
	sink.mu.Lock()
	count := len(sink.entries)
	sink.mu.Unlock()
	if count != 2 {
		t.Fatalf("entries after sink panic = %d, want 2", count)
	}
}