Add filtered leveled logging and error promotion #252
Adds a couple new options to levels.Levels, including: - FilterLogLevel: specify the minimum log level you want to see actually hit your logs. Anything below this will be discarded in as cheap a fashion as possible. Defaults to Debug, that is, showing everything. - PromoteErrors: if true, a log line that contains an error will be promoted to a higher log level, potentially bypassing the FilterLogLevel. This allows a developer to do a bunch of debug logging of function call results and, in production, not see any of them unless the log line actually has an error. By default this flag is set to false. - ErrorKey: the key in the context where we should search for the existence of an error. If we find a `nil` value at that location, or if that is not a key in the context, we treat it as not having an error. Defaults to "error". - PromoteErrorToLogLevel: the log level that error logs should be promoted to. Promotion only occurs if the log level explicitly specified is lower than this value, so if you log at critical level and include an error, it doesn't get downgraded. Default for this is Error. Creates an ordered enumeration of LogLevels from Debug up to Crit, so users can specify the FilterLogLevel and PromoteErrorToLogLevel options noted above. To detect the presence of errors in the context, I had to add a HasValue utility method on log.Context. I would have preferred that this method not be exported at all, but because levels and log are different packages, it is not visible to be used in levels unless it is exported.
Thanks for the well documented PR!
I don't have time for a full review, but I believe returning the levelCommitted logger from the Levels methods will interact badly with log.DefaultCaller. Can you add test code to check that. See #131 for information about when we changed levels.Levels to work correctly with log.DefaultCaller. We should try not to cause a regression in this area.
I will review in more detail when I get the time.
@ChrisHines Your intuition is correct, it messes up
I'm racking my brain trying to come up with a workaround for this that doesn't involve tacking on more junk to the public API of
We can jettison the error promotion, and this becomes super easy: in
I will consider it more this weekend but I'd love it if you had some ideas.
Possibly the cleanest solution I can come up with is to add some kind of
The hook would be of the signature
Which would allow consumer packages to add a hook that checks and potentially replaces the keyvals that are about to be logged, and bails out on an error, which could include "don't log this because of the log level."
But it feels clunky and complicated and overkill to support something that isn't a prominent use case.
@bishoprook I agree that
The idea you mentioned in #250 to avoid evaluation of expensive
logger := log.NewLogfmtLogger(w) logger = LazyValueLogger(logger) logger = levels.NewLevelFilterLogger(logger, logLevel) logger = levels.NewEscalateErrLogger(logger) lvllog := levels.New(logger).With("t", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
In this way
I am not suggesting we add