-
Notifications
You must be signed in to change notification settings - Fork 220
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
Migrate from zerolog to slog #248
Conversation
Pull Request Test Coverage Report for Build 5379673535
💛 - Coveralls |
I am a bit confused about coveralls does their line calculation. How could there be a 0.5% change when lines were only replaced? |
I have removed the Delay Merging tag in the name as I believe now with the release of GO |
Big change to review but LGTM. Will this go in with a Mochi minor version update as some indication people need to also update Go in ther environment? |
@torntrousers I believe. I was also thinking if there was any apatite for some more experimental version tag to denote that this is a big change and a way to test before having it in an official version release; however, I don't want to start muddying up our versions. |
I've reviewed the changes, and everything looks fine. LGTM. I looked into slog, and it doesn't directly support interface-based abstraction. Perhaps we can make it more flexible by creating an adapter to achieve a similar interface-like behavior. This interface could include common log functions such as Info(), Infof(), Error(), Errorf(), etc. We could provide a default logger implementation using slog, and users could switch to other loggers by implementing their own logger structs and swapping them in the Server code. @mochi-co @dgduncan, I'd like to hear your thoughts on this approach. |
@werbenhu If I understand what you mean correctly, I personally feel that providing a |
@dgduncan I've written a simple stress test to compare the performance differences between slog.LogAttrs(), slog.Info(), and zerolog.Info(). Below is my code and the results: package main
import (
"context"
"os"
"testing"
"github.com/rs/zerolog"
"golang.org/x/exp/slog"
)
func BenchmarkSlogInfo(b *testing.B) {
f, err := os.OpenFile("a.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
panic(err)
}
defer f.Close()
log := slog.New(slog.NewTextHandler(f, nil))
b.ResetTimer()
for n := 0; n < b.N; n++ {
log.Info("hello world", "index", n, "name", "werben", "age", 300)
}
}
func BenchmarkSlogAttr(b *testing.B) {
f, err := os.OpenFile("b.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
panic(err)
}
defer f.Close()
log := slog.New(slog.NewTextHandler(f, nil))
b.ResetTimer()
ctx := context.TODO()
for n := 0; n < b.N; n++ {
log.LogAttrs(ctx,
slog.LevelInfo,
"hello world",
slog.Int("index", n),
slog.String("name", "werben"),
slog.Int("age", 300),
)
}
}
func BenchmarkZerologInfo(b *testing.B) {
f, err := os.OpenFile("c.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
panic(err)
}
defer f.Close()
zlog := zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: f})
b.ResetTimer()
for n := 0; n < b.N; n++ {
zlog.Info().
Int("index", n).
Str("name", "werben").
Int("age", 300).
Msg("hello world")
}
}
From my results, slog outperforms zerolog by a significant margin. However, the difference between slog.LogAttrs() and slog.Info() is not very significant. Personally, I lean towards the style of log.Info(), which is concise and elegant. I'm unsure about how others perceive this code style. If the majority share my perspective, could we consider sacrificing a slight amount of performance for code that is more elegant? |
I'm just moving house this week so I'm a bit short on connectivity, but I'm going to reply to all these shortly - it's great work you've done! |
@werbenhu Wow thank you so much for doing that test and putting some numbers to what I have read. I absolutely am in favor of a more elegant and easier to read |
Pull Request Test Coverage Report for Build 6034299854
💛 - Coveralls |
Changing "data" to "key" or "id" here might be more appropriate.
Changing "data" to "key" or "id" here might be more appropriate.
Not checking if err is equal to nil
printing information for ID or error is missing.
I made another update as per the mention of @thedevop of changing |
So I have decided to leave the Unless anyone has any other objections I do believe that this is ready for final review by @mochi-co . |
So much good stuff in this PR, thank you @dgduncan, @werbenhu, @thedevop, @torntrousers. Particularly great to see the benchmarks, helps in making an informed decision. Apologies for being late to the party. Overall I think this is the right move and I'm excited to get it merged in. My thoughts:
However, in conclusion I am very excited about this change. @dgduncan you have done a superb job at driving this project forward, and implementing it in a thoughtful and well-considered way. I think this is something you can be proud of. |
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've reviewed this PR, the only recommendation I have is to remove the 48 calls to errorMsg.Error()
- as @thedevop noted, this is no longer needed when using log/slog
. Otherwise, looks good to me!
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.
Approved. I'll leave it for a bit for anyone to raise any other concerns, otherwise I will merge this in over the weekend and release as v2.4.0
!
@dgduncan , as I prepare my application for this break change (as I do override default logger), I ran into few issues, maybe you have some insights to simple solutions:
|
@thedevop Would you mind explaining providing an example of a struct that is failing to be logged? I have not run into this issue yet in my own use. Do you mean that there is something |
I want to see what @thedevop says before I merge this in case there's a compelling reason that we really should be adding an interface. |
@mochi-co I am curious as well. I do think that if this is a problem in only very specific use cases you in theory should be able to wrap the struct in another struct that implements The thing I do want to know is if this is a problem for structs that are being logged in the broker code itself or if this is a problem with code from in the embedding application that will now have to use |
This is a problem for any struct that doesn't implement LogValuer (or json. Marshaler) interface AND using JSONHandler. Here is an example that demonstrates the issue: https://go.dev/play/p/KNsC_CmrmSG. I also have B which implements LogValue (commented out). type A struct {
name string
value int
}
textLog := slog.New(slog.NewTextHandler(os.Stdout, nil))
jsonLog := slog.New(slog.NewJSONHandler(os.Stdout, nil))
a := A{"test", 123}
textLog.Info("text without logvalue", "a", a)
jsonLog.Info("json without logvalue", "a", a) shows up empty with JSONHandler for a:
|
@thedevop the JSON marshaling process only formats the fields that are exported. Making Name and Value of struct A exported is fine. https://go.dev/play/p/8H4doeulXIS
|
@werbenhu , you're right! I missed that because the TextHandler was able log. Thanks! |
If everyone is happy, I will merge this branch. Please let me know 🙂 |
Engage. |
Merged! Amazing, immense work by everyone, especially @dgduncan! 🥳 |
This PR, if approved, will be merged upon the release of GO v1.21.
This PR changes the repository over from
zerolog
toslog
. Some important considerations were taken when working on this PR:zerolog
is capable of low to zero allocation logging. Because of this, I decided to useLogAttrs
to get as close to this performance as possible at the expense of readability.slog
does not have any equivalent to.Err()
where you can pass anerror
directly to and it will check ifnil
in.Err()
itself. To get around this, anif
has been included on those calls and the log lines all but duplicated. I would love some feedback and see if anyone has any other ideas for this.