Skip to content

log/slog: add slog.DiscardHandler #62005

@flowchartsman

Description

@flowchartsman

Proposal

Add a package-level variable slog.DiscardHandler (type slog.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 the exp 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 a slog.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 where Records 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:

  • there are no guards for a nil receiver on *Logger methods
  • the zero value of Logger is not usable and will panic
  • Loggers initialized with a nil handler will panic
  • there is no way to modify a logger to change its handler or logging level once it's created, making slog.Default() unworkable as a fallback
  • defaultHandler is unexported, so it cannot act as a fallback, and handlers do not provide a WithLevel or WithLeveler 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 no WithHandlerOptions 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 a func DisabledLogger()*Logger convenience method that could act as a default. when the zero values are nil.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Accepted

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions