-
Notifications
You must be signed in to change notification settings - Fork 67
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
slog: Support structured logging #17
Conversation
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.
Very nice! LGTM, just a couple of nits and/or suggestions.
Really excited to get this in so we can have structured logs in lnd
.
514790a
to
d81e157
Compare
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 @guggero 📁
Note to reviewers: Im going to look into writing a Default Handler that can be passed to the NewSLog() constructor that will result in the output looking identical to the existing sublogger output. If this is done, then we can actually just completely remove the |
Note: I have updated the PR to include a default Handler that can be used with the new NewSlogger method :) |
c3830ec
to
9a10e4f
Compare
I have also added a test now |
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.
Great stuff! Super cool that we can get all of those features with zero additional dependencies 💯
log.go
Outdated
// | ||
// This is part of the Logger interface implementation. | ||
func (l *subLog) TraceS(_ context.Context, msg string, attrs ...any) { | ||
l.Tracef(msg, attrs...) |
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.
Do we need to process the attrs
first? Or is the assumption that msg
would never contain formatting directives? Then perhaps we should call into l.Trace()
instead.
Example of what I mean:
bar := "bar"
subLog.TraceS(ctx, "incorrectly used directive: %v", "foo", bar)
What I think would be printed:
incorrectly used directive: foo%!(EXTRA string=bar)
What the user would expect to be printed:
incorrectly used directive: foo=bar
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.
cool yeah agreed
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.
ah - the thing is thought that for this implementation, under the hood there is not actually structured logging going on. So the output we get will not look like "key=value" etc. So I used the *f
methods just so that we are passing the msg
and attr
to some underlying impl. the non f
methods only take ...attr
so the message would not be used.
So this was just to somewhat complete this implementation. In reality if callers want structured logging, they should use the new slog implementation. In the LL fork PR, I completely remove this impl anyways. So I think instead of trying to get this to work as if it is using slog underneath, let's just add a warning to the impl saying it works like the *f
methods.
Taking a step back: i think we arent gonna get this into this repo anyways and so we will carry on with the forked version which removes this impl anyways
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.
EDIT given latest updates to the convo: perhaps I should just look into a best effort way of converting the given attributes to k=v pairs and writing those to the writer
handler.go
Outdated
d.appendAttr(buf, attr) | ||
} | ||
|
||
// Append slog attributes |
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.
nit: missing full stop.
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.
Pending Oli's comments, otherwise LGTM🙏
Ok yall: thanks so much for review here! I think we are gonna go with the forking path here as I dont think the maintainer(s) of the repo is/are active. So let's move discussion & final review there: lightninglabs#1 EDIT: nevermind... if we fork this then we'd also need to fork every other btcsuite PR that we use that imports the logger.... |
cc @davecgh |
@Roasbeef What's up? I guess maybe perms? I really no longer have anything to do with maintaining btc things. A couple of things I noticed on a quick review though is:
|
Aside from the two aforementioned things, and some nits like missing copyright headers, the changes otherwise look fine. As I said though, I no longer maintain anything btc, so I'll defer to @Roasbeef to make merge decisions. |
The reason that go1.21 was chosen is cause
it is off by default - but if we are worried about this, i can update the PR to just allow the user to pass in various
Ok cool |
Aye, I understand the reasoning. The code was changed over to define Regardless of the rationale though, the effect is that anything that uses it will necessarily be hoisted forward thereby forcing everything that uses those things to also be hoisted forwards all the way down the dependency stack. For example, take Even something relatively simple like Without looking super closely, I imagine it would be possible to make this compatible with Go 1.17 by implementing the relevant |
But this is only the case if they want to update to the latest version right? And they would only want to update to the latest version if they want the new features which in this case is The only other reason they'd want to update their btclog dep is if there is some bug found in the old code & they want to update to include just that fix - but for that, we can just maintain a branch that only includes the old implementation & backfills the fix. That way they can point to that branch. It just seems like we should be able to move logging forward and take advantage of new features that go provides us in terms of logging so that those users that want to take advantage of newer features can do so
Meaning just update the interface but have the implementation be defined elsewhere (ie not in this repo)? This would work except for the tricky situation with the |
I think we might be able to get away with using the old version of the package: https://pkg.go.dev/golang.org/x/exp/slog. It doesn't look to use as many recent language features. |
c138569
to
f2bad85
Compare
Ok, I've updated this to create a v2 module instead so that there is no blast radius. I've also removed any direct styling functionality from this and instead have added functional options that will allow a caller of the new module to add its own styling if it wishes |
@yyforyongyu & @guggero - just tagging you guys for a re-review here if possible given the new structure. The biggest "controversial" thing here (and difference from the previous version of this PR) is probably the re-use of the |
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 a couple of nits, otherwise LGTM 💯
|
||
// mergeAttrs returns the attributes from the context merged with the provided attributes. | ||
func mergeAttrs(ctx context.Context, attrs []any) []any { | ||
resp, _ := ctx.Value(attrsKey{}).([]any) // We know the type. |
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.
if that's the case maybe we can just panic here if !ok
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 dont want it to panic cause if nothing has been set yet (ie, there is nothing to append to), then ok will actually be false
// attributes with btclog.LevelTrace to the log. | ||
// | ||
// This is part of the Logger interface implementation. | ||
func (l *sLogger) TraceS(ctx context.Context, msg string, attrs ...any) { |
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.
Wanna point out this does create some overhead as we now need to be concerned about context leak? Is Background
context auto GCed?
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 think passing a context around in go is generally considered a good pattern. If we are doing lots of context.Background() calls, then it means we should update the parent function to take a context. ie, if we are creating lots of context.Backgrounds() then they really should actually be context.TODO()
.
Passing in the context here is really nice cause we can add request scoped attributes that we want to include in logs to the context and then dont need to remember to log them
Thanks for the review yall - added LICENSE to v2 & copied the golang license context to the appropriate file |
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 🪛
log.Critical("Critical") | ||
}, | ||
expectedLog: `[TRC]: Trace | ||
[DBG]: Debug |
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.
hmm formatting seems weird here.
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.
no real other way to define this such that things match on a char by char level. It's just a test too :)
@@ -0,0 +1,16 @@ | |||
ISC License |
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.
maybe you can describe a short how-to create a v2 library of a specific module in case we need it in the future.
// appendValue writes the given slog.Value to the buffer. | ||
func appendValue(buf *buffer, v slog.Value) { | ||
defer func() { | ||
// Recovery in case of nil pointer dereferences. |
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 a slog.Value be nil here ?
This PR does a few things:
Logger
interface with new structured logging messages (DebugS
,InfoS
etc)log/slog
implementation of theLogger
that uses proper structured logging.Details
slog.Handlers
(which underneath could be a json formatter, a logfmt formatter or any anything else like a charm HandlerUsage
The idea is that the
msg
passed to anS
method should be a constant. Theattr...
params are then used for key-value pairs.Each new method also takes a
context.Context
. TheWithCtx
helper can be used to attach any key-value pairs to acontext and then when the logger is called, the pairs will be extracted from the context and added as attributes in the log.
For example: