-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
log/slog: add slog.DiscardHandler
#62005
Comments
log.Logger has an optimization where it detects when the writer is io.Discard and does no allocations. Maybe slog should do the same thing. |
(CC @jba) |
I'm not sure this would work in this case, since this would be an implementation at the handler level, which would mean it would only apply to the standard lib JSON and KV handlers. It would require the user to take an if config.LoggingHandler == nil {
//default: this JSONHandler does nothing
config.LoggingHandler = slog.NewJSONHandler(io.Discard, nil)
}
// need the handler since slog.New(nil) will panic
logger := slog.New(config.LoggingHandler) Which still feels pretty awkward. |
I wouldn't want to make a nil I think a |
Concur. Either handler name works for me and, while I like This would make the fallback story for logging injection something like: type ClientConfig struct {
Logger: *slog.Logger
}
func NewClient(config ClientConfig) (*Client, error) {
if config.Logger == nil {
config.Logger = slog.New(slog.DiscardHandler)
}
//...
return &Client{
logger: config.Logger,
}, nil
} Which isn't too bad, and makes a |
The only reason to prefer |
The proposal here is to add It's ready for the proposal committee. /cc @rsc |
I want to cast a vote for this being added. I found myself writing the following today in a test suite because the portion of my application I was testing relies on having a logger instance, but I don't care about logs during the tests: package main
import "log/slog"
type NullWriter struct{}
func (NullWriter) Write([]byte) (int, error) { return 0, nil }
func main() {
logger := NullLogger()
logger.Info("foo")
}
func NullLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(NullWriter{}, nil))
} Reading through this thread I learned about |
@flowchartsman, can you edit your top post to refer to the actual proposal (#62005 (comment))? |
@flowchartsman ... or just edit it to be the proposal. |
slog.DiscardHandler
Done! |
Seeing this is not on track for 1.22, what should package authors do in the meantime, in case they want to provide structured logging as an optional feature?
I'd assume that a gradual ecosystem-wide migration to |
Added https://go-review.googlesource.com/c/go/+/548335 which uses @bcmills example to demonstrate how users can solve this, for now. |
Change https://go.dev/cl/548335 mentions this issue: |
Change https://go.dev/cl/547956 mentions this issue: |
I've gone ahead and submitted change 547956, just to get the ball rolling. It required I rename the internal It seemed appropriate to make a whole file example demonstrating its usage in type initialization, since the initial purpose of the proposal was to have a sensible fallback handler that types which consume a |
Hey, I missed this issue while writing my own #69227. I closed it. As a workaround, you will see in my issue I used |
This proposal has been added to the active column of the proposals project |
In the log package, we talked about adding a special "discard" mode and instead we recognized log.SetOutput(io.Discard). Thoughts? |
If we do this for Those are the built-in handlers. Would people expect the same behavior from third-party handlers? It's already hard enough to write a handler. I think we'll end up with wordy, brittle magic formula. A single new exported symbol is a small price to pay to avoid that. |
Based on the discussion above, this proposal seems like a likely accept. The proposal is: package slog
// DiscardHandler dicards all log output.
var DiscardHandler Handler |
For the folks paranoid about security out there (like me), is it worth considering adding a constructor to get a clean discard handler constructed from the same unexported type I assume is being created to back func NewDiscardHandler() Handler |
If it's good enough for Less glibly, the threat model of Go does not include having access to the code of the program. If it did, many things about the language would be different. We do understand that writable globals are not great, but we are not going to start addressing that in |
No change in consensus, so accepted. 🎉 The proposal is: package slog
// DiscardHandler dicards all log output.
var DiscardHandler Handler |
slog.DiscardHandler
slog.DiscardHandler
Está que es |
Is anyone working on a CL for this? I can possibly tackle it if not. |
https://go.dev/cl/547956. But the author hasn't touched it since December 2023, so at this point you can claim it. |
This adds a package-level variable, slog.Discard, which is a slog.Handler which performs no output. This serves a similar purpose to io.Discard. Fixes golang#62005
Change https://go.dev/cl/626486 mentions this issue: |
Sorry I haven't had the time to keep up with this; had a baby and have just now gotten to the point where I can follow up on things. Great to see it finally went through though! I can revisit the existing PR, or we can accept Carlana's based on the feedback mine received. I'm good either way. If there's still desire to have me revisit it, I can get to it this week. |
Carlana's got it. You can abandon yours. And congratulations! |
Thank you @earthboundkid. |
In our tests this is what we do
|
Proposal
Add a package-level variable
slog.DiscardHandler
(typeslog.Handler
) that will discard all log output.link to comment
Rationale
I have been happily busying myself integrating
log/slog
into old packages and replacing theexp
import in new ones, however I've found myself implementing the same "nop handler" again and again in packages with optional logging APIs.A common pattern in logging injection is to consume an interface or a pointer to a logging type to allow the dependency to use a derived logger with some field marking the messages as originating from that component, enabling both a standard format as well as per-component level thresholds to keep the output sane. For packages that support
slog
, this will likely often end up being a*slog.Logger
or aslog.Handler
, however I've also had to develop a series of log adapters for common dependencies which use their own concrete types, interfaces and callbacks.In both of these cases, it is helpful to have a default fallback which logs nothing, so that the packages can be tested or used on their own without needing boilerplate guard clauses on every logging method to check for nil values. It's also helpful for less well-behaved dependencies which create their own loggers if one is not provided, some of whom are exceptionally chatty for no good reason. Packages like this are sadly far too common, and we're likely to continue seeing them, so a way to bring some sanity to the process would be very useful.
While it's true that checks for the presence of logging can be reduced by funneling all handler interaction through a single point, this introduces another sort of boilerplate for
slog
whereRecord
s need to be created manually, adjusting their PC to account for the extra frame as seen in the wrapping example. This is a low-level and overly-manual process just to enable conditional logging.Currently, there doesn't seem to be a great answer for this in
slog
:*Logger
methodsLogger
is not usable and will panicLogger
s initialized with anil
handler will panicslog.Default()
unworkable as a fallbackdefaultHandler
is unexported, so it cannot act as a fallback, and handlers do not provide aWithLevel
orWithLeveler
method, so it wouldn't work as a fallback even if it were exported.HandlerOptions
, which allows specifying a minimum level, only applies to the built-in handlers, and there is noWithHandlerOptions
that would allow derived handlers to adjust their logging level using this type.Leaving aside the arguments on the merits of logging in packages, this pattern is nonetheless common enough that it would probably be useful to have a
type DisabledHandler struct{}
or even afunc DisabledLogger()*Logger
convenience method that could act as a default. when the zero values arenil
.The text was updated successfully, but these errors were encountered: