Skip to content

Commit

Permalink
Accommodate wrapped Logger instances
Browse files Browse the repository at this point in the history
Introduce the "log.Delegate" function to extract a wrapped delegate
Logger instance from a decorating instance, allowing the level-related
functions to find the configuration they need within the outermost
Logger created by one of the leveled factory functions.

Benchmarks demonstrate both run time and allocation reduction for the
disallowed/squelching cases compared to the "experimental_level"
alternate:

name                     old time/op    new time/op    delta
NopBaseline-8               470ns ± 0%     494ns ± 0%   +5.11%
NopDisallowedLevel-8        513ns ± 0%     123ns ± 0%  -76.02%
NopAllowedLevel-8           516ns ± 0%     474ns ± 0%   -8.14%
JSONBaseline-8             2.44µs ± 0%    2.39µs ± 0%   -2.45%
JSONDisallowedLevel-8       515ns ± 0%     123ns ± 0%  -76.12%
JSONAllowedLevel-8         2.48µs ± 0%    2.33µs ± 0%   -5.97%
LogfmtBaseline-8           1.05µs ± 0%    1.03µs ± 0%   -2.19%
LogfmtDisallowedLevel-8     514ns ± 0%     121ns ± 0%  -76.46%
LogfmtAllowedLevel-8       1.13µs ± 0%    1.02µs ± 0%   -9.39%

name                     old alloc/op   new alloc/op   delta
NopBaseline-8                288B ± 0%      288B ± 0%   +0.00%
NopDisallowedLevel-8         288B ± 0%       64B ± 0%  -77.78%
NopAllowedLevel-8            288B ± 0%      288B ± 0%   +0.00%
JSONBaseline-8               968B ± 0%      968B ± 0%   +0.00%
JSONDisallowedLevel-8        288B ± 0%       64B ± 0%  -77.78%
JSONAllowedLevel-8           968B ± 0%      968B ± 0%   +0.00%
LogfmtBaseline-8             288B ± 0%      288B ± 0%   +0.00%
LogfmtDisallowedLevel-8      288B ± 0%       64B ± 0%  -77.78%
LogfmtAllowedLevel-8         288B ± 0%      288B ± 0%   +0.00%

name                     old allocs/op  new allocs/op  delta
NopBaseline-8                9.00 ± 0%      9.00 ± 0%   +0.00%
NopDisallowedLevel-8         9.00 ± 0%      3.00 ± 0%  -66.67%
NopAllowedLevel-8            9.00 ± 0%      9.00 ± 0%   +0.00%
JSONBaseline-8               22.0 ± 0%      22.0 ± 0%   +0.00%
JSONDisallowedLevel-8        9.00 ± 0%      3.00 ± 0%  -66.67%
JSONAllowedLevel-8           22.0 ± 0%      22.0 ± 0%   +0.00%
LogfmtBaseline-8             9.00 ± 0%      9.00 ± 0%   +0.00%
LogfmtDisallowedLevel-8      9.00 ± 0%      3.00 ± 0%  -66.67%
LogfmtAllowedLevel-8         9.00 ± 0%      9.00 ± 0%   +0.00%

The running time increase in the "NopBaseline-8" benchmare is due to
the level.Debug function doing more work, needing to inspect the
supplied log.Logger to see whether it wraps a level restriction that
would preclude preceding with creating a log.Context to record the
level.
  • Loading branch information
Steven E. Harris committed Jan 31, 2017
1 parent b7a6343 commit c6bd2a9
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 98 deletions.
195 changes: 117 additions & 78 deletions log/alt_experimental_level/level.go
Original file line number Diff line number Diff line change
@@ -1,169 +1,208 @@
package level

import (
"github.com/go-kit/kit/log"
)
import "github.com/go-kit/kit/log"

var (
// Alternately, we could use a similarly inert logger that does nothing but
// return a given error value.
nop = log.NewNopLogger()

// Invoking a leveling function with a Logger that neither
// originated from nor wraps a Logger that originated from one of
// the level-filtering factory functions still yields a
// level-stamped Context, as if no filtering is in effect.
defaultLeveler = &debugAndAbove{}
)

type leveler interface {
Debug() log.Logger
Info() log.Logger
Warn() log.Logger
Error() log.Logger
Debug(log.Logger) log.Logger
Info(log.Logger) log.Logger
Warn(log.Logger) log.Logger
Error(log.Logger) log.Logger
}

type leveledLogger struct {
log.Logger
leveler
}

func outermostLevelerOr(logger log.Logger, otherwise leveler) leveler {
for {
switch l := logger.(type) {
case *leveledLogger:
return l.leveler
// Optimize unwrapping a Context by saving a type comparison.
case *log.Context:
logger = l.Delegate()
default:
logger = log.Delegate(logger)
}
if logger == nil {
return otherwise
}
}
}

func outermostLeveler(logger log.Logger) leveler {
return outermostLevelerOr(logger, nil)
}

func outermostEffectiveLeveler(logger log.Logger) leveler {
return outermostLevelerOr(logger, defaultLeveler)
}

// Debug returns a logger ready to emit log records at the "debug"
// level, intended for fine-level detailed tracing information. If the
// supplied logger disallows records at that level, it instead returns
// an inert logger that drops the record.
func Debug(logger log.Logger) log.Logger {
return outermostEffectiveLeveler(logger).Debug(logger)
}

// Info returns a logger ready to emit log records at the "info"
// level, intended for informational messages. If the supplied logger
// disallows records at that level, it instead returns an inert logger
// that drops the record.
func Info(logger log.Logger) log.Logger {
return outermostEffectiveLeveler(logger).Info(logger)
}

// Warn returns a logger ready to emit log records at the "warn"
// level, intended for indicating potential problems. If the supplied
// logger disallows records at that level, it instead returns an inert
// logger that drops the record.
func Warn(logger log.Logger) log.Logger {
return outermostEffectiveLeveler(logger).Warn(logger)
}

// Error returns a logger ready to emit log records at the "error"
// level, intended for indicating serious failures. If the supplied
// logger disallows records at that level, it instead returns an inert
// logger that drops the record.
func Error(logger log.Logger) log.Logger {
return outermostEffectiveLeveler(logger).Error(logger)
}

func withLevel(level string, logger log.Logger) log.Logger {
return log.NewContext(logger).With("level", level)
}

type debugAndAbove struct {
log.Logger
}

func (l debugAndAbove) Debug() log.Logger {
return withLevel("debug", l.Logger)
func (l debugAndAbove) Debug(logger log.Logger) log.Logger {
return withLevel("debug", logger)
}

func (l debugAndAbove) Info() log.Logger {
return withLevel("info", l.Logger)
func (l debugAndAbove) Info(logger log.Logger) log.Logger {
return withLevel("info", logger)
}

func (l debugAndAbove) Warn() log.Logger {
return withLevel("warn", l.Logger)
func (l debugAndAbove) Warn(logger log.Logger) log.Logger {
return withLevel("warn", logger)
}

func (l debugAndAbove) Error() log.Logger {
return withLevel("error", l.Logger)
func (l debugAndAbove) Error(logger log.Logger) log.Logger {
return withLevel("error", logger)
}

type infoAndAbove struct {
debugAndAbove
}

func (infoAndAbove) Debug() log.Logger {
func (infoAndAbove) Debug(logger log.Logger) log.Logger {
return nop
}

type warnAndAbove struct {
infoAndAbove
}

func (warnAndAbove) Info() log.Logger {
func (warnAndAbove) Info(logger log.Logger) log.Logger {
return nop
}

type errorOnly struct {
warnAndAbove
}

func (errorOnly) Warn() log.Logger {
func (errorOnly) Warn(logger log.Logger) log.Logger {
return nop
}

type none struct {
errorOnly
}

func (none) Error() log.Logger {
func (none) Error(logger log.Logger) log.Logger {
return nop
}

// AllowingAll returns a logger allowed to emit log records at all
// levels, unless the supplied logger is already restricted to some
// narrower set of levels, in which case it retains that restriction.
//
// The behavior is equivalent to AllowingDebugAndAbove.
func AllowingAll(logger log.Logger) log.Logger {
return AllowingDebugAndAbove(logger)
}

// AllowingDebugAndAbove returns a logger allowed to emit log records
// at all levels, unless the supplied logger is already restricted to
// some narrower set of levels, in which case it retains that
// restriction.
func AllowingDebugAndAbove(logger log.Logger) log.Logger {
if _, ok := logger.(leveler); ok {
if outermostLeveler(logger) != nil {
return logger
}
return debugAndAbove{logger}
return &leveledLogger{logger, debugAndAbove{}}
}

// AllowingInfoAndAbove returns a logger allowed to emit log records
// at levels "info" and above, dropping "debug"-level records, unless
// the supplied logger is already restricted to some narrower set of
// levels, in which case it retains that restriction.
func AllowingInfoAndAbove(logger log.Logger) log.Logger {
switch l := logger.(type) {
case debugAndAbove:
return infoAndAbove{l}
switch outermostLeveler(logger).(type) {
case infoAndAbove, warnAndAbove, errorOnly, none:
return logger
default:
return infoAndAbove{debugAndAbove{logger}}
return &leveledLogger{logger, infoAndAbove{}}
}
}

// AllowingWarnAndAbove returns a logger allowed to emit log records
// at levels "warn" and above, dropping "debug"- and "info"-level
// records, unless the supplied logger is already restricted to some
// narrower set of levels, in which case it retains that restriction.
func AllowingWarnAndAbove(logger log.Logger) log.Logger {
switch l := logger.(type) {
case debugAndAbove:
return warnAndAbove{infoAndAbove{l}}
case infoAndAbove:
return warnAndAbove{l}
switch outermostLeveler(logger).(type) {
case warnAndAbove, errorOnly, none:
return logger
default:
return warnAndAbove{infoAndAbove{debugAndAbove{logger}}}
return &leveledLogger{logger, warnAndAbove{}}
}
}

// AllowingErrorOnly returns a logger allowed to emit log records only
// at level "error", dropping "debug"-, "info"-, and "warn"-level
// records, unless the supplied logger is already restricted to some
// narrower set of levels, in which case it retains that restriction.
func AllowingErrorOnly(logger log.Logger) log.Logger {
switch l := logger.(type) {
case debugAndAbove:
return errorOnly{warnAndAbove{infoAndAbove{l}}}
case infoAndAbove:
return errorOnly{warnAndAbove{l}}
case warnAndAbove:
return errorOnly{l}
switch outermostLeveler(logger).(type) {
case errorOnly, none:
return logger
default:
return errorOnly{warnAndAbove{infoAndAbove{debugAndAbove{logger}}}}
return &leveledLogger{logger, errorOnly{}}
}
}

// AllowingNone returns a logger that drops log records at all levels.
func AllowingNone(logger log.Logger) log.Logger {
switch l := logger.(type) {
case debugAndAbove:
return none{errorOnly{warnAndAbove{infoAndAbove{l}}}}
case infoAndAbove:
return none{errorOnly{warnAndAbove{l}}}
case warnAndAbove:
return none{errorOnly{l}}
case errorOnly:
return none{l}
switch outermostLeveler(logger).(type) {
case none:
return logger
default:
return none{errorOnly{warnAndAbove{infoAndAbove{debugAndAbove{logger}}}}}
}
}

func Debug(logger log.Logger) log.Logger {
if l, ok := logger.(leveler); ok {
return l.Debug()
return &leveledLogger{logger, none{}}
}
return nop
}

func Info(logger log.Logger) log.Logger {
if l, ok := logger.(leveler); ok {
return l.Info()
}
return nop
}

func Warn(logger log.Logger) log.Logger {
if l, ok := logger.(leveler); ok {
return l.Warn()
}
return nop
}

func Error(logger log.Logger) log.Logger {
if l, ok := logger.(leveler); ok {
return l.Error()
}
return nop
}
Loading

0 comments on commit c6bd2a9

Please sign in to comment.