Skip to content

Commit d5ea65a

Browse files
committed
Split Entry into Entry and EntryFields
Also remove the now incompatible Trace method. This is easier to reason about and saves some memory allocations: ```bash name old time/op new time/op delta Logger_small-10 84.1ns ± 4% 81.6ns ± 4% ~ (p=0.200 n=4+4) Logger_medium-10 234ns ± 0% 217ns ± 0% -6.86% (p=0.029 n=4+4) Logger_large-10 623ns ± 1% 583ns ± 0% -6.38% (p=0.029 n=4+4) name old alloc/op new alloc/op delta Logger_small-10 224B ± 0% 144B ± 0% -35.71% (p=0.029 n=4+4) Logger_medium-10 536B ± 0% 456B ± 0% -14.93% (p=0.029 n=4+4) Logger_large-10 1.86kB ± 0% 1.69kB ± 0% -9.44% (p=0.029 n=4+4) name old allocs/op new allocs/op delta Logger_small-10 2.00 ± 0% 2.00 ± 0% ~ (all equal) Logger_medium-10 6.00 ± 0% 6.00 ± 0% ~ (all equal) Logger_large-10 11.0 ± 0% 11.0 ± 0% ~ (all equal) ```
1 parent cd8b1a9 commit d5ea65a

File tree

11 files changed

+78
-211
lines changed

11 files changed

+78
-211
lines changed

‎entry.go‎

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,81 @@
11
package log
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"os"
67
"strings"
78
"time"
89
)
910

1011
// assert interface compliance.
11-
var _ Interface = (*Entry)(nil)
12+
var _ Interface = (*EntryFields)(nil)
1213

1314
// Now returns the current time.
1415
var Now = time.Now
1516

16-
// Entry represents a single log entry.
17+
// EntryFields represents a single log entry.
18+
type EntryFields struct {
19+
Logger *Logger `json:"-"`
20+
Fields Fields `json:"-"`
21+
start time.Time
22+
}
23+
24+
// Entry holds a Entry with a Message, Timestamp and a Level.
25+
// This is what is actually logged.
1726
type Entry struct {
18-
Logger *Logger `json:"-"`
19-
Fields Fields `json:"fields"`
20-
Level Level `json:"level"`
21-
Timestamp time.Time `json:"timestamp"`
22-
Message string `json:"message"`
23-
start time.Time
27+
*EntryFields
28+
FieldsUnique Fields `json:"-"`
29+
Level Level `json:"level"`
30+
Timestamp time.Time `json:"timestamp"`
31+
Message string `json:"message"`
32+
}
33+
34+
func (e Entry) MarshalJSON() ([]byte, error) {
35+
fields := make(map[string]any)
36+
for _, f := range e.FieldsUnique {
37+
fields[f.Name] = f.Value
38+
}
39+
40+
type EntryAlias Entry
41+
return json.Marshal(&struct {
42+
Fields map[string]any `json:"fields"`
43+
EntryAlias
44+
}{
45+
Fields: fields,
46+
EntryAlias: (EntryAlias)(e),
47+
})
2448
}
2549

2650
// NewEntry returns a new entry for `log`.
27-
func NewEntry(log *Logger) *Entry {
28-
return &Entry{
51+
func NewEntry(log *Logger) *EntryFields {
52+
return &EntryFields{
2953
Logger: log,
3054
}
3155
}
3256

3357
// WithFields returns a new entry with `fields` set.
34-
func (e Entry) WithFields(fielder Fielder) *Entry {
58+
func (e EntryFields) WithFields(fielder Fielder) *EntryFields {
3559
e.Fields = append(e.Fields, fielder.Fields()...)
3660
return &e
3761
}
3862

3963
// WithField returns a new entry with the `key` and `value` set.
40-
func (e *Entry) WithField(key string, value any) *Entry {
64+
func (e *EntryFields) WithField(key string, value any) *EntryFields {
4165
return e.WithFields(Fields{{key, value}})
4266
}
4367

4468
// WithDuration returns a new entry with the "duration" field set
4569
// to the given duration in milliseconds.
46-
func (e *Entry) WithDuration(d time.Duration) *Entry {
70+
func (e *EntryFields) WithDuration(d time.Duration) *EntryFields {
4771
return e.WithField("duration", d.Milliseconds())
4872
}
4973

5074
// WithError returns a new entry with the "error" set to `err`.
5175
//
5276
// The given error may implement .Fielder, if it does the method
5377
// will add all its `.Fields()` into the returned entry.
54-
func (e *Entry) WithError(err error) *Entry {
78+
func (e *EntryFields) WithError(err error) *EntryFields {
5579
if err == nil {
5680
return e
5781
}
@@ -81,78 +105,58 @@ func (e *Entry) WithError(err error) *Entry {
81105
}
82106

83107
// Debug level message.
84-
func (e *Entry) Debug(msg string) {
108+
func (e *EntryFields) Debug(msg string) {
85109
e.Logger.log(DebugLevel, e, msg)
86110
}
87111

88112
// Info level message.
89-
func (e *Entry) Info(msg string) {
113+
func (e *EntryFields) Info(msg string) {
90114
e.Logger.log(InfoLevel, e, msg)
91115
}
92116

93117
// Warn level message.
94-
func (e *Entry) Warn(msg string) {
118+
func (e *EntryFields) Warn(msg string) {
95119
e.Logger.log(WarnLevel, e, msg)
96120
}
97121

98122
// Error level message.
99-
func (e *Entry) Error(msg string) {
123+
func (e *EntryFields) Error(msg string) {
100124
e.Logger.log(ErrorLevel, e, msg)
101125
}
102126

103127
// Fatal level message, followed by an exit.
104-
func (e *Entry) Fatal(msg string) {
128+
func (e *EntryFields) Fatal(msg string) {
105129
e.Logger.log(FatalLevel, e, msg)
106130
os.Exit(1)
107131
}
108132

109133
// Debugf level formatted message.
110-
func (e *Entry) Debugf(msg string, v ...any) {
134+
func (e *EntryFields) Debugf(msg string, v ...any) {
111135
e.Debug(fmt.Sprintf(msg, v...))
112136
}
113137

114138
// Infof level formatted message.
115-
func (e *Entry) Infof(msg string, v ...any) {
139+
func (e *EntryFields) Infof(msg string, v ...any) {
116140
e.Info(fmt.Sprintf(msg, v...))
117141
}
118142

119143
// Warnf level formatted message.
120-
func (e *Entry) Warnf(msg string, v ...any) {
144+
func (e *EntryFields) Warnf(msg string, v ...any) {
121145
e.Warn(fmt.Sprintf(msg, v...))
122146
}
123147

124148
// Errorf level formatted message.
125-
func (e *Entry) Errorf(msg string, v ...any) {
149+
func (e *EntryFields) Errorf(msg string, v ...any) {
126150
e.Error(fmt.Sprintf(msg, v...))
127151
}
128152

129153
// Fatalf level formatted message, followed by an exit.
130-
func (e *Entry) Fatalf(msg string, v ...any) {
154+
func (e *EntryFields) Fatalf(msg string, v ...any) {
131155
e.Fatal(fmt.Sprintf(msg, v...))
132156
}
133157

134-
// Trace returns a new entry with a Stop method to fire off
135-
// a corresponding completion log, useful with defer.
136-
func (e *Entry) Trace(msg string) *Entry {
137-
e.Info(msg)
138-
v := e.WithFields(e.Fields)
139-
v.Message = msg
140-
v.start = time.Now()
141-
return v
142-
}
143-
144-
// Stop should be used with Trace, to fire off the completion message. When
145-
// an `err` is passed the "error" field is set, and the log level is error.
146-
func (e *Entry) Stop(err *error) {
147-
if err == nil || *err == nil {
148-
e.WithDuration(time.Since(e.start)).Info(e.Message)
149-
} else {
150-
e.WithDuration(time.Since(e.start)).WithError(*err).Error(e.Message)
151-
}
152-
}
153-
154158
// mergedFields returns the fields list collapsed into a single slice.
155-
func (e *Entry) mergedFields() Fields {
159+
func (e *EntryFields) mergedFields() Fields {
156160
fields := make(Fields, 0, len(e.Fields))
157161
for i := len(e.Fields) - 1; i >= 0; i-- {
158162
f := e.Fields[i]
@@ -176,12 +180,12 @@ func (e *Entry) mergedFields() Fields {
176180
}
177181

178182
// finalize returns a copy of the Entry with Fields merged.
179-
func (e *Entry) finalize(level Level, msg string) *Entry {
183+
func (e *EntryFields) finalize(level Level, msg string) *Entry {
180184
return &Entry{
181-
Logger: e.Logger,
182-
Fields: e.mergedFields(),
183-
Level: level,
184-
Message: msg,
185-
Timestamp: Now(),
185+
EntryFields: e,
186+
FieldsUnique: e.mergedFields(),
187+
Level: level,
188+
Message: msg,
189+
Timestamp: Now(),
186190
}
187191
}

‎handlers/cli/cli.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (h *Handler) HandleLog(e *log.Entry) error {
6767

6868
color.Fprintf(h.Writer, "%s %-25s", bold.Sprintf("%*s", h.Padding+1, level), e.Message)
6969

70-
for _, field := range e.Fields {
70+
for _, field := range e.FieldsUnique {
7171
if field.Name == "source" {
7272
continue
7373
}

‎handlers/json/json_test.go‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ func init() {
1717
}
1818
}
1919

20-
// TODO(bep) fix me.
21-
func _Test(t *testing.T) {
20+
func Test(t *testing.T) {
2221
var buf bytes.Buffer
2322

2423
log.SetHandler(json.New(&buf))

‎handlers/text/text.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (h *Handler) HandleLog(e *log.Entry) error {
6969
ts := time.Since(start) / time.Second
7070
fmt.Fprintf(h.Writer, "\033[%dm%6s\033[0m[%04d] %-25s", color, level, ts, e.Message)
7171

72-
for _, f := range e.Fields {
72+
for _, f := range e.FieldsUnique {
7373
fmt.Fprintf(h.Writer, " \033[%dm%s\033[0m=%v", color, f.Name, f.Value)
7474
}
7575

‎handlers/text/text_test.go‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@ func init() {
1717
}
1818
}
1919

20-
// TODO(bep) fix me.
21-
func _Test(t *testing.T) {
20+
func TestTextHandler(t *testing.T) {
2221
var buf bytes.Buffer
2322

2423
log.SetHandler(text.New(&buf))
2524
log.WithField("user", "tj").WithField("id", "123").Info("hello")
2625
log.WithField("user", "tj").Info("world")
2726
log.WithField("user", "tj").Error("boom")
2827

29-
expected := "\x1b[34m INFO\x1b[0m[0000] hello \x1b[34mid\x1b[0m=123 \x1b[34muser\x1b[0m=tj\n\x1b[34m INFO\x1b[0m[0000] world \x1b[34muser\x1b[0m=tj\n\x1b[31m ERROR\x1b[0m[0000] boom \x1b[31muser\x1b[0m=tj\n"
28+
expected := "\x1b[34m INFO\x1b[0m[0000] hello \x1b[34muser\x1b[0m=tj \x1b[34mid\x1b[0m=123\n\x1b[34m INFO\x1b[0m[0000] world \x1b[34muser\x1b[0m=tj\n\x1b[31m ERROR\x1b[0m[0000] boom \x1b[31muser\x1b[0m=tj\n"
3029

3130
qt.Assert(t, buf.String(), qt.Equals, expected)
3231
}

‎interface.go‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import "time"
44

55
// Interface represents the API of both Logger and Entry.
66
type Interface interface {
7-
WithFields(Fielder) *Entry
8-
WithField(string, any) *Entry
9-
WithDuration(time.Duration) *Entry
10-
WithError(error) *Entry
7+
WithFields(Fielder) *EntryFields
8+
WithField(string, any) *EntryFields
9+
WithDuration(time.Duration) *EntryFields
10+
WithError(error) *EntryFields
1111
Debug(string)
1212
Info(string)
1313
Warn(string)
@@ -18,5 +18,4 @@ type Interface interface {
1818
Warnf(string, ...any)
1919
Errorf(string, ...any)
2020
Fatalf(string, ...any)
21-
Trace(string) *Entry
2221
}

‎levels_test.go‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,20 @@ func TestParseLevel(t *testing.T) {
3838

3939
func TestLevel_MarshalJSON(t *testing.T) {
4040
e := Entry{
41-
Level: InfoLevel,
42-
Message: "hello",
43-
Fields: Fields{},
41+
Level: InfoLevel,
42+
Message: "hello",
43+
EntryFields: &EntryFields{},
4444
}
4545

46-
expect := `{"fields":[],"level":"info","timestamp":"0001-01-01T00:00:00Z","message":"hello"}`
46+
expect := `{"fields":{},"level":"info","timestamp":"0001-01-01T00:00:00Z","message":"hello"}`
4747

4848
b, err := json.Marshal(e)
4949
qt.Assert(t, err, qt.IsNil)
5050
qt.Assert(t, string(b), qt.Equals, expect)
5151
}
5252

5353
func TestLevel_UnmarshalJSON(t *testing.T) {
54-
s := `{"fields":[],"level":"info","timestamp":"0001-01-01T00:00:00Z","message":"hello"}`
54+
s := `{"fields":{},"level":"info","timestamp":"0001-01-01T00:00:00Z","message":"hello"}`
5555
e := new(Entry)
5656

5757
err := json.Unmarshal([]byte(s), e)

‎logger.go‎

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,26 +53,26 @@ type Logger struct {
5353
}
5454

5555
// WithFields returns a new entry with `fields` set.
56-
func (l *Logger) WithFields(fields Fielder) *Entry {
56+
func (l *Logger) WithFields(fields Fielder) *EntryFields {
5757
return NewEntry(l).WithFields(fields.Fields())
5858
}
5959

6060
// WithField returns a new entry with the `key` and `value` set.
6161
//
6262
// Note that the `key` should not have spaces in it - use camel
6363
// case or underscores
64-
func (l *Logger) WithField(key string, value any) *Entry {
64+
func (l *Logger) WithField(key string, value any) *EntryFields {
6565
return NewEntry(l).WithField(key, value)
6666
}
6767

6868
// WithDuration returns a new entry with the "duration" field set
6969
// to the given duration in milliseconds.
70-
func (l *Logger) WithDuration(d time.Duration) *Entry {
70+
func (l *Logger) WithDuration(d time.Duration) *EntryFields {
7171
return NewEntry(l).WithDuration(d)
7272
}
7373

7474
// WithError returns a new entry with the "error" set to `err`.
75-
func (l *Logger) WithError(err error) *Entry {
75+
func (l *Logger) WithError(err error) *EntryFields {
7676
return NewEntry(l).WithError(err)
7777
}
7878

@@ -126,16 +126,10 @@ func (l *Logger) Fatalf(msg string, v ...any) {
126126
NewEntry(l).Fatalf(msg, v...)
127127
}
128128

129-
// Trace returns a new entry with a Stop method to fire off
130-
// a corresponding completion log, useful with defer.
131-
func (l *Logger) Trace(msg string) *Entry {
132-
return NewEntry(l).Trace(msg)
133-
}
134-
135129
// log the message, invoking the handler. We clone the entry here
136130
// to bypass the overhead in Entry methods when the level is not
137131
// met.
138-
func (l *Logger) log(level Level, e *Entry, msg string) {
132+
func (l *Logger) log(level Level, e *EntryFields, msg string) {
139133
if level < l.Level {
140134
return
141135
}

0 commit comments

Comments
 (0)