Conversation
|
I see that CI is failing, and probably needs to be updated. For the tests in this PR to be meaningful, we'll alsoneed to run tests against Go 1.21 (since the new code is behind a build tag). If you'd like me to create a PR to address any of these, please let me know. I'm quite happy to contribute, but don't want to waste my effort if the package maintainers aren't interested 😉 |
|
(did a quick rebase to make CI run) |
mdelapenya
left a comment
There was a problem hiding this comment.
@thaJeztah this LGTM, although I added some minor concerns regarding the usage of a logger Vs a handler. It's basically about not swallowing errors.
@flimzy 👋 Happy to support you here and make progress to eventually merge this into the project.
That was just copied verbatim from the |
|
I've added a test to validate that we report the proper caller, and updated the skip argument accordingly. |
hooks/slog/slog.go
Outdated
| attrs := make([]any, 0, len(entry.Data)) | ||
| for k, v := range entry.Data { | ||
| attrs = append(attrs, slog.Any(k, v)) | ||
| } | ||
| var pcs [1]uintptr | ||
| // skip 8 callers to get to the original logrus caller | ||
| runtime.Callers(8, pcs[:]) |
There was a problem hiding this comment.
Looking if there's some optimisations to be made here; there's also entry.Caller - wondering if that should be used?
There was a problem hiding this comment.
Heh - I was lazy; this is what ChatGPT suggested for this; not entirely sure if the runtime.Callers suggestion is good;
var pc uintptr
if entry.Caller != nil {
pc = entry.Caller.PC
} else {
var pcs [1]uintptr
// Skip runtime.Callers + this function; adjust as needed or make configurable.
runtime.Callers(2, pcs[:])
pc = pcs[0]
}
r := slog.NewRecord(entry.Time, lvl, entry.Message, pc)
if len(entry.Data) != 0 {
attrs := make([]slog.Attr, 0, len(entry.Data))
for k, v := range entry.Data {
attrs = append(attrs, slog.Any(k, v))
}
r.AddAttrs(attrs...)
}
return handler.Handle(ctx, r)There was a problem hiding this comment.
We definitely can do that, but it subtly changes behavior. Perhaps in a good way?
If we use entry.Caller.PC, then to get source= in the final output, both Logrus and the slog handler need to be configured to show sources. With the current implementation, the behavior depends only on slog's configuration.
I'm not sure which is "more correct". I suppose in practice, they'll likely both be enabled or both disabled. So I'll make the change to use entry.Caller.PC unless you feel that's not appropriate :)
hooks/slog/slog.go
Outdated
| attrs := make([]any, 0, len(entry.Data)) | ||
| for k, v := range entry.Data { | ||
| attrs = append(attrs, slog.Any(k, v)) | ||
| } |
There was a problem hiding this comment.
One thing I noticed: logrus used to sort its values when printing the log line.
However here the iteration order of the entry.Data hashmap is not deterministic. The resulting values are put into a slice, which slog keeps ordered as is. This means bridged key-value-pairs will always be shuffled, making the log lines hard to read as a human.
There was a problem hiding this comment.
I guess that could be done. Though I'd push back on it a bit, as it adds computation to something that should be as lightweight as possible. Though I know that's a difference of philosophy between logrus and log/slog. I guess logrus likes to make logs "nice" at the expense of performance. log/slog aims to make logs very efficient, even if the ergonomics are a bit less than ideal. As an example, when logging JSON with log/slog, it writes the key/value pairs as they are received, rather than buffering them, to reduce allocations and other overhead. One of the results of this is that it means you can actually produce logs with duplicate JSON keys.
Now which philosophy is more appropriate for a feature bridging two worlds? Hard to say. It's a coin toss I guess. But my feeling is that if you want this sort of niceness-over-performance, you're probably not going to be using slog in the first place, so you wouldn't be using this hook. 🤷
There was a problem hiding this comment.
Though I'd push back on it a bit, as it adds computation to something that should be as lightweight as possible. Though I know that's a difference of philosophy between logrus and log/slog. I guess logrus likes to make logs "nice" at the expense of performance. log/slog aims to make logs very efficient, even if the ergonomics are a bit less than ideal.
I once wrote a similar bridge myself and also did some benchmarks. Interestingly, logrus bridged to slog (with sorting) is still significantly faster than plain logrus.
(at least when I used logrus as a mere facade like slf4j in Java world, which means I have to call SetOutput and SetFormatter and provide no-op implementations to disable logrus' own processing)
Another thing I think is worth considering:
slog has consistent value ordering. It's just that the values are not lexicographically sorted, but by insertion order. Hashmaps, on the other hand, are totally unpredictable in their order. IMHO that's worse than insertion order (but feel free to disagree, just wanted to give some aspects to think about 🙂)
There was a problem hiding this comment.
To be clear: I don't have a strong feeling on this. I won't likely be using this feature any longer any way 😉 I had forgotten that this PR even existed until I was pinged to make some changes. I've long since retired logrus from any active projects I'm on.
So whatever direction the maintainers want to go I'm happy with.
There was a problem hiding this comment.
Pull request overview
This PR adds a new slog hook for logrus that forwards logrus log entries to a Go log/slog logger. It addresses half of issue #1401, providing a bridge for applications transitioning from logrus to slog. The hook maps logrus levels to slog levels, forwards entry data as slog attributes, and supports custom level mapping and caller source information.
Changes:
- New
hooks/slog/slog.goimplementingSlogHookwith level mapping, field forwarding, and caller propagation - New
hooks/slog/slog_test.gowith tests for default behavior, fields, custom level mapper, error propagation, and source/caller forwarding
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| hooks/slog/slog.go | Implements SlogHook struct, NewSlogHook constructor, default level mapping, and Fire/Levels methods for the logrus.Hook interface |
| hooks/slog/slog_test.go | Tests covering default logging, fields, custom level mapper, slog handler errors propagating to stderr, and source location forwarding |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Jonathan Hall <flimzy@flimzy.com> Co-authored-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Co-authored-by: Jonathan Hall <flimzy@flimzy.com> Co-authored-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Co-authored-by: Jonathan Hall <flimzy@flimzy.com> Co-authored-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Co-authored-by: Jonathan Hall <jonathan@jhall.io> Co-authored-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Co-authored-by: Jonathan Hall <jonathan@jhall.io> Co-authored-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
thaJeztah
left a comment
There was a problem hiding this comment.
OK, I think I pleased all the bots enough now 😂
LGTM, thank you!!
This addresses half of #1401. The easy half, honestly.
I have an implementation of the other half we've been using in our own project, but it will need some polish before making it public, so I'm submitting this PR now to gauge interest of the project maintainers. If we like this, I'll work on polishing the other half in a later PR.