Skip to content

Commit f259c31

Browse files
committed
add tests for xlog [wip]
1 parent d680742 commit f259c31

File tree

7 files changed

+182
-17
lines changed

7 files changed

+182
-17
lines changed

service/middleware/logging_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func TestLogging(t *testing.T) {
2727
log, err := xlog.New(
2828
xlog.AsText(),
2929
xlog.WriteTo(&buf),
30+
xlog.WithSource(),
3031
xlog.MockClock(time.Unix(1650000000, 0).UTC()),
3132
)
3233
require.NoError(t, err)

service/status_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func TestHandleStatus_withFailIO(t *testing.T) {
6363
log, err := xlog.New(
6464
xlog.AsText(),
6565
xlog.WriteTo(&buf),
66+
xlog.WithSource(),
6667
xlog.MockClock(time.Unix(1650000000, 0).UTC()),
6768
)
6869
require.NoError(t, err)

xlog/attrs.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ func (err ErrorValue) Value() slog.Value {
3232

3333
// LogValue implements [slog.LogValuer].
3434
func (err ErrorValue) LogValue() slog.Value {
35-
return slog.StringValue(err.Error())
35+
return err.Value()
3636
}
3737

3838
// Error constructs a first-class error log attribute.
3939
//
4040
// Not to be confused with (xlog.Logger).Error() or (log/slog).Error(),
4141
// which produce an error-level log message.
4242
func Error(err error) slog.Attr {
43-
return slog.Any(ErrorKey, ErrorValue{err})
43+
return Any(ErrorKey, ErrorValue{err})
4444
}

xlog/discard.go

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package xlog
22

33
import (
4+
"context"
45
"log/slog"
56
"os"
67
)
@@ -16,9 +17,19 @@ func NewDiscard() Logger {
1617
return &discard{}
1718
}
1819

20+
var (
21+
_ Logger = (*discard)(nil)
22+
_ slog.Handler = (*discard)(nil)
23+
)
24+
1925
func (*discard) Debug(string, ...slog.Attr) {}
2026
func (*discard) Info(string, ...slog.Attr) {}
2127
func (*discard) Warn(string, ...slog.Attr) {}
2228
func (*discard) Error(string, ...slog.Attr) {}
2329
func (*discard) Fatal(string, ...slog.Attr) { os.Exit(1) }
2430
func (d *discard) With(...slog.Attr) Logger { return d }
31+
32+
func (*discard) Enabled(context.Context, slog.Level) bool { return false }
33+
func (*discard) Handle(context.Context, slog.Record) error { return nil }
34+
func (d *discard) WithAttrs(attrs []slog.Attr) slog.Handler { return d }
35+
func (d *discard) WithGroup(name string) slog.Handler { return d }

xlog/options.go

+19-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package xlog
22

33
import (
4+
"errors"
45
"io"
56
"log/slog"
67
"os"
@@ -16,12 +17,11 @@ import (
1617
type Option func(*options) error
1718

1819
type options struct {
19-
discard bool
20-
output io.Writer
21-
color bool
22-
clock internal.Clock
23-
level slog.Leveler
24-
source bool
20+
output io.Writer
21+
color bool
22+
clock internal.Clock
23+
level slog.Leveler
24+
source bool
2525

2626
buildHandler func(o *options) slog.Handler
2727
}
@@ -47,9 +47,15 @@ func LeveledString(s string) Option {
4747
}
4848
}
4949

50+
var ErrNilWriter = errors.New("invalid writer: nil")
51+
5052
// WriteTo sets the output.
5153
func WriteTo(w io.Writer) Option {
5254
return func(o *options) error {
55+
if w == nil {
56+
return ErrNilWriter
57+
}
58+
5359
o.output = w
5460
return nil
5561
}
@@ -103,7 +109,9 @@ func AsText() Option {
103109
opts := []slogor.OptionFn{
104110
slogor.SetLevel(o.level.Level()),
105111
slogor.SetTimeFormat("[15:04:05.000]"),
106-
slogor.ShowSource(),
112+
}
113+
if o.source {
114+
opts = append(opts, slogor.ShowSource())
107115
}
108116
if f, isFile := o.output.(*os.File); !isFile || !isatty.IsTerminal(f.Fd()) {
109117
opts = append(opts, slogor.DisableColor())
@@ -118,7 +126,10 @@ func AsText() Option {
118126
// constructor.
119127
func Discard() Option {
120128
return func(o *options) error {
121-
o.discard = true
129+
o.buildHandler = func(o *options) slog.Handler {
130+
// the discard logger doesn't have any options
131+
return &discard{}
132+
}
122133
return nil
123134
}
124135
}

xlog/options_test.go

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package xlog
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"log/slog"
8+
"runtime"
9+
"testing"
10+
"time"
11+
12+
"github.com/stretchr/testify/suite"
13+
)
14+
15+
func TestOptionSuite(t *testing.T) {
16+
suite.Run(t, new(OptionSuite))
17+
}
18+
19+
type OptionSuite struct {
20+
suite.Suite
21+
options
22+
}
23+
24+
func (o *OptionSuite) SetupTest() {
25+
o.level = slog.LevelDebug
26+
}
27+
28+
func (o *OptionSuite) TestLeveled() {
29+
err := Leveled(slog.LevelError)(&o.options)
30+
o.Require().NoError(err)
31+
o.Assert().Equal(slog.LevelError, o.level)
32+
33+
err = Leveled(slog.Level(999))(&o.options)
34+
o.Require().NoError(err)
35+
o.Assert().Equal(slog.Level(999), o.level)
36+
}
37+
38+
func (o *OptionSuite) TestLeveledString_valid() {
39+
for i, tt := range []struct {
40+
input string
41+
expected slog.Level
42+
}{
43+
{"dbg", slog.LevelDebug},
44+
{"debug", slog.LevelDebug},
45+
46+
{"", slog.LevelInfo},
47+
{"info", slog.LevelInfo},
48+
{"INFO", slog.LevelInfo},
49+
50+
{"WARN", slog.LevelWarn},
51+
{"WARNING", slog.LevelWarn},
52+
53+
{"ERR", slog.LevelError},
54+
{"ERROR", slog.LevelError},
55+
{"FaTaL", slog.LevelError},
56+
} {
57+
o.level = slog.Level(100 + i)
58+
err := LeveledString(tt.input)(&o.options)
59+
o.Require().NoError(err)
60+
o.Assert().Equal(tt.expected, o.level)
61+
}
62+
}
63+
64+
func (o *OptionSuite) TestLeveledString_invalid() {
65+
o.level = slog.Level(100)
66+
err := LeveledString("ifno")(&o.options)
67+
o.Require().Equal(slog.Level(100), o.level)
68+
o.Assert().EqualError(err, `unknown log level: "ifno"`)
69+
}
70+
71+
func (o *OptionSuite) TestWriteTo() {
72+
var buf bytes.Buffer
73+
err := WriteTo(&buf)(&o.options)
74+
o.Require().NoError(err)
75+
o.Assert().Equal(&buf, o.output)
76+
77+
err = WriteTo(nil)(&o.options)
78+
o.Assert().EqualError(err, "invalid writer: nil")
79+
}
80+
81+
func (o *OptionSuite) TestMockClock() {
82+
t := time.Now()
83+
err := MockClock(t)(&o.options)
84+
o.Require().NoError(err)
85+
o.Require().NotNil(o.clock)
86+
o.Assert().EqualValues(t, o.clock.Now())
87+
}
88+
89+
func (o *OptionSuite) TestWithSource() {
90+
err := WithSource()(&o.options)
91+
o.Require().NoError(err)
92+
o.Assert().True(o.source)
93+
}
94+
95+
func (o *OptionSuite) TestColor() {
96+
err := Color()(&o.options)
97+
o.Require().NoError(err)
98+
o.Assert().True(o.color)
99+
}
100+
101+
func (o *OptionSuite) testBuildHandler(expectedLog string, opts ...Option) {
102+
o.T().Helper()
103+
104+
var buf bytes.Buffer
105+
o.output = &buf
106+
107+
for _, opt := range opts {
108+
o.Require().NoError(opt(&o.options))
109+
}
110+
o.Require().NotNil(o.buildHandler)
111+
h := o.buildHandler(&o.options)
112+
o.Require().NotNil(h)
113+
114+
o.Assert().False(h.Enabled(context.Background(), -999))
115+
116+
pc, _, _, ok := runtime.Caller(1)
117+
o.Require().True(ok)
118+
err := h.Handle(context.Background(), slog.NewRecord(
119+
time.Time{}, slog.LevelInfo, "test", pc,
120+
))
121+
o.Assert().NoError(err)
122+
o.Assert().EqualValues(expectedLog, buf.String())
123+
}
124+
125+
func (o *OptionSuite) TestAsJSON() {
126+
o.testBuildHandler(`{"level":"INFO","msg":"test"}`+"\n", AsJSON())
127+
}
128+
129+
func (o *OptionSuite) TestAsText() {
130+
o.testBuildHandler("INFO test\n", AsText())
131+
132+
_, _, line, ok := runtime.Caller(0)
133+
o.Require().True(ok)
134+
msg := fmt.Sprintf("INFO options_test.go:%d test\n", line+3)
135+
o.testBuildHandler(msg, WithSource(), AsText())
136+
}
137+
138+
func (o *OptionSuite) TestDiscard() {
139+
o.testBuildHandler("", Discard())
140+
}

xlog/xlog.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ func New(opt ...Option) (Logger, error) {
4949
}
5050
}
5151

52-
// the discard logger doesn't require any further setup
53-
if opts.discard {
54-
return &discard{}, nil
55-
}
56-
5752
// setup mock time
5853
h := opts.buildHandler(&opts)
54+
55+
// skip a lot of overhead for the discard logger
56+
if d, ok := h.(*discard); ok {
57+
return d, nil
58+
}
59+
5960
if opts.clock != nil {
6061
h = &mockTimeHandler{
6162
clock: opts.clock,
@@ -111,13 +112,13 @@ func (log *logger) With(a ...slog.Attr) Logger {
111112
// "error" and "fatal". Other input will result in err not being nil.
112113
func ParseLevel(s string) (l slog.Level, err error) {
113114
switch strings.ToLower(s) {
114-
case "debug":
115+
case "dbg", "debug":
115116
l = slog.LevelDebug
116117
case "info", "": // make the zero value useful
117118
l = slog.LevelInfo
118119
case "warn", "warning":
119120
l = slog.LevelWarn
120-
case "error", "fatal":
121+
case "err", "error", "fatal":
121122
l = slog.LevelError
122123
default:
123124
err = fmt.Errorf("unknown log level: %q", s)

0 commit comments

Comments
 (0)