-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Adding File, Line, Stacktrace #75
Conversation
Thanks @derekdowling, awesome to get work started on this. I feel like benchmarks are a relevant concern for this work going forward, can you see if you can get some numbers on the impact of this? |
Because if it's negligible, we could consider adding it to the core.. however, you might be right that it's more cleanly implemented as hooks since the formatters are run after the hooks anyway. |
This didn't seem to work for me, but adding it to the core should make it simper. I just hacked to together for myself, but +1 for adding it directly to the core and letting it be a setting. |
So if you just add this: _, file, line, _ := runtime.Caller(1) (or however many calls deep you want to intercept it) to logrus itself and provide that data through to any middleware that wants to use it, you're only adding 275 nanoseconds per actual logging call (which is nothing, and compared to the cost of writing to stdout or a file is double nothing). As reported by: When swapped to actually print, the cost becomes ~2.8 microseconds in total. Running just the print with supplied data comes to ~2.4 microseconds, so I'd buy the 275 nanoseconds for the lookup and copy out. |
Thanks @robmccoll for taking a look at this! Honestly, I think most people are willing to pay that price for better traceability. I think the bigger thing would be adding some configurability about how and when this additional metadata is logged. In my experience, file/lineno are pretty universally useful in most circumstances. On the other hand, I only really want a stack trace on errors or on Debug prints when I am debugging/testing/deving. I'd also like to do some work around formatting the stack trace output as it's pretty gross. I'll try and have a core integrated version this afternoon. |
@placeybordeaux yeah, sorry I did a quick copy and paste job from my own project and didn't fix the package name/imports. |
Okay, I took my first crack at baking it right into the core. I still need to deal with the stack trace, but that's going to take a bit more formatting and thought. The "caller" field will give you something like "logrus_test.go:56". I can probably do some fancier logic to make it more like "logrus/logrus_test.go:56" but I am trying to keep the call as fast as possible. I guess the other option is to print the absolute path, but in my opinion that clogs up the logging because of potentially how long the path might be. |
return strings.Join([]string{filepath.Base(file), strconv.Itoa(line)}, ":") | ||
} | ||
// not sure what the convention should be here | ||
return "unavailable" |
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.
Not sure when this case might occur, or what exactly we want to return if it does.
Cool! This looks good to me. Thanks |
@@ -74,6 +78,11 @@ func (entry *Entry) log(level Level, msg string) { | |||
entry.Time = time.Now() | |||
entry.Level = level | |||
entry.Message = msg | |||
entry.Data["caller"] = context() | |||
|
|||
// if level == WarnLevel || level == InfoLevel { |
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.
Not sure if we want this feature, but we can do something like only printing stack traces for Debug or Error/Fatal/Panic cases so we know how we got there.
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 don't think the penalty of including it is too big. Capturing it only for certain levels can be confusing.
@sirupsen any thoughts on the features I've commented or asked for feedback on here? I'd like to try and keep this PR in line with whatever philosophies you want. As soon as I have feedback, I'll get this fully implemented/tested. |
Sorry about the delay here, super busy atm. I'll try to get to this PR on the weekend. |
Understandable, I have my own work around in place for now so I am not in a rush, but I'd like to add get this sorted out as well! |
@sirupsen any update on this? I need this feature as well and if we have some guidance on how you prefer it to be implemented, I can help with the implementation. |
stack := make([]byte, 2048) | ||
size := runtime.Stack(stack, false) | ||
return string(stack[:size]) | ||
} |
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.
What does this string look like? Is it necessary to cast?
Sorry for the terrible responsiveness on this ticket. We'll get this in before the new year for sure, hopefully in time for Christmas. My thoughts here are:
What I'm a little unsure of is whether we should include There are plenty of people in this thread, what are you use case for either? Cheers! |
As for default text formatting of the caller, I would suggest having an explicit fixed-width "column" in the console,
allocating maybe 12-15 characters to it, truncating the filename if necessary, with no path. I think this would do for a good default for most people because there are rare situations in which you work on a project with conflicting file names even in different directories/packages. An alternative would be using the package name by default instead of the filename (pro would be consistent logging even when you split existing code into multiple files, just like you keep consistent package API, con would be that many projects have large |
@aybabtme what do you think about this? Interested to hear your opinion. |
I think the For stacktraces, I prefer keeping this out. When we need a stacktrace using logrus, we use sentry hooks that fire and generate them. They could be logged at that point, in the hook. The same holds for airbrake hooks or other such exception reporting hooks. So I think the stacktrace part should be left out. |
@aybabtme you mean left out by default or not including the function altogether? For dev, i don't configure sentry; i use that for testing released versions (either staging or production), but for dev on my own computer of my own working copy, I would prefer a local solution. |
@rasky I meant leaving it out altogether. It seems to be something that would fit in a hook that you could add a development time when you need it. |
I've been playing around with this a bit and I'm not sure there's a reliable way to get the constant of stack frames to skip right in all environments to get the file name. Does anyone know of a better way? |
Did you check what sentry-go does? |
I don't see any logic for it. I'm toying with this as a middleware. I'm stuck getting the file name reliably, because it's different in what seems to be three cases:
I want it as a middleware, because I don't think there's any value in adding those fields to the core since it'll require hacking the formatters. It's much easier to leverage hooks, by just adding it and then getting the fields for free. Most people are happy with the default formatters—just want the fields. I'm not going to enable the fields by default, because a lot of CLI applications use |
Any update on this? I came looking for a way to just get filename and line number. Thx. edit: I should add the middleware link you posted somewhat works for me when using log.WithFields, but prints wrong level/line/file when using plain log.Info, etc. |
@jeberly Yep, that's the problem with this PR— |
Please looks at #133 Usage example: import (
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/sourcefile"
)
var log = logrus.New()
func init() {
log.Hooks.Add(&logrus_sourcefile.SourceFileHook{
LogLevel: logrus.InfoLevel,
})
}
func main() {
defer func() {
err := recover()
if err != nil {
log.WithFields(logrus.Fields{
"omg": true,
"err": err,
"number": 100,
}).Fatal("The ice breaks!")
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
} |
I want filename and line number, too. I'm very happy to do so. Any update? |
+1 :) |
+1 |
+1 :-) |
Hey, what's the status of this PR? Still blocked? Now way to reliably get them? |
@oli-g I'm sure there is, but noone has convinced me of an implementation that works across platforms. I'd love for this to make it into core, but I can't merge something that has a hard-coded constant that only works on some machines. |
+1 |
The constant "4" is not platform dependant, it is just the number of callers:
It won't change across platform. However, if the developer call |
Maybe something like this: https://gist.github.com/978b2c102497b1f3f022 |
So @sirupsen I'm back from the dead and have been playing with this code a bit more. I've isolated the tracing logic to only get called with a |
+1 |
1 similar comment
+1 |
This is just an initial approach to get the ball rolling in reference to #63 that I implemented last night really quickly. It needs test, hardening,etc. I am all for a much more permanent solution, but this is at least a start. The two things that need to be addressed are whether or not we are losing context by combining the file & line number like so:
and how to format the stack trace. Right now it is pretty gross:
In this example you add this functionality by: