Skip to content

Commit

Permalink
add named handler
Browse files Browse the repository at this point in the history
  • Loading branch information
nasdf committed Mar 12, 2024
1 parent def6648 commit c642e7d
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 218 deletions.
77 changes: 77 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package corelog

import (
"context"
"io"
"log/slog"
"os"
)

type namedHandler struct {
name string
attrs []slog.Attr
group string
}

func (h namedHandler) Enabled(ctx context.Context, level slog.Level) bool {
leveler := namedLeveler(h.name)
return level >= leveler.Level()
}

func (h namedHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &namedHandler{
name: h.name,
group: h.group,
attrs: attrs,
}
}

func (h namedHandler) WithGroup(name string) slog.Handler {
return &namedHandler{
name: h.name,
attrs: h.attrs,
group: name,
}
}

func (h namedHandler) Handle(ctx context.Context, record slog.Record) error {
config := GetConfig(h.name)
leveler := namedLeveler(h.name)
opts := &slog.HandlerOptions{
AddSource: config.EnableSource,
Level: leveler,
ReplaceAttr: leveler.ReplaceAttr,
}

var output io.Writer
switch config.Output {
case OutputStderr:
output = os.Stderr

case OutputStdout:
output = os.Stdout

default:
output = os.Stderr
}

var handler slog.Handler
switch config.Format {
case FormatText:
handler = slog.NewTextHandler(output, opts)

case FormatJSON:
handler = slog.NewJSONHandler(output, opts)

default:
handler = slog.NewTextHandler(output, opts)
}

if len(h.attrs) > 0 {
handler = handler.WithAttrs(h.attrs)
}
if len(h.group) > 0 {
handler = handler.WithGroup(h.group)
}
return handler.Handle(ctx, record)
}
99 changes: 99 additions & 0 deletions handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package corelog

import (
"context"
"log/slog"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type TestHandler struct {
attrs []slog.Attr
group string
records []slog.Record
level slog.Level
}

var _ (slog.Handler) = (*TestHandler)(nil)

func (h *TestHandler) Enabled(ctx context.Context, level slog.Level) bool {
return level >= h.level
}

func (h *TestHandler) Handle(ctx context.Context, record slog.Record) error {
h.records = append(h.records, record)
return nil
}

func (h *TestHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &TestHandler{attrs: attrs, group: h.group, records: h.records}
}

func (h *TestHandler) WithGroup(name string) slog.Handler {
return &TestHandler{attrs: h.attrs, group: name, records: h.records}
}

func TestHandlerWithLevelDebug(t *testing.T) {
SetConfig(Config{Level: LevelDebug})
handler := namedHandler{name: "test"}

assert.True(t, handler.Enabled(context.Background(), levelDebug))
assert.True(t, handler.Enabled(context.Background(), levelInfo))
assert.True(t, handler.Enabled(context.Background(), levelError))
assert.True(t, handler.Enabled(context.Background(), levelFatal))
}

func TestHandlerWithLevelInfo(t *testing.T) {
SetConfig(Config{Level: LevelInfo})
handler := namedHandler{name: "test"}

assert.False(t, handler.Enabled(context.Background(), levelDebug))
assert.True(t, handler.Enabled(context.Background(), levelInfo))
assert.True(t, handler.Enabled(context.Background(), levelError))
assert.True(t, handler.Enabled(context.Background(), levelFatal))
}

func TestHandlerWithLevelError(t *testing.T) {
SetConfig(Config{Level: LevelError})
handler := namedHandler{name: "test"}

assert.False(t, handler.Enabled(context.Background(), levelDebug))
assert.False(t, handler.Enabled(context.Background(), levelInfo))
assert.True(t, handler.Enabled(context.Background(), levelError))
assert.True(t, handler.Enabled(context.Background(), levelFatal))
}

func TestHandlerWithLevelFatal(t *testing.T) {
SetConfig(Config{Level: LevelFatal})
handler := namedHandler{name: "test"}

assert.False(t, handler.Enabled(context.Background(), levelDebug))
assert.False(t, handler.Enabled(context.Background(), levelInfo))
assert.False(t, handler.Enabled(context.Background(), levelError))
assert.True(t, handler.Enabled(context.Background(), levelFatal))
}

func TestHandlerWithAttrs(t *testing.T) {
handler := namedHandler{name: "test"}
attrs := []slog.Attr{slog.Any("extra", "value")}
other := handler.WithAttrs(attrs)

otherHandler, ok := other.(*namedHandler)
require.True(t, ok)
assert.Equal(t, "test", otherHandler.name)
assert.Equal(t, "", otherHandler.group)
assert.Equal(t, attrs, otherHandler.attrs)
}

func TestHandlerWithGroup(t *testing.T) {
handler := namedHandler{name: "test"}
other := handler.WithGroup("group")

otherHandler, ok := other.(*namedHandler)
require.True(t, ok)
assert.Equal(t, "test", otherHandler.name)
assert.Equal(t, "group", otherHandler.group)
assert.Equal(t, []slog.Attr(nil), otherHandler.attrs)
}
79 changes: 14 additions & 65 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,66 +11,33 @@ import (

// Logger is a logger that wraps the slog package.
type Logger struct {
name string
stderrHandler slog.Handler
stdoutHandler slog.Handler
// skipExit is used for tests that don't want to call os.Exit when logging fatal.
skipExit bool
name string
handler slog.Handler
}

// NewLogger returns a new named logger.
func NewLogger(name string) *Logger {
config := GetConfig(name)
leveler := namedLeveler(name)
opts := &slog.HandlerOptions{
AddSource: config.EnableSource,
Level: leveler,
ReplaceAttr: leveler.ReplaceAttr,
}

var stdoutHandler slog.Handler
var stderrHandler slog.Handler

switch config.Format {
case FormatText:
stdoutHandler = slog.NewTextHandler(os.Stdout, opts)
stderrHandler = slog.NewTextHandler(os.Stderr, opts)

case FormatJSON:
stdoutHandler = slog.NewJSONHandler(os.Stdout, opts)
stderrHandler = slog.NewJSONHandler(os.Stderr, opts)

default:
stdoutHandler = slog.NewTextHandler(os.Stdout, opts)
stderrHandler = slog.NewTextHandler(os.Stderr, opts)
}

return &Logger{
name: name,
stderrHandler: stderrHandler,
stdoutHandler: stdoutHandler,
name: name,
handler: &namedHandler{name: name},
}
}

// WithAttrs returns a new Logger whose attributes consist of
// both the receiver's attributes and the arguments.
func (l *Logger) WithAttrs(attrs ...slog.Attr) *Logger {
return &Logger{
name: l.name,
stdoutHandler: l.stdoutHandler.WithAttrs(attrs),
stderrHandler: l.stderrHandler.WithAttrs(attrs),
skipExit: l.skipExit,
name: l.name,
handler: l.handler.WithAttrs(attrs),
}
}

// WithGroup returns a new Logger with the given group appended to
// the receiver's existing groups.
func (l *Logger) WithGroup(name string) *Logger {
return &Logger{
name: l.name,
stdoutHandler: l.stdoutHandler.WithGroup(name),
stderrHandler: l.stderrHandler.WithGroup(name),
skipExit: l.skipExit,
name: l.name,
handler: l.handler.WithGroup(name),
}
}

Expand All @@ -97,17 +64,13 @@ func (l *Logger) ErrorE(msg string, err error, args ...any) {
// Fatal logs a message at fatal log level.
func (l *Logger) Fatal(msg string, args ...any) {
l.log(context.Background(), levelFatal, nil, msg, args)
if !l.skipExit {
os.Exit(1)
}
os.Exit(1)
}

// FatalE logs a message at fatal log level with an error stacktrace.
func (l *Logger) FatalE(msg string, err error, args ...any) {
l.log(context.Background(), levelFatal, err, msg, args)
if !l.skipExit {
os.Exit(1)
}
os.Exit(1)
}

// DebugContext logs a message at debug log level.
Expand All @@ -133,24 +96,20 @@ func (l *Logger) ErrorContextE(ctx context.Context, msg string, err error, args
// FatalContext logs a message at fatal log level and calls os.Exit(1).
func (l *Logger) FatalContext(ctx context.Context, msg string, args ...any) {
l.log(ctx, levelFatal, nil, msg, args)
if !l.skipExit {
os.Exit(1)
}
os.Exit(1)
}

// FatalContextE logs a message at fatal log level with an error stacktrace and calls os.Exit(1).
func (l *Logger) FatalContextE(ctx context.Context, msg string, err error, args ...any) {
l.log(ctx, levelFatal, err, msg, args)
if !l.skipExit {
os.Exit(1)
}
os.Exit(1)
}

// log wraps calls to the underlying logger so that the caller source can be corrected and
// an optional stacktrace can be included.
func (l *Logger) log(ctx context.Context, level slog.Level, err error, msg string, args []any) {
// check if logger is enabled
if !l.stderrHandler.Enabled(ctx, level) {
if !l.handler.Enabled(ctx, level) {
return
}

Expand All @@ -172,15 +131,5 @@ func (l *Logger) log(ctx context.Context, level slog.Level, err error, msg strin
r.Add("stacktrace", fmt.Sprintf("%+v", err))
}

// handle with configured handler
switch config.Output {
case OutputStdout:
_ = l.stdoutHandler.Handle(ctx, r)

case OutputStderr:
_ = l.stderrHandler.Handle(ctx, r)

default:
_ = l.stderrHandler.Handle(ctx, r)
}
_ = l.handler.Handle(ctx, r)
}
Loading

0 comments on commit c642e7d

Please sign in to comment.