diff --git a/CHANGELOG.md b/CHANGELOG.md index 595887d..69edd9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ The format is based on [Keep a Changelog], and this project adheres to [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html +## [Unreleased] + +### Added + +- Added `NewStreamLogger()` and `NewStreamHandler()` functions that create + loggers and handlers that write to an `io.Writer`. + +### Changed + +- **[BC]** Renamed `NewLogger` to `NewTestLogger` +- **[BC]** Renamed `NewHandler` to `NewTestHandler` + ## [0.1.1] - 2024-04-09 - Include the elapsed duration since the logger was created in each log message. diff --git a/spruce.go b/spruce.go index ec081a5..bc2a8dc 100644 --- a/spruce.go +++ b/spruce.go @@ -6,33 +6,11 @@ import ( "log/slog" "slices" "strings" - "testing" "time" ) -// TestingT is the subset of the [testing.TB] interface that is used to write -// logs. -type TestingT interface { - Log(...any) -} - -var _ TestingT = (testing.TB)(nil) - -// NewLogger returns a [slog.Logger] that writes to t. -func NewLogger(t TestingT) *slog.Logger { - return slog.New(NewHandler(t)) -} - -// NewHandler returns a new [slog.Handler] that writes to t. -func NewHandler(t TestingT) slog.Handler { - return &handler{ - T: t, - epoch: time.Now(), - } -} - type handler struct { - T TestingT + log func(string) error attrs []slog.Attr groups []string epoch time.Time @@ -84,9 +62,7 @@ func (h *handler) Handle(_ context.Context, rec slog.Record) error { writeAttrs(buf, 0, attrs, true) - h.T.Log(buf.String()) - - return nil + return h.log(buf.String()) } func writeAttrs( @@ -150,7 +126,7 @@ func writeAttrs( func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler { return &handler{ - T: h.T, + log: h.log, attrs: append(slices.Clone(h.attrs), attrs...), groups: h.groups, epoch: h.epoch, @@ -159,7 +135,7 @@ func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler { func (h *handler) WithGroup(name string) slog.Handler { return &handler{ - T: h.T, + log: h.log, attrs: h.attrs, groups: append(slices.Clone(h.groups), name), epoch: h.epoch, diff --git a/spruce_test.go b/spruce_test.go index 2693fac..9ea5b80 100644 --- a/spruce_test.go +++ b/spruce_test.go @@ -9,12 +9,13 @@ import ( "time" "github.com/dogmatiq/spruce" + . "github.com/dogmatiq/spruce" "github.com/google/go-cmp/cmp" ) func TestHandler_noAttributes(t *testing.T) { s := &testingTStub{T: t} - l := spruce.NewLogger(s) + l := NewTestLogger(s) l.Info("") s.Expect( @@ -24,7 +25,7 @@ func TestHandler_noAttributes(t *testing.T) { func TestHandler_stringAttribute(t *testing.T) { s := &testingTStub{T: t} - l := spruce.NewLogger(s) + l := NewTestLogger(s) l.Info("", "", "") s.Expect( @@ -35,7 +36,7 @@ func TestHandler_stringAttribute(t *testing.T) { func TestHandler_stringerAttribute(t *testing.T) { s := &testingTStub{T: t} - l := spruce.NewLogger(s) + l := NewTestLogger(s) l.Info( "", @@ -50,7 +51,7 @@ func TestHandler_stringerAttribute(t *testing.T) { func TestHandler_attributeAlignment(t *testing.T) { s := &testingTStub{T: t} - l := spruce.NewLogger(s) + l := NewTestLogger(s) l.Info( "", @@ -66,7 +67,7 @@ func TestHandler_attributeAlignment(t *testing.T) { func TestHandler_nestedAttributes(t *testing.T) { s := &testingTStub{T: t} - l := spruce.NewLogger(s) + l := NewTestLogger(s) l.Info( "", @@ -92,7 +93,7 @@ func TestHandler_nestedAttributes(t *testing.T) { func TestHandler_whitespaceEscaping(t *testing.T) { s := &testingTStub{T: t} - l := spruce.NewLogger(s) + l := NewTestLogger(s) l.Info( "", @@ -107,7 +108,7 @@ func TestHandler_whitespaceEscaping(t *testing.T) { func TestHandler_WithAttrs(t *testing.T) { s := &testingTStub{T: t} l := spruce. - NewLogger(s). + NewTestLogger(s). With("", "") l.Info("") @@ -120,7 +121,7 @@ func TestHandler_WithAttrs(t *testing.T) { func TestHandler_WithAttrs_sameKey(t *testing.T) { s := &testingTStub{T: t} l := spruce. - NewLogger(s). + NewTestLogger(s). With("", "") l.Info("", "", "") @@ -134,7 +135,7 @@ func TestHandler_WithAttrs_sameKey(t *testing.T) { func TestHandler_WithGroup(t *testing.T) { s := &testingTStub{T: t} l := spruce. - NewLogger(s). + NewTestLogger(s). WithGroup("") l.Info("", "", "") diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..080330e --- /dev/null +++ b/stream.go @@ -0,0 +1,30 @@ +package spruce + +import ( + "io" + "log/slog" + "time" +) + +var newLine = []byte{'\n'} + +// NewStreamLogger returns a [slog.Logger] that writes to w. +func NewStreamLogger(w io.Writer) *slog.Logger { + return slog.New(NewStreamHandler(w)) +} + +// NewStreamHandler returns a new [slog.Handler] that writes to w. +func NewStreamHandler(w io.Writer) slog.Handler { + return &handler{ + log: func(s string) error { + if _, err := w.Write([]byte(s)); err != nil { + return err + } + if _, err := w.Write(newLine); err != nil { + return err + } + return nil + }, + epoch: time.Now(), + } +} diff --git a/stream_test.go b/stream_test.go new file mode 100644 index 0000000..be594ac --- /dev/null +++ b/stream_test.go @@ -0,0 +1,22 @@ +package spruce_test + +import ( + "strings" + "testing" + + . "github.com/dogmatiq/spruce" +) + +func TestNewStreamLogger(t *testing.T) { + w := &strings.Builder{} + l := NewStreamLogger(w) + + l.Info("") + + got := w.String() + want := "\n" + + if !strings.HasSuffix(got, want) { + t.Errorf("got %q, want suffix of %q", got, want) + } +} diff --git a/testing.go b/testing.go new file mode 100644 index 0000000..29dc249 --- /dev/null +++ b/testing.go @@ -0,0 +1,30 @@ +package spruce + +import ( + "log/slog" + "testing" + "time" +) + +// TestingT is the subset of [testing.TB] that is used to write logs. +type TestingT interface { + Log(...any) +} + +var _ TestingT = (testing.TB)(nil) + +// NewTestLogger returns a [slog.Logger] that writes to t. +func NewTestLogger(t TestingT) *slog.Logger { + return slog.New(NewTestHandler(t)) +} + +// NewTestHandler returns a new [slog.Handler] that writes to t. +func NewTestHandler(t TestingT) slog.Handler { + return &handler{ + log: func(s string) error { + t.Log(s) + return nil + }, + epoch: time.Now(), + } +}