-
Notifications
You must be signed in to change notification settings - Fork 81
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
contextual logging for slog #234
Comments
Option 3: None of the above. Developers who want to support contextual logging can do so with the If this is the consensus, then we can do a few things in
|
Given that we already have context support for logr users, can we more clearly state the problem we are trying to solve? Is it: a) to implement context support for slog users in general Given that Go itself does not define a standard way to get a slog.Logger into and out of a Context, code which knows slog will have to either use the slog default Logger, pass Do we think slog-context has or might have critical mass for this? Can we describe the desired UX for whomever is going to consume whatever it is we are conceptualizing? I feel like there are 3 categories of users:
Do we feel like there's going to be a lot of back-and-forth between domains (1) and (2) or is it more like "this lib uses logr, so let me convert my slog to logr and stick it in context" ? |
Let's start with a problem statement: different packages defining their own keys for passing a logger (whether it's My option 2 matches your point a - solve this for all slog users by trying to make I'm not sure about your point b - is that "implement Point c is what Point d might match my option 3: advocate to use |
I agree with a caveat. I'm not sure I buy that "passing a logger" is a generic statement. Surely we don't intend this to support any arbitrary logging API with the same key, with a matrix of transformations, do we? As fun as that sounds, it doesn't seem appropriate to store a So what are the reasonable bounds?
From a strict POV, I don't think we have a "problem", per se. Both slog-context and logr work just fine with their own logger types. But, because logr and slog are so closely aligned, we're trying to do better, right? It's not exactly "arbitrary" but "a select list of implementations which choose to coordinate".
It feels like overreaching, to me. If I am a slog user, why is logr involved? If I were building slog-context, why should I take a dep on logr?
I mistyped - I meant "to implement /context/ support for users who store slog in Context via some external lib"
slog-context includes a lot more than that, though most of it is just wrapping and re-presenting a similar interface.
I think, realistically, this is backwards. IMO So if we think Simple mode:
Fancy mode (hand-waving):
Extra points for adding a "cost" per conversion function and doing a shortest-path conversion. But realisitically, how many of these will we have in a single binary? I see https://pkg.go.dev/github.com/juju/zaputil/zapctx and logrus and zerolog all do things a little differently. |
True. But
The main advantage would be that a package using slog-context would work seamlessly in a Kubernetes binary that uses logr and klog's text logger or zapr. Without the integration, that binary can set the default slog logger (PR pending) but log entries then lack the context values.
If the call sequence is
That would work, but is more complex than doing it for I'm still not sure whether it's worth it. Option 3 seems perfectly fine to me, so it all depends on what other developers will pick for their packages. |
Ping @veqryn - we need your input. |
I feel like there's got to be some way, but I don't have time to prove it right now. If we get a logr, check to see if the logr.sloghandler == context's slog handler, and if not reconstruct the logr. Writing it out, it starts to feel "too clever to be useful" |
#213 is a prior PR for option 1. I am going to close it while we figure out what to do in this issue. |
Perhaps I should have led with the inverse situation: Kubernetes packages like |
Thank you for including me @pohly So, just to recap:
And let me explore what integrating the two libraries looks like right now, without any changes to either library: Primarily using package main
import (
"context"
"log"
"log/slog"
"os"
"github.com/go-logr/logr"
"github.com/go-logr/logr/slogr"
"github.com/go-logr/stdr"
slogcontext "github.com/veqryn/slog-context"
)
func main() {
logger := stdr.New(log.Default()) // Create a logr.Logger
ctx := logr.NewContext(context.Background(), logger) // Set it in the context
slogger := slog.New(slogr.NewSlogHandler(logger)) // Convert logr.Logger to slog.Logger
slog.SetDefault(slogger) // Set as default so that libraries that just use the default package level slog will still output the way we like
ctx = slogcontext.ToCtx(ctx, slogger) // Store the slog.Logger in the context too, so that libraries using slog.Logger from context output the way we like
doSomething(ctx)
}
func doSomething(ctx context.Context) {
logger := logr.FromContextOrDiscard(ctx).WithValues("myKey", "myVal") // Retrieve from context and store some attributes
ctx = logr.NewContext(context.Background(), logger) // Set the new logr in the context, again
ctx = slogcontext.ToCtx(ctx, slog.New(slogr.NewSlogHandler(logger))) // Set the new slog in the context, again
logger.Info("calling into library...")
someLibraryWeHaveNoControlOver(ctx)
}
func someLibraryWeHaveNoControlOver(ctx context.Context) {
slogcontext.Info(ctx, "in the library...")
} And the primarily using func main2() {
logger := slog.New(slogcontext.NewHandler(slog.NewJSONHandler(os.Stdout, nil), nil)) // Create a slog.Logger
slog.SetDefault(logger) // Set as default
ctx := slogcontext.ToCtx(context.Background(), logger) // Store in the context
// Convert slog.Logger to logr.Logger, and store the logr.Logger in the context too, so that libraries using logr.Logger from context output the way we like
ctx = logr.NewContext(ctx, slogr.NewLogr(logger.Handler()))
doSomething2(ctx)
}
func doSomething2(ctx context.Context) {
ctx = slogcontext.With(ctx, "myKey", "myVal") // Retrieve from context and store some attributes, and store back in the context
ctx = logr.NewContext(ctx, slogr.NewLogr(slogcontext.Logger(ctx).Handler())) // Convert the new slog to logr and set in the context
slogcontext.Info(ctx, "calling into library...")
someLibraryWeHaveNoControlOver2(ctx)
}
func someLibraryWeHaveNoControlOver2(ctx context.Context) {
logr.FromContextOrDiscard(ctx).Info("in the library...")
} We could cut down on some boilerplate with a few helper methods. For the primarily logr workflow: func doSomething(ctx context.Context) {
logger := logr.FromContextOrDiscard(ctx).WithValues("myKey", "myVal")
ctx = ContextWithLogrAndSlogr(ctx, logger)
logger.Info("calling into library...")
someLibraryWeHaveNoControlOver(ctx)
}
func ContextWithLogrAndSlogr(ctx context.Context, logger logr.Logger) context.Context {
ctx = logr.NewContext(context.Background(), logger)
return slogcontext.ToCtx(ctx, slog.New(slogr.NewSlogHandler(logger)))
} For the primarily slog-context workflow: func doSomething2(ctx context.Context) {
ctx = ContextWithLogr(slogcontext.With(ctx, "myKey", "myVal"))
slogcontext.Info(ctx, "calling into library...")
someLibraryWeHaveNoControlOver2(ctx)
}
func ContextWithLogr(ctx context.Context) context.Context {
return logr.NewContext(ctx, slogr.NewLogr(slogcontext.Logger(ctx).Handler()))
} It could make sense that those helper methods live in logr, with better names and inlining some of the function calls. I can also see a use for both of @thockin's "simple mode" ideas. The "fancy" versions might be overkill in a world where devs are hopefully moving towards slog for most everything. |
@veqryn: the main drawback of
The other drawback is that
The first one doesn't work (extracting a logger cannot know which one to use). That leaves "Make logr.NewContext() store both our logr key and (if applicable) the slog-context key." Isn't that the same as your
I agree, it's overkill. One argument against supporting logrus and zap the same way as slog and logr is that logrus and zap are logging implementations. If someone uses those, they decide to not be interoperable. Both logr and slog are abstractions and are interoperable with each other. Regarding "slog for most everything", what do you think about recommending the usage of |
I gave some thought to the above, and created this PR: veqryn/slog-context#1 I haven't added any documentation yet, and am open to renaming the methods. Just want to get a concrete idea out there right now, for us to talk about.
I think that slog will gradually take over the ecosystem as the only structured logging interface, by virtue of being in the standard library. |
I agree that this can work, but it has the drawbacks that I mentioned earlier:
Perhaps I should put the idea that I had about converting on demand into code... stay tuned.
That may be the perception, but I think it's wrong: My hope was that in slog-context, we could point them in a better direction. |
See second commit in #237 |
@thockin: that PR brings us back to my original implementation where everything is in the main package, which is required to avoid circular package dependencies. The |
@pohly I can't comment on the packaging changes in #237, but I like the basic type switch for I think once you have a release with this, I can support it in slog-context and also link to it from my readme. |
So we cross-reference both efforts and then let developers decide which API they want to use, with an explanation of the pros and cons. That sounds like a plan to me. |
I agree. I know we think slog is "obviously" missing context support and other things, but really I don't think it's bad enough to prevent adoption. IMO, pushing logr as an interface is pretty much a dead-end. I can't say whether slog-context will be the dominant out-of-core helper lib. If not for Kubernetes, would we be having this discussion? I'll look at the PR, but here's something I don't have confidence in: Thru the |
This is what I don't understand. On the one hand you are saying that logr is a dead-end because "it's not slog", but then so would be every other high-level API for Perhaps we need to distinguish more clearly what we are talking about: I see a future for
And that's a good thing, right? 😁 |
Yes. |
In some sense, logr is a victim of its own success. Many aspects of logr made it into slog - so much so that the differences between
I suspect this is going to be a decision that slog folks will come to regret. It seems so painfully obvious that wrappers are sometimes needed, and their answer is basically "too bad, so sad, go do it yourself". I don't know if this alone will make people choose logr, though. I suspect that eventually slog will HAVE to fix this. Same as having context support.
Two answers.
|
FWIW, if slog ever supports its use with context, I will happily call that a win. |
Some of their design decisions will make it hard to fix or more expensive (like using a pointer to an allocated struct instead of a small value). I think the main reason for using the logr API is context support. It's more efficient if packages supporting that approach use the same logger instance and thus API.
Hopefully not again. I've made enough noise about logr's slog support (added on https://github.com/golang/go/wiki/Resources-for-slog, posted in the issue) that other developers should be aware. Providing yet another package for "store slog.Logger in context" when there are already two (one which is in use, one which focuses on slog) seems stupid and hopefully won't get used even if someone publishes it.
We (or I, if you are loosing interest) will have to maintain it because Kubernetes depends on it. Once we are done with hashing out details of the slog interaction, I don't expect much further work. |
Or we convert to slog, like everyone else (my hypothesis)? Basically, I'm not convinced that there won't be more things that slog changes that we have to keep pace with. I'm not deeply against this today, I really just want to understand how we see it playing out over the longer term. So, is it a fair summary that our position is:
@veqryn are you good with that? |
That's something that we can discuss when slog reaches feature parity. Until then, it's a lot of code churn with no real benefit and some relevant drawbacks.
👍 |
👍 |
As far as I am concerned, we are done with this. |
@pohly what is the timeline for a new release? |
PTAL at #252 |
v1.4.0 is tagged. |
|
The Golang community decided against introducing an API for passing a
slog.Logger
orslog.Handler
via acontext.Context
.logr
supports this withNewContext
andFromContext[orDiscard]
forlogr.Logger
. https://github.com/veqryn/slog-context supports this forslog.Logger
. Both are not interoperable.This issue is about exploring whether further work is needed in this area and if so, what that should be.
Here are some possibilities:
Add
slogr.NewContext(ctx context.Context, logger *slog.Logger)
andslogr.FromContext(ctx context.Context) *slog.Logger
on top of the correspondinglogr
functions, with conversion to and fromlogr.Logger
. Currently there's only documentation for how to write such functions. Drawback: additional allocations, in particular on each retrieval.Move the conversion code from
slogr
and the context key definition intologr/internal
. The value for that key can be either alogr.Logger
or a* slog.Logger
. Thenslogr.NewContext
can store its parameter under that context key andslogr.FromContext
can retrieved it without allocations. Bothslogr.FromContext
andlogr.FromContext
would have to do type checks and convert if needed. Drawback: the mainlogr
package depends on slog when built with Go > 1.21 (currently it doesn't).If we do option 2, then https://github.com/veqryn/slog-context could use slogr under the hood to set and retrieve a logger instance and code using one or the other package would become interoperable.
Note that option 2 suggests to store and retrieve a
slog.Logger
because that avoids the memory allocation. Storing aslog.Handler
implies that code must construct aslog.Logger
when it wants to use one. We could also have functions for storing and retrieving aslog.Handler
as a third alternative.cc @veqryn @antichris
The text was updated successfully, but these errors were encountered: