1
0
Fork 0
mirror of https://github.com/chainguard-dev/clog synced 2026-07-06 22:12:46 +00:00
clog/logger_test.go
Billy Lynch fa6bbc4954
Fix duplicate log keys from context. (#34)
Because we were using attr.With, we were setting the logger attributes,
and then appending the log context attributes again later for any
contextf functions. This changes the behavior to use the context for any
log.Info functions.
2025-03-03 18:20:42 -05:00

245 lines
5 KiB
Go

package clog
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log/slog"
"reflect"
"testing"
)
var (
testopts = &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// Ignore time to make testing easier.
if a.Key == "time" {
return slog.Attr{}
}
return a
},
}
)
func TestLogger(t *testing.T) {
ctx := context.Background()
b := new(bytes.Buffer)
base := slog.New(NewHandler(slog.NewJSONHandler(b, testopts)))
log := NewLogger(base).With("a", "b")
log.InfoContext(ctx, "")
t.Log(b.String())
want := map[string]any{
"level": "INFO",
"msg": "",
"a": "b",
}
var got map[string]any
if err := json.Unmarshal(b.Bytes(), &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("want %v, got %v", want, got)
}
}
func TestLoggerNilBase(t *testing.T) {
log := NewLogger(nil)
log.Info("")
}
func TestLoggerFromContext(t *testing.T) {
b := new(bytes.Buffer)
base := slog.New(NewHandler(slog.NewJSONHandler(b, testopts)))
log := NewLogger(base).With("a", "b")
ctx := WithLogger(context.Background(), log)
FromContext(ctx).Info("")
want := map[string]any{
"level": "INFO",
"msg": "",
"a": "b",
}
t.Run("FromContext.Info", func(t *testing.T) {
var got map[string]any
if err := json.Unmarshal(b.Bytes(), &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("want %v, got %v", want, got)
}
})
b.Reset()
t.Run("clog.Info", func(t *testing.T) {
InfoContext(ctx, "")
var got map[string]any
if err := json.Unmarshal(b.Bytes(), &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("want %v, got %v", want, got)
}
})
}
func TestLoggerPC(t *testing.T) {
b := new(bytes.Buffer)
log := NewLogger(slog.New(NewHandler(slog.NewJSONHandler(b, &slog.HandlerOptions{
AddSource: true,
ReplaceAttr: testopts.ReplaceAttr,
}))))
log.Info("")
t.Log(b.String())
var got struct {
Source struct {
File string `json:"file"`
Function string `json:"function"`
} `json:"source"`
}
if err := json.Unmarshal(b.Bytes(), &got); err != nil {
t.Fatal(err)
}
// Knowing that the PC is from this test is good enough.
want := fmt.Sprintf("github.com/chainguard-dev/clog.%s", t.Name())
if got.Source.Function != want {
t.Errorf("want %v, got %v", want, got)
}
}
func TestWith(t *testing.T) {
ctx := context.WithValue(context.Background(), "test", "test")
log := NewLoggerWithContext(ctx, nil)
withed := log.With("a", "b")
if want := withed.ctx; want != ctx {
t.Errorf("want %v, got %v", want, ctx)
}
withed = log.WithGroup("a")
if want := withed.ctx; want != ctx {
t.Errorf("want %v, got %v", want, ctx)
}
}
func TestDefaultHandler(t *testing.T) {
old := slog.Default()
defer func() {
slog.SetDefault(old)
}()
b := new(bytes.Buffer)
slog.SetDefault(slog.New(slog.NewJSONHandler(b, testopts)))
t.Run("Info", func(t *testing.T) {
FromContext(WithValues(context.Background(), "a", "b")).Info("")
want := map[string]any{
"level": "INFO",
"msg": "",
"a": "b",
}
var got map[string]any
if err := json.Unmarshal(b.Bytes(), &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("want %v, got %v", want, got)
}
})
b.Reset()
t.Run("InfoContext", func(t *testing.T) {
// Set logger with original value
ctx := WithValues(context.Background(), "a", "b")
logger := FromContext(ctx)
// Override value in request context - we expect this to overwrite the original value set in the logger
logger.InfoContext(WithValues(ctx, "a", "c"), "")
want := map[string]any{
"level": "INFO",
"msg": "",
"a": "c",
}
var got map[string]any
if err := json.Unmarshal(b.Bytes(), &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("want %v, got %v", want, got)
}
})
b.Reset()
t.Run("Debug - no log", func(t *testing.T) {
logger := FromContext(context.Background())
logger.Debug("asdf")
if b.Len() != 0 {
t.Errorf("want empty, got %q", b.String())
}
})
}
func TestContext(t *testing.T) {
old := slog.Default()
t.Cleanup(func() {
slog.SetDefault(old)
})
b := new(bytes.Buffer)
slog.SetDefault(slog.New(slog.NewJSONHandler(b, testopts)))
msg := "hello world"
want := map[string]any{
"level": "INFO",
"msg": msg,
"a": "b",
}
ctx := WithValues(context.Background(), "a", "b")
// These should all give the same output and be functionally equivalent.
for _, tc := range []struct {
name string
fn func()
}{
{
name: "clog.InfoContext",
fn: func() {
InfoContext(ctx, msg)
},
},
{
name: "clog.FromContext.Info",
fn: func() {
FromContext(ctx).Info(msg)
},
},
{
name: "clog.FromContext.InfoContext",
fn: func() {
FromContext(ctx).InfoContext(ctx, msg)
},
},
} {
t.Run(tc.name, func(t *testing.T) {
b.Reset()
tc.fn()
var got map[string]any
if err := json.Unmarshal(b.Bytes(), &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("want %v, got %v", want, got)
}
})
}
}