-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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 New Function Log Entry #844
Conversation
Codecov Report
@@ Coverage Diff @@
## master #844 +/- ##
==========================================
+ Coverage 98.35% 98.36% +0.01%
==========================================
Files 43 43
Lines 2368 2391 +23
==========================================
+ Hits 2329 2352 +23
Misses 32 32
Partials 7 7
Continue to review full report at Codecov.
|
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.
Thanks you for the contribution @wijayaerick!
It looks great overall, the only design question I'm trying to decide is whether Function
be part of the EntryCaller
struct, with the function key always specified, but not encoded (for backwards compatibility) by leaving the default encode fields to skip.
This would allow us to reuse AddCaller
and WithCaller
, and assumes that there's no additional performance impact to get the function name when we're already getting the caller information.
logger_test.go
Outdated
}{ | ||
{opts(), `^$`}, | ||
{opts(WithFunction(false)), `^$`}, | ||
{opts(AddFunction()), `zap.TestLoggerAddFunction.func1$`}, |
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.
To avoid relying on the internal function names used when anonymous functions are used, can you put the log calls into a separate function in this file, like func infoLog(logger *logger.Logger)
and that way, we can compare this to a value we control
logger_test.go
Outdated
func TestLoggerAddFunction(t *testing.T) { | ||
tests := []struct { | ||
options []Option | ||
pat string |
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.
Can we use ==
to compare the Function
output? Is there parts of the output that we don't control? I think the function is just package.function
?
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.
Yes, we can use ==
The example output is go.uber.org/zap.infoLog
, there should not be parts of the output that we don't control
Thanks for the suggestion
logger_test.go
Outdated
func TestLoggerAddFunctionFail(t *testing.T) { | ||
errBuf := &ztest.Buffer{} | ||
withLogger(t, DebugLevel, opts(AddFunction(), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) { | ||
log.callerSkip = 1e3 |
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.
instead of accessing the internals, can you do AddCallerSkip(1e3)
?
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.
@prashantv done
t, | ||
logs.AllUntimed()[0].Entry.Message, | ||
"Failure.", | ||
"Expected original message to survive failures in runtime.Caller.") |
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.
can you also verify that the Function
is set to an empty string in the failure case
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.
@prashantv done
options.go
Outdated
// AddFunction configures the Logger to annotate each message with the function | ||
// name of zap's caller. See also WithFunction. | ||
func AddFunction() Option { | ||
return WithFunction(true) | ||
} |
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.
We have both AddCaller
and WithCaller
for backwards compatibility reasons (WithCaller
was added later, and we couldn't remove AddCaller
without breaking the API).
In this case, since this is a new API, only adding WithFunction
would be preferred
zapcore/console_encoder.go
Outdated
@@ -92,6 +92,9 @@ func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, | |||
if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil { | |||
c.EncodeCaller(ent.Caller, arr) | |||
} | |||
if ent.Function != "" && c.FunctionKey != "" { | |||
arr.AppendString(ent.Function) |
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.
the FunctionKey
is not being used
zapcore/entry.go
Outdated
@@ -147,6 +147,7 @@ type Entry struct { | |||
LoggerName string | |||
Message string | |||
Caller EntryCaller | |||
Function string |
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.
Perhaps Function
be a part of the EntryCaller
struct, since it's additional metadata related to the caller?
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.
I had the same thought while I was implementing this. I think I was confused in the past and so I ended up putting Function
as a log entry instead of being part of EntryCaller
.
Now I think again, it is reasonable to put Function
as part of EntryCaller
.
@prashantv Is this what you mean? I just want to confirm. |
Yes, exactly. For backwards compatibility, we may need to leave the default values in the encoder configuration for function to be |
9a1f58d
to
9c616d5
Compare
9c616d5
to
dafe1a2
Compare
@prashantv Thank you for your detailed review comments. |
logger.go
Outdated
numFrames := runtime.Callers(log.callerSkip+callerSkipOffset+1, pc) | ||
frame, _ := runtime.CallersFrames(pc[:numFrames]).Next() | ||
defined := frame.PC != 0 | ||
if !defined { |
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.
couple of things here:
- do we need to handle the case where
numFrames == 0
which is handled in the runtime (e.g., if someone sets skip too high) - let's move the logic into a separate function that returns a frame
Can we also add a comment for the frame.PC != 0
case (i think that's the "failed to decode case" according to the docs)
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.
I completely missed that case... thanks for the suggestions, updated the code @prashantv
I've checked the benchmark, there should be no additional performance impact
logger.go
Outdated
ce.Entry.Caller = zapcore.NewEntryCaller(frame.PC, frame.File, frame.Line, defined) | ||
ce.Entry.Caller.Function = frame.Function |
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.
since we're setting fields anyway, we could just do zapcore.EntryCaller{PC: frame.PC, File: frame.File, ...}
instead of using the constructor but then modifying (prefer to create once and use that instead of mutating the Entry.Caller
)
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.
thanks @prashantv updated
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.
LGTM, thanks @wijayaerick!
I'll merge this in later in the week, so others can also review (cc @abhinav).
Co-authored-by: Prashant Varanasi <[email protected]>
Co-authored-by: Abhinav Gupta <[email protected]>
This PR introduces function name in log entry, as mentioned in #716.
Example: