Skip to content
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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

ellemouton
Copy link

This PR does a few things:

  • turns this package into a go module
  • Expands the existing Logger interface with new structured logging messages (DebugS, InfoS etc)
  • let's the existing default implementation implement these new methods (in an unstructured way)
  • add a new log/slog implementation of the Logger that uses proper structured logging.

Details

  • This new implementation is flexible enough to allow callers to pass in their own slog.Handlers (which underneath could be a json formatter, a logfmt formatter or any anything else like a charm Handler

Usage

The idea is that the msg passed to an S method should be a constant. The attr... params are then used for key-value pairs.

log.InfoS(ctx, "User performed a request",
			"user_id", id,
			"request_uri: ", uri)

Each new method also takes a context.Context. The WithCtx helper can be used to attach any key-value pairs to a
context and then when the logger is called, the pairs will be extracted from the context and added as attributes in the log.

For example:

ctx := WithCtx(context.Background(), "user_id", id)

// This log will include both the `user_id` and `request_uri` attributes.
log.InfoS(ctx, "User performed a request", "request_uri", uri)

Copy link

@guggero guggero left a 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.

go.mod Outdated Show resolved Hide resolved
slog.go Show resolved Hide resolved
slog.go Outdated Show resolved Hide resolved
Which sets the new minimum golang version required for compiling the
package to go 1.21 which is when the standard library `log/slog` package
was introduced.
So that it does not clash with the standard lib's slog package.
Reuse some of the slog.Level types and add a few of our own.

Also, move level logic to its own file for readability.
And let subLog implement the methods by calling its existing methods.
Note that subLog will not produce structured logs as of this commit. In
future, it can be updated to use an slog.Logger and this package can
implement a default slog.Handler.
Copy link
Author

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @guggero 📁

go.mod Outdated Show resolved Hide resolved
slog.go Show resolved Hide resolved
slog.go Outdated Show resolved Hide resolved
@ellemouton
Copy link
Author

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 subLog impl in this PR

@ellemouton
Copy link
Author

Note: I have updated the PR to include a default Handler that can be used with the new NewSlogger method :)

Update the buffer pool to use a concrete type and steel some other
updates from the buffer pool used by std lib. Also adds a few useful
helpers that we will use later on and also factors out the timestamp
writing part from formatHeader since we will use that again later.
Since we will use it later no with a different skip depth.
Which is a valid Handler that can be used with the NewSloggger call.
@ellemouton
Copy link
Author

I have also added a test now

slog.go Show resolved Hide resolved
Copy link

@guggero guggero left a 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 💯

//
// This is part of the Logger interface implementation.
func (l *subLog) TraceS(_ context.Context, msg string, attrs ...any) {
l.Tracef(msg, attrs...)
Copy link

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

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool yeah agreed

Copy link
Author

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

Copy link
Author

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 Show resolved Hide resolved
d.appendAttr(buf, attr)
}

// Append slog attributes
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing full stop.

handler_test.go Show resolved Hide resolved
Copy link

@yyforyongyu yyforyongyu left a 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🙏

@ellemouton
Copy link
Author

ellemouton commented Sep 23, 2024

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....
Ideally we need merge here.

@ellemouton ellemouton closed this Sep 23, 2024
@ellemouton ellemouton reopened this Sep 23, 2024
@Roasbeef
Copy link
Member

cc @davecgh

@davecgh
Copy link
Member

davecgh commented Sep 23, 2024

@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:

  1. Making this a module that requires Go 1.21 will force every single consumer, even those that do not want to use the new logging semantics, to be hoisted forward and themselves require Go 1.21. We no longer use this code, so it wouldn't affect us, but if we did, this change would 100% be a showstopper as a result of that.
  2. Allowing and encouraging ANSI codes (styled output) is just asking for log injection attacks. That is something I personally would never allow in anything even remotely related to cryptocurrency code.

@davecgh
Copy link
Member

davecgh commented Sep 23, 2024

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.

@ellemouton
Copy link
Author

ellemouton commented Sep 24, 2024

Making this a module that requires Go 1.21

The reason that go1.21 was chosen is cause log/slog is only available in that version onwards.

Allowing and encouraging ANSI codes (styled output) is just asking for log injection attacks

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 Style(string)string call-backs instead of specifying anything specific here. Then the risk is totally left up to the caller.

so I'll defer to @Roasbeef to make merge decisions.

Ok cool

@davecgh
Copy link
Member

davecgh commented Sep 24, 2024

The reason that go1.21 was chosen is cause log/slog is only available in that version onwards.

Aye, I understand the reasoning. The code was changed over to define Level as a slog.Level (and some technically incompatible changes to the constant values, but since everything really should be using the constants, that doesn't matter so much) and it also now embeds a new instance of a log/slog.

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 github.com/btcsuite/btcd/wire. It currently only requires Go 1.17 and it's imported by nearly 3000 modules, a huge number of which themselves only require Go 1.17. This would force every single one of those, and anything that uses them to now require Go 1.21. For many projects, that would prevent them from being able to update to the latest versions of the code.

Even something relatively simple like github.com/btcsuite/btcd/btcutil/psbt currently only requires Go 1.17 and is imported by ~100 modules, many of which themselves also only require Go 1.17.

Without looking super closely, I imagine it would be possible to make this compatible with Go 1.17 by implementing the relevant slog interface and not defining anything explicitly in terms of the slog types. That way, it'll still work fine with older Go versions and still be able to interop with the log/slog package in Go 1.21+.

@ellemouton
Copy link
Author

ellemouton commented Sep 30, 2024

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.

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 slog stuff which would anyways require them to run go 1.21.

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

I imagine it would be possible to make this compatible with Go 1.17 by implementing the relevant slog interface and not defining anything explicitly in terms of the slog types. That way, it'll still work fine with older Go versions and still be able to interop with the log/slog package in Go 1.21+.

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 slog.Levels since I think slog requires the use of its Level numbers... so that makes things tricky I think... I'll double check this to make sure though

@Roasbeef
Copy link
Member

Roasbeef commented Oct 2, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants