Skip to content

Commit

Permalink
Support keysAndArgs API of stdlib slog (#173)
Browse files Browse the repository at this point in the history
  • Loading branch information
ammario authored May 23, 2023
1 parent 2202bcf commit c17dfd2
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 14 deletions.
2 changes: 1 addition & 1 deletion s.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (w stdlogWriter) Write(p []byte) (n int, err error) {
// we do not want.
msg = strings.TrimSuffix(msg, "\n")

w.l.log(w.ctx, w.level, msg, Map{})
w.l.log(w.ctx, w.level, msg, nil)

return len(p), nil
}
66 changes: 59 additions & 7 deletions slog.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,40 +80,67 @@ func Make(sinks ...Sink) Logger {
}

// Debug logs the msg and fields at LevelDebug.
func (l Logger) Debug(ctx context.Context, msg string, fields ...Field) {
// See Info for information on the fields argument.
func (l Logger) Debug(ctx context.Context, msg string, fields ...any) {
l.log(ctx, LevelDebug, msg, fields)
}

// Info logs the msg and fields at LevelInfo.
func (l Logger) Info(ctx context.Context, msg string, fields ...Field) {
// Fields may contain any combination of key value pairs, Field, and Map.
// For example:
//
// log.Info(ctx, "something happened", "user", "alex", slog.F("age", 20))
//
// is equivalent to:
//
// log.Info(ctx, "something happened", slog.F("user", "alex"), slog.F("age", 20))
//
// is equivalent to:
//
// log.Info(ctx, "something happened", slog.M(
// slog.F("user", "alex"),
// slog.F("age", 20),
// ))
//
// is equivalent to:
//
// log.Info(ctx, "something happened", "user", "alex", "age", 20)
//
// In general, prefer using key value pairs over Field and Map, as that is how
// the standard library's slog package works.
func (l Logger) Info(ctx context.Context, msg string, fields ...any) {
l.log(ctx, LevelInfo, msg, fields)
}

// Warn logs the msg and fields at LevelWarn.
func (l Logger) Warn(ctx context.Context, msg string, fields ...Field) {
// See Info() for information on the fields argument.
func (l Logger) Warn(ctx context.Context, msg string, fields ...any) {
l.log(ctx, LevelWarn, msg, fields)
}

// Error logs the msg and fields at LevelError.
// See Info() for information on the fields argument.
//
// It will then Sync().
func (l Logger) Error(ctx context.Context, msg string, fields ...Field) {
func (l Logger) Error(ctx context.Context, msg string, fields ...any) {
l.log(ctx, LevelError, msg, fields)
l.Sync()
}

// Critical logs the msg and fields at LevelCritical.
// See Info() for information on the fields argument.
//
// It will then Sync().
func (l Logger) Critical(ctx context.Context, msg string, fields ...Field) {
func (l Logger) Critical(ctx context.Context, msg string, fields ...any) {
l.log(ctx, LevelCritical, msg, fields)
l.Sync()
}

// Fatal logs the msg and fields at LevelFatal.
// See Info() for information on the fields argument.
//
// It will then Sync() and os.Exit(1).
func (l Logger) Fatal(ctx context.Context, msg string, fields ...Field) {
func (l Logger) Fatal(ctx context.Context, msg string, fields ...any) {
l.log(ctx, LevelFatal, msg, fields)
l.Sync()

Expand Down Expand Up @@ -155,7 +182,32 @@ func (l Logger) AppendSinks(s ...Sink) Logger {
return l
}

func (l Logger) log(ctx context.Context, level Level, msg string, fields Map) {
func (l Logger) log(ctx context.Context, level Level, msg string, rawFields []any) {
fields := make(Map, 0, len(rawFields))
var wipField Field
for i, f := range rawFields {
if wipField.Name != "" {
wipField.Value = f
fields = append(fields, wipField)
wipField = Field{}
continue
}
switch f := f.(type) {
case Field:
fields = append(fields, f)
case Map:
fields = append(fields, f...)
case string:
wipField.Name = f
default:
panic(fmt.Sprintf("unexpected field type %T at index %v (does it have a key?)", f, i))
}
}

if wipField.Name != "" {
panic(fmt.Sprintf("field %q has no value", wipField.Name))
}

ent := l.entry(ctx, level, msg, fields)
l.Log(ctx, ent)
}
Expand Down
35 changes: 33 additions & 2 deletions slog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package slog_test

import (
"context"
"fmt"
"io"
"runtime"
"testing"
Expand Down Expand Up @@ -75,7 +76,7 @@ func TestLogger(t *testing.T) {

File: slogTestFile,
Func: "cdr.dev/slog_test.TestLogger.func2",
Line: 67,
Line: 68,

Fields: slog.M(
slog.F("ctx", 1024),
Expand Down Expand Up @@ -108,7 +109,7 @@ func TestLogger(t *testing.T) {

File: slogTestFile,
Func: "cdr.dev/slog_test.TestLogger.func3",
Line: 98,
Line: 99,

SpanContext: span.SpanContext(),

Expand Down Expand Up @@ -149,6 +150,36 @@ func TestLogger(t *testing.T) {
assert.Equal(t, "level", slog.LevelFatal, s.entries[5].Level)
assert.Equal(t, "exits", 1, exits)
})

t.Run("kv", func(t *testing.T) {
s := &fakeSink{}
l := slog.Make(s)

// All of these formats should be equivalent.
formats := [][]any{
{"animal", "cat", "weight", 15},
{slog.F("animal", "cat"), "weight", 15},
{slog.M(
slog.F("animal", "cat"),
slog.F("weight", 15),
)},
{slog.F("animal", "cat"), slog.F("weight", 15)},
}

for _, format := range formats {
l.Info(bg, "msg", format...)
}

assert.Len(t, "entries", 4, s.entries)

for i := range s.entries {
assert.Equal(
t, fmt.Sprintf("%v", i),
s.entries[0].Fields,
s.entries[i].Fields,
)
}
})
}

func TestLevel_String(t *testing.T) {
Expand Down
8 changes: 4 additions & 4 deletions sloggers/slogtest/t.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,25 +103,25 @@ func l(t testing.TB) slog.Logger {
}

// Debug logs the given msg and fields to t via t.Log at the debug level.
func Debug(t testing.TB, msg string, fields ...slog.Field) {
func Debug(t testing.TB, msg string, fields ...any) {
slog.Helper()
l(t).Debug(ctx, msg, fields...)
}

// Info logs the given msg and fields to t via t.Log at the info level.
func Info(t testing.TB, msg string, fields ...slog.Field) {
func Info(t testing.TB, msg string, fields ...any) {
slog.Helper()
l(t).Info(ctx, msg, fields...)
}

// Error logs the given msg and fields to t via t.Error at the error level.
func Error(t testing.TB, msg string, fields ...slog.Field) {
func Error(t testing.TB, msg string, fields ...any) {
slog.Helper()
l(t).Error(ctx, msg, fields...)
}

// Fatal logs the given msg and fields to t via t.Fatal at the fatal level.
func Fatal(t testing.TB, msg string, fields ...slog.Field) {
func Fatal(t testing.TB, msg string, fields ...any) {
slog.Helper()
l(t).Fatal(ctx, msg, fields...)
}

0 comments on commit c17dfd2

Please sign in to comment.