-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a slightly slower, but less verbose, logger #147
Changes from 1 commit
be920bc
e513235
77b7321
ae2b22c
b41c502
18e13ed
bcde07a
1591492
62a712a
4dda6ea
596acf3
b819160
729ad48
56a80f6
79553b0
c925d2d
d03d8f0
1398fd2
8c913fb
92b8c4e
2162452
66f4380
0e10f81
c22e1a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,31 +112,33 @@ func getSugarFields(args ...interface{}) ([]Field, error) { | |
return nil, errors.New("field name must be string") | ||
} | ||
} else { | ||
switch value := value.(type) { | ||
switch v := value.(type) { | ||
case error: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this special casing for error. I think it's a little surprising, complicates the code, and will possibly cause unexpected errors. For example, if some struct happens to satisfy the error interface (E.g., Thrift exceptions), then they will never be able to use it as a value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure either but I don't see much options here. Also I think this wrapper supposed to be merely syntax sugar. It's not supposed to satisfy all possible requirements and just have to be "ok for most uses". I'm open to any ideas about handling errors, but as for now this is the only working and (mostly) magic free implementation I can think of. |
||
return nil, errors.New("error can only be the first argument") | ||
case bool: | ||
fields = append(fields, Bool(key, value)) | ||
fields = append(fields, Bool(key, v)) | ||
case float64: | ||
fields = append(fields, Float64(key, value)) | ||
fields = append(fields, Float64(key, v)) | ||
case int: | ||
fields = append(fields, Int(key, value)) | ||
fields = append(fields, Int(key, v)) | ||
case int64: | ||
fields = append(fields, Int64(key, value)) | ||
fields = append(fields, Int64(key, v)) | ||
case uint: | ||
fields = append(fields, Uint(key, value)) | ||
fields = append(fields, Uint(key, v)) | ||
case uint64: | ||
fields = append(fields, Uint64(key, value)) | ||
fields = append(fields, Uint64(key, v)) | ||
case string: | ||
fields = append(fields, String(key, value)) | ||
fields = append(fields, String(key, v)) | ||
case time.Time: | ||
fields = append(fields, Time(key, value)) | ||
fields = append(fields, Time(key, v)) | ||
case time.Duration: | ||
fields = append(fields, Duration(key, value)) | ||
fields = append(fields, Duration(key, v)) | ||
case fmt.Stringer: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please add a unit test to ensure that's the case as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed LogMarshaler altogether. As I mentioned in previous comments, type switch fails to catch structs implementing this interface for some reason. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain more about the type switch not catching this -- I think keeping log marshaler is a good idea, and it should work, |
||
fields = append(fields, Stringer(key, value)) | ||
case error: | ||
return nil, errors.New("error can only be the first argument") | ||
fields = append(fields, Stringer(key, v)) | ||
case LogMarshaler: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @akshayjshah @anuptalwalkar @sectioneight I looked through coverage report and eventually found that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @akabos I bet (Just guessing) the LogMarshaler didn't understand unexported struct. I commented on the test below to update m9e to json serializable struct that seemed to fix the test coverage issue you are seeing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I understand Go exported/unexported logic, these fields are only used by |
||
fields = append(fields, Marshaler(key, v)) | ||
default: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is missing support for the in the existing zap logger, logging an sugar.With("k1", 42, errors.New("this automatically goes into 'error'))` or sugar.With("k1", errors.New("nothing automatic"), "k2", errors.New("field is an errorType still")) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest sugar logger should accept optional error value as the first of variadic arguments. E.g.:
Motivation behind my suggestion is that having more then one errors in single log message is probably pretty rare requirement which don't need to be covered by syntax sugar. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (amended after looking more closely) I'd prefer not to do this, since I'd prefer an API in which callers need to pass both the key and the value for errors:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I missed this earlier, but it seems like both @akshayjshah and I think we should remove the first variadic argument can be As for the checking the |
||
return nil, errors.New("invalid argument type") | ||
fields = append(fields, Object(key, value)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this approach, if a type has |
||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,23 +40,20 @@ func TestSugarGetSugarFields(t *testing.T) { | |
_, err = getSugarFields("test1", 1, "test2") | ||
assert.Error(t, err, "Should return error on invalid number of arguments") | ||
|
||
_, err = getSugarFields("test1", nil) | ||
assert.Error(t, err, "Should return error on argument of unknown type") | ||
|
||
_, err = getSugarFields("test1", 1, "error", errors.New("")) | ||
assert.Error(t, err, "Should return error when error passed as value (special case of unknown type)") | ||
|
||
_, err = getSugarFields(1, 1) | ||
assert.Error(t, err, "Should return error on non-string field name") | ||
|
||
fields, _ = getSugarFields("test", 1) | ||
assert.Equal(t, 1, len(fields), "Should return 1 field") | ||
assert.Len(t, fields, 1, "Should return 1 field") | ||
|
||
fields, _ = getSugarFields("test1", 1, "test2", 2) | ||
assert.Equal(t, 2, len(fields), "Should return 2 fields") | ||
assert.Len(t, fields, 2, "Should return 2 fields") | ||
|
||
fields, _ = getSugarFields(errors.New("error"), "test1", 1) | ||
assert.Equal(t, 2, len(fields), "Should return 2 fields") | ||
assert.Len(t, fields, 2, "Should return 2 fields") | ||
} | ||
|
||
func TestSugarLevel(t *testing.T) { | ||
|
@@ -83,6 +80,17 @@ func withSugarLogger(t testing.TB, opts []Option, f func(Sugar, *testBuffer)) { | |
f(sugar, sink) | ||
} | ||
|
||
type m9e struct { | ||
Foo int `json:"foo"` | ||
Bar bool `json:"bar"` | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need this struct to be json formattable. This will fix the failing TestSugarLogTypes test -
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will fix test but no the issue. When you define struct as json formatable There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed LogMarshaler altogether because type switch fail to catch structs implementing this interface. |
||
|
||
func (m *m9e) MarshalLog(kv KeyValue) error { | ||
kv.AddInt("foo", m.Foo) | ||
kv.AddBool("bar", m.Bar) | ||
return nil | ||
} | ||
|
||
func TestSugarLog(t *testing.T) { | ||
opts := opts(Fields(Int("foo", 42))) | ||
withSugarLogger(t, opts, func(logger Sugar, buf *testBuffer) { | ||
|
@@ -101,6 +109,7 @@ func TestSugarLog(t *testing.T) { | |
|
||
func TestSugarLogTypes(t *testing.T) { | ||
withSugarLogger(t, nil, func(logger Sugar, buf *testBuffer) { | ||
logger.Debug("") | ||
logger.Debug("", "bool", true) | ||
logger.Debug("", "float64", float64(1.23456789)) | ||
logger.Debug("", "int", int(-1)) | ||
|
@@ -111,7 +120,10 @@ func TestSugarLogTypes(t *testing.T) { | |
logger.Debug("", "time", time.Unix(0, 0)) | ||
logger.Debug("", "duration", time.Second) | ||
logger.Debug("", "stringer", DebugLevel) | ||
logger.Debug("", "marshaler", m9e{1, true}) | ||
logger.Debug("", "object", []string{"foo", "bar"}) | ||
assert.Equal(t, []string{ | ||
`{"level":"debug","msg":""}`, | ||
`{"level":"debug","msg":"","bool":true}`, | ||
`{"level":"debug","msg":"","float64":1.23456789}`, | ||
`{"level":"debug","msg":"","int":-1}`, | ||
|
@@ -122,15 +134,8 @@ func TestSugarLogTypes(t *testing.T) { | |
`{"level":"debug","msg":"","time":0}`, | ||
`{"level":"debug","msg":"","duration":1000000000}`, | ||
`{"level":"debug","msg":"","stringer":"debug"}`, | ||
}, buf.Lines(), "Incorrect output from logger") | ||
}) | ||
} | ||
|
||
func TestSugarLogNoArgs(t *testing.T) { | ||
withSugarLogger(t, nil, func(logger Sugar, buf *testBuffer) { | ||
logger.Debug("no args message") | ||
assert.Equal(t, []string{ | ||
`{"level":"debug","msg":"no args message"}`, | ||
`{"level":"debug","msg":"","marshaler":{"foo":1,"bar":true}}`, | ||
`{"level":"debug","msg":"","object":["foo","bar"]}`, | ||
}, buf.Lines(), "Incorrect output from logger") | ||
}) | ||
} | ||
|
@@ -190,10 +195,6 @@ func TestSugarLogErrors(t *testing.T) { | |
logger.Log(InfoLevel, "foo", "foo", errors.New("b")) | ||
assert.Equal(t, `{"level":"info","msg":"foo","error":"error can only be the first argument"}`, buf.Stripped(), "Should log error argument position is invalid") | ||
}) | ||
withSugarLogger(t, nil, func(logger Sugar, buf *testBuffer) { | ||
logger.Log(InfoLevel, "foo", "bar", nil) | ||
assert.Equal(t, `{"level":"info","msg":"foo","error":"invalid argument type"}`, buf.Stripped(), "Should log invalid argument type") | ||
}) | ||
} | ||
|
||
func TestSugarLogDiscards(t *testing.T) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just my $0.02- Loggers usually don't throw/return errors and rather gulp them down to the log files. Instead of returning error for this function, you want to just add a string field with a tiny message "unparsable argument" or something. It should be on the devs to notice an fix the logging format later in the game.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, please see b819160
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latest commit makes sugar logger handle internal errors in the same way core logger does.