Skip to content

Commit df03e14

Browse files
committed
Drop fields in entries with level < log's level
This is a big saver when doing debug logging in tight loop, but needs to be clearly documented. ```bash name old alloc/op new alloc/op delta Logger_levels/level_not_met-10 264B ± 0% 264B ± 0% ~ (all equal) Logger_levels/level_not_met,_one_field-10 344B ± 0% 264B ± 0% -23.26% (p=0.029 n=4+4) Logger_levels/level_not_met,_many_fields-10 6.17kB ± 0% 0.52kB ± 0% -91.57% (p=0.029 n=4+4) Logger_levels/level_met-10 18.5kB ± 0% 18.5kB ± 0% ~ (all equal) Logger_levels/level_met,_one_field-10 2.02kB ± 0% 2.02kB ± 0% ~ (all equal) Logger_levels/level_met,_many_fields-10 7.84kB ± 0% 7.84kB ± 0% ~ (all equal) name old allocs/op new allocs/op delta Logger_levels/level_not_met-10 13.0 ± 0% 13.0 ± 0% ~ (all equal) Logger_levels/level_not_met,_one_field-10 15.0 ± 0% 13.0 ± 0% -13.33% (p=0.029 n=4+4) Logger_levels/level_not_met,_many_fields-10 85.0 ± 0% 45.0 ± 0% -47.06% (p=0.029 n=4+4) Logger_levels/level_met-10 512 ± 0% 512 ± 0% ~ (all equal) Logger_levels/level_met,_one_field-10 55.0 ± 0% 55.0 ± 0% ~ (all equal) Logger_levels/level_met,_many_fields-10 125 ± 0% 125 ± 0% ~ (all equal) ```
1 parent 6c54444 commit df03e14

File tree

7 files changed

+89
-46
lines changed

7 files changed

+89
-46
lines changed

‎README.md‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,42 @@ Main changes:
1010
* Trim unneeded dependencies.
1111
* Make `Fields` into a slice to preserve log order.
1212
* Split `Entry` into `Entry` and `EntryFields`. This is easier to reason about and more effective.
13-
* Rework the logger interface to allow lazy creation of messages, e.g. `Info(fmt.Stringer)`.
13+
* Split the old `Interface` in two and remove all but one `Log` method (see below).
14+
* This allows for lazy creation of messages in `Log(fmt.Stringer)` and ignoring fields added in `LevelLogger`s with levels below the `Logger`'s.
15+
16+
17+
```go
18+
// Logger is the main interface for the logger.
19+
type Logger interface {
20+
// WithLevel returns a new entry with `level` set.
21+
WithLevel(Level) *EntryFields
22+
}
23+
24+
// LevelLogger handles logging for a given level.
25+
type LevelLogger interface {
26+
// Log logs a message at the given level using the string from calling s.String().
27+
// Note that s.String() will not be called if the level is not enabled.
28+
Log(s fmt.Stringer)
29+
30+
// WithLevel returns a new entry with `level` set.
31+
WithLevel(Level) *EntryFields
32+
33+
// WithFields returns a new entry with the`fields` in fields set.
34+
// This is a noop if LevelLogger's level is less than Logger's.
35+
WithFields(fields Fielder) *EntryFields
36+
37+
// WithLevel returns a new entry with the field f set with value v
38+
// This is a noop if LevelLogger's level is less than Logger's.
39+
WithField(f string, v any) *EntryFields
40+
41+
// WithDuration returns a new entry with the "duration" field set
42+
// to the given duration in milliseconds.
43+
// This is a noop if LevelLogger's level is less than Logger's.
44+
WithDuration(time.Duration) *EntryFields
45+
46+
// WithError returns a new entry with the "error" set to `err`.
47+
// This is a noop if err is nil or LevelLogger's level is less than Logger's.
48+
WithError(error) *EntryFields
49+
}
50+
51+
```

‎default.go‎

Lines changed: 0 additions & 23 deletions
This file was deleted.

‎entry.go‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99

1010
// assert interface compliance.
1111
var (
12-
_ Logger = (*EntryFields)(nil)
13-
_ Logger = (*Entry)(nil)
12+
_ LevelLogger = (*EntryFields)(nil)
13+
_ LevelLogger = (*Entry)(nil)
1414
)
1515

1616
// EntryFields represents a single log entry at a given log level.
@@ -57,26 +57,30 @@ func NewEntry(log *logger) *EntryFields {
5757
}
5858
}
5959

60-
// WithLevel returns a new entry with `level` set.
6160
func (e EntryFields) WithLevel(level Level) *EntryFields {
6261
e.Level = level
6362
return &e
6463
}
6564

66-
// WithFields returns a new entry with `fields` set.
6765
func (e EntryFields) WithFields(fielder Fielder) *EntryFields {
66+
if e.isLevelDisabled() {
67+
return &e
68+
}
6869
e.Fields = append(e.Fields, fielder.Fields()...)
6970
return &e
7071
}
7172

72-
// WithField returns a new entry with the `key` and `value` set.
7373
func (e *EntryFields) WithField(key string, value any) *EntryFields {
74+
if e.isLevelDisabled() {
75+
return e
76+
}
7477
return e.WithFields(Fields{{key, value}})
7578
}
7679

77-
// WithDuration returns a new entry with the "duration" field set
78-
// to the given duration in milliseconds.
7980
func (e *EntryFields) WithDuration(d time.Duration) *EntryFields {
81+
if e.isLevelDisabled() {
82+
return e
83+
}
8084
return e.WithField("duration", d.Milliseconds())
8185
}
8286

@@ -85,7 +89,7 @@ func (e *EntryFields) WithDuration(d time.Duration) *EntryFields {
8589
// The given error may implement .Fielder, if it does the method
8690
// will add all its `.Fields()` into the returned entry.
8791
func (e *EntryFields) WithError(err error) *EntryFields {
88-
if err == nil {
92+
if err == nil || e.isLevelDisabled() {
8993
return e
9094
}
9195

@@ -113,6 +117,10 @@ func (e *EntryFields) WithError(err error) *EntryFields {
113117
return ctx
114118
}
115119

120+
func (e *EntryFields) isLevelDisabled() bool {
121+
return e.Level < e.Logger.Level
122+
}
123+
116124
// Log a message at the given level.
117125
func (e *EntryFields) Log(s fmt.Stringer) {
118126
e.Logger.log(e, s)

‎entry_test.go‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,21 @@ func TestEntry_WithFields(t *testing.T) {
2727
}
2828

2929
func TestEntry_WithField(t *testing.T) {
30-
a := NewEntry(nil)
30+
a := NewLogger(LoggerConfig{Handler: NoopHandler, Level: InfoLevel}).WithLevel(InfoLevel)
3131
b := a.WithField("foo", "baz").WithField("foo", "bar")
3232
qt.Assert(t, a.distinctFieldsLastByName(), qt.IsNil)
3333
qt.Assert(t, b.distinctFieldsLastByName(), qt.DeepEquals, Fields{{"foo", "bar"}})
3434
}
3535

3636
func TestEntry_WithError(t *testing.T) {
37-
a := NewEntry(nil)
37+
a := NewLogger(LoggerConfig{Handler: NoopHandler, Level: InfoLevel}).WithLevel(InfoLevel)
3838
b := a.WithError(fmt.Errorf("boom"))
3939
qt.Assert(t, a.distinctFieldsLastByName(), qt.IsNil)
4040
qt.Assert(t, b.distinctFieldsLastByName(), qt.DeepEquals, Fields{{"error", "boom"}})
4141
}
4242

4343
func TestEntry_WithError_fields(t *testing.T) {
44-
a := NewEntry(nil)
44+
a := NewLogger(LoggerConfig{Handler: NoopHandler, Level: InfoLevel}).WithLevel(InfoLevel)
4545
b := a.WithError(errFields("boom"))
4646
qt.Assert(t, a.distinctFieldsLastByName(), qt.IsNil)
4747
qt.Assert(t,
@@ -54,14 +54,14 @@ func TestEntry_WithError_fields(t *testing.T) {
5454
}
5555

5656
func TestEntry_WithError_nil(t *testing.T) {
57-
a := NewEntry(nil)
57+
a := NewLogger(LoggerConfig{Handler: NoopHandler, Level: InfoLevel}).WithLevel(InfoLevel)
5858
b := a.WithError(nil)
5959
qt.Assert(t, a.distinctFieldsLastByName(), qt.IsNil)
6060
qt.Assert(t, b.distinctFieldsLastByName(), qt.IsNil)
6161
}
6262

6363
func TestEntry_WithDuration(t *testing.T) {
64-
a := NewEntry(nil)
64+
a := NewLogger(LoggerConfig{Handler: NoopHandler, Level: InfoLevel}).WithLevel(InfoLevel)
6565
b := a.WithDuration(time.Second * 2)
6666
qt.Assert(t, b.distinctFieldsLastByName(), qt.DeepEquals, Fields{{"duration", int64(2000)}})
6767
}

‎interface.go‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,35 @@ import (
55
"time"
66
)
77

8-
type Leveler interface {
8+
// Logger is the main interface for the logger.
9+
type Logger interface {
10+
// WithLevel returns a new entry with `level` set.
911
WithLevel(Level) *EntryFields
1012
}
1113

12-
type Logger interface {
13-
Log(fmt.Stringer)
14-
Leveler
15-
WithFields(Fielder) *EntryFields
16-
WithField(string, any) *EntryFields
14+
// LevelLogger
15+
type LevelLogger interface {
16+
// Log logs a message at the given level using the string from calling s.String().
17+
// Note that s.String() will not be called if the level is not enabled.
18+
Log(s fmt.Stringer)
19+
20+
// WithLevel returns a new entry with `level` set.
21+
WithLevel(Level) *EntryFields
22+
23+
// WithFields returns a new entry with the`fields` in fields set.
24+
// This is a noop if LevelLogger's level is less than Logger's.
25+
WithFields(fields Fielder) *EntryFields
26+
27+
// WithLevel returns a new entry with the field f set with value v
28+
// This is a noop if LevelLogger's level is less than Logger's.
29+
WithField(f string, v any) *EntryFields
30+
31+
// WithDuration returns a new entry with the "duration" field set
32+
// to the given duration in milliseconds.
33+
// This is a noop if LevelLogger's level is less than Logger's.
1734
WithDuration(time.Duration) *EntryFields
35+
36+
// WithError returns a new entry with the "error" set to `err`.
37+
// This is a noop if err is nil or LevelLogger's level is less than Logger's.
1838
WithError(error) *EntryFields
1939
}

‎logger.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
// assert interface compliance.
13-
var _ Leveler = (*logger)(nil)
13+
var _ Logger = (*logger)(nil)
1414

1515
// String implements fmt.Stringer and can be used directly in
1616
// the log methods.
@@ -92,7 +92,7 @@ type LoggerConfig struct {
9292
}
9393

9494
// New returns a new logger.
95-
func NewLogger(cfg LoggerConfig) Leveler {
95+
func NewLogger(cfg LoggerConfig) Logger {
9696
if cfg.Handler == nil {
9797
panic("handler cannot be nil")
9898
}

‎logger_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func BenchmarkLogger_large(b *testing.B) {
130130
}
131131

132132
func BenchmarkLogger_levels(b *testing.B) {
133-
doWork := func(l log.Logger) {
133+
doWork := func(l log.LevelLogger) {
134134
for i := 0; i < 10; i++ {
135135
l.Log(log.NewStringFunc(
136136
func() string {

0 commit comments

Comments
 (0)