mlog: refactor to use Clone/Set instead of With pattern, implement CtxSetAll

This commit is contained in:
Brian Picciano 2019-01-30 16:06:24 -05:00
parent 5bd0790f6e
commit 20c6f95091
9 changed files with 123 additions and 78 deletions

View File

@ -60,7 +60,9 @@ func main() {
}) })
authHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authLogger := logger.WithKV(mlog.CtxKV(r.Context())) // TODO mlog.FromHTTP?
authLogger := logger.Clone()
authLogger.SetKV(mlog.CtxKV(r.Context()))
unauthorized := func() { unauthorized := func() {
w.Header().Add("WWW-Authenticate", "Basic") w.Header().Add("WWW-Authenticate", "Basic")

View File

@ -28,7 +28,9 @@ func MListenAndServe(ctx mctx.Context, h http.Handler) *http.Server {
ctx = mctx.ChildOf(ctx, "http") ctx = mctx.ChildOf(ctx, "http")
listener := mnet.MListen(ctx, "tcp", "") listener := mnet.MListen(ctx, "tcp", "")
listener.NoCloseOnStop = true // http.Server.Shutdown will do this listener.NoCloseOnStop = true // http.Server.Shutdown will do this
logger := mlog.From(ctx).WithKV(listener)
logger := mlog.From(ctx)
logger.SetKV(listener)
srv := http.Server{Handler: h} srv := http.Server{Handler: h}
mrun.OnStart(ctx, func(mctx.Context) error { mrun.OnStart(ctx, func(mctx.Context) error {

View File

@ -15,8 +15,11 @@ import (
) )
func TestMListenAndServe(t *T) { func TestMListenAndServe(t *T) {
// TODO mtest.NewCtx
ctx := mctx.ChildOf(mctx.New(), "test") ctx := mctx.ChildOf(mctx.New(), "test")
mlog.CtxSet(ctx, mlog.From(ctx).WithMaxLevel(mlog.DebugLevel)) logger := mlog.From(ctx)
logger.SetMaxLevel(mlog.DebugLevel)
mlog.CtxSet(ctx, logger)
h := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { h := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
io.Copy(rw, r.Body) io.Copy(rw, r.Body)

View File

@ -21,6 +21,24 @@ func CtxSet(ctx mctx.Context, l *Logger) {
}) })
} }
// CtxSetAll traverses the given Context's children, breadth-first. It calls the
// callback for each Context which has a Logger set on it, replacing that Logger
// with the returned one.
//
// This is useful, for example, when changing the log level of all Loggers in an
// app.
func CtxSetAll(ctx mctx.Context, callback func(mctx.Context, *Logger) *Logger) {
mctx.BreadthFirstVisit(ctx, func(ctx mctx.Context) bool {
mctx.GetSetMutableValue(ctx, false, ctxKey(0), func(i interface{}) interface{} {
if i == nil {
return nil
}
return callback(ctx, i.(*Logger))
})
return true
})
}
type ctxPathStringer struct { type ctxPathStringer struct {
str Stringer str Stringer
pathStr string pathStr string
@ -39,39 +57,31 @@ func (cp ctxPathStringer) String() string {
// Context parent's Logger. If the parent hasn't had From called on it its // Context parent's Logger. If the parent hasn't had From called on it its
// parent will be queried instead, and so on. // parent will be queried instead, and so on.
func From(ctx mctx.Context) *Logger { func From(ctx mctx.Context) *Logger {
var from func(mctx.Context) *Logger return mctx.GetSetMutableValue(ctx, true, ctxKey(0), func(interface{}) interface{} {
from = func(ctx mctx.Context) *Logger { ctxPath := mctx.Path(ctx)
return mctx.GetSetMutableValue(ctx, true, ctxKey(0), func(interface{}) interface{} { if len(ctxPath) == 0 {
// we're at the root node and it doesn't have a Logger set, use
// the default
return NewLogger()
}
ctxPath := mctx.Path(ctx) // set up child's logger
if len(ctxPath) == 0 { pathStr := "/" + path.Join(ctxPath...)
// we're at the root node and it doesn't have a Logger set, use
// the default
return NewLogger()
}
// set up child's logger parentL := From(mctx.Parent(ctx))
pathStr := "/" parentH := parentL.Handler()
if len(ctxPath) > 0 { thisL := parentL.Clone()
pathStr += path.Join(ctxPath...) thisL.SetHandler(func(msg Message) error {
} // if the Description is already a ctxPathStringer it can be
// assumed this Message was passed in from a child Logger.
parentL := from(mctx.Parent(ctx)) if _, ok := msg.Description.(ctxPathStringer); !ok {
prevH := parentL.Handler() msg.Description = ctxPathStringer{
return parentL.WithHandler(func(msg Message) error { str: msg.Description,
// if the Description is already a ctxPathStringer it can be pathStr: pathStr,
// assumed this Message was passed in from a child Logger.
if _, ok := msg.Description.(ctxPathStringer); !ok {
msg.Description = ctxPathStringer{
str: msg.Description,
pathStr: pathStr,
}
} }
return prevH(msg) }
}) return parentH(msg)
})
}).(*Logger) return thisL
} }).(*Logger)
return from(ctx)
} }

View File

@ -14,7 +14,8 @@ func TestContextStuff(t *T) {
ctx1b := mctx.ChildOf(ctx1, "b") ctx1b := mctx.ChildOf(ctx1, "b")
var descrs []string var descrs []string
l := NewLogger().WithHandler(func(msg Message) error { l := NewLogger()
l.SetHandler(func(msg Message) error {
descrs = append(descrs, msg.Description.String()) descrs = append(descrs, msg.Description.String())
return nil return nil
}) })
@ -23,6 +24,7 @@ func TestContextStuff(t *T) {
From(ctx1a).Info("ctx1a") From(ctx1a).Info("ctx1a")
From(ctx1).Info("ctx1") From(ctx1).Info("ctx1")
From(ctx).Info("ctx") From(ctx).Info("ctx")
From(ctx1b).Debug("ctx1b (shouldn't show up)")
From(ctx1b).Info("ctx1b") From(ctx1b).Info("ctx1b")
ctx2 := mctx.ChildOf(ctx, "2") ctx2 := mctx.ChildOf(ctx, "2")
@ -36,4 +38,23 @@ func TestContextStuff(t *T) {
massert.Equal(descrs[3], "(/1/b) ctx1b"), massert.Equal(descrs[3], "(/1/b) ctx1b"),
massert.Equal(descrs[4], "(/2) ctx2"), massert.Equal(descrs[4], "(/2) ctx2"),
)) ))
// use CtxSetAll to change all MaxLevels in-place
ctx2L := From(ctx2)
CtxSetAll(ctx, func(_ mctx.Context, l *Logger) *Logger {
l.SetMaxLevel(DebugLevel)
return l
})
descrs = descrs[:0]
From(ctx).Info("ctx")
From(ctx).Debug("ctx debug")
ctx2L.Debug("ctx2L debug")
massert.Fatal(t, massert.All(
massert.Len(descrs, 3),
massert.Equal(descrs[0], "ctx"),
massert.Equal(descrs[1], "ctx debug"),
massert.Equal(descrs[2], "(/2) ctx2L debug"),
))
} }

View File

@ -264,9 +264,10 @@ func DefaultHandler() Handler {
} }
// Logger directs Messages to an internal Handler and provides convenient // Logger directs Messages to an internal Handler and provides convenient
// methods for creating and modifying its own behavior. // methods for creating and modifying its own behavior. All methods are
// thread-safe.
type Logger struct { type Logger struct {
w io.Writer l *sync.RWMutex
h Handler h Handler
maxLevel uint maxLevel uint
kv KVer kv KVer
@ -278,54 +279,50 @@ type Logger struct {
// to the DefaultHandler. // to the DefaultHandler.
func NewLogger() *Logger { func NewLogger() *Logger {
return &Logger{ return &Logger{
l: new(sync.RWMutex),
h: DefaultHandler(), h: DefaultHandler(),
maxLevel: InfoLevel.Uint(), maxLevel: InfoLevel.Uint(),
} }
} }
// Handler returns the Handler currently in use by the Logger. // Clone returns an identical instance of the Logger which can be modified
func (l *Logger) Handler() Handler { // independently of the original.
return l.h func (l *Logger) Clone() *Logger {
}
func (l *Logger) clone() *Logger {
l2 := *l l2 := *l
l2.l = new(sync.RWMutex)
return &l2 return &l2
} }
// WithMaxLevelUint returns a copy of the Logger which will not log any messages // SetMaxLevel sets the Logger to not log any messages with a higher Level.Uint
// with a higher Level.Uint value than the one given. The returned Logger is // value than of the one given.
// identical in all other aspects. func (l *Logger) SetMaxLevel(lvl Level) {
func (l *Logger) WithMaxLevelUint(i uint) *Logger { l.l.Lock()
l = l.clone() defer l.l.Unlock()
l.maxLevel = i l.maxLevel = lvl.Uint()
return l
} }
// WithMaxLevel returns a copy of the Logger which will not log any messages // SetHandler sets the Logger to use the given Handler in order to process
// with a higher Level.Uint value than of the Level given. The returned Logger // Messages.
// is identical in all other aspects. func (l *Logger) SetHandler(h Handler) {
func (l *Logger) WithMaxLevel(lvl Level) *Logger { l.l.Lock()
return l.WithMaxLevelUint(lvl.Uint()) defer l.l.Unlock()
}
// WithHandler returns a copy of the Logger which will use the given Handler in
// order to process messages. The returned Logger is identical in all other
// aspects.
func (l *Logger) WithHandler(h Handler) *Logger {
l = l.clone()
l.h = h l.h = h
return l
} }
// WithKV returns a copy of the Logger which will use the merging of the given // Handler returns the Handler currently in use by the Logger.
// KVers as a base KVer for all log messages. If the original Logger already had func (l *Logger) Handler() Handler {
// a base KVer (via a previous WithKV call) then this set will be merged onto l.l.RLock()
// that one. defer l.l.RUnlock()
func (l *Logger) WithKV(kvs ...KVer) *Logger { return l.h
l = l.clone() }
// SetKV sets the Logger to use the merging of the given KVers as a base KVer
// for all Messages. If the Logger already had a base KVer (via a previous SetKV
// call) then this set will be merged onto that one.
func (l *Logger) SetKV(kvs ...KVer) {
l.l.Lock()
defer l.l.Unlock()
l.kv = MergeInto(l.kv, kvs...) l.kv = MergeInto(l.kv, kvs...)
return l
} }
// Log can be used to manually log a message of some custom defined Level. // Log can be used to manually log a message of some custom defined Level.
@ -333,6 +330,9 @@ func (l *Logger) WithKV(kvs ...KVer) *Logger {
// If the Level is a fatal (Uint() == 0) then calling this will never return, // If the Level is a fatal (Uint() == 0) then calling this will never return,
// and the process will have os.Exit(1) called. // and the process will have os.Exit(1) called.
func (l *Logger) Log(msg Message) { func (l *Logger) Log(msg Message) {
l.l.RLock()
defer l.l.RUnlock()
if l.maxLevel < msg.Level.Uint() { if l.maxLevel < msg.Level.Uint() {
return return
} }

View File

@ -41,7 +41,8 @@ func TestLogger(t *T) {
return DefaultFormat(buf, msg) return DefaultFormat(buf, msg)
} }
l := NewLogger().WithHandler(h) l := NewLogger()
l.SetHandler(h)
l.testMsgWrittenCh = make(chan struct{}, 10) l.testMsgWrittenCh = make(chan struct{}, 10)
assertOut := func(expected string) massert.Assertion { assertOut := func(expected string) massert.Assertion {
@ -68,7 +69,7 @@ func TestLogger(t *T) {
assertOut("~ ERROR -- buz\n"), assertOut("~ ERROR -- buz\n"),
)) ))
l = l.WithMaxLevel(WarnLevel) l.SetMaxLevel(WarnLevel)
l.Debug("foo") l.Debug("foo")
l.Info("bar") l.Info("bar")
l.Warn("baz") l.Warn("baz")
@ -78,8 +79,9 @@ func TestLogger(t *T) {
assertOut("~ ERROR -- buz -- a=\"b\"\n"), assertOut("~ ERROR -- buz -- a=\"b\"\n"),
)) ))
l2 := l.WithMaxLevel(InfoLevel) l2 := l.Clone()
l2 = l2.WithHandler(func(msg Message) error { l2.SetMaxLevel(InfoLevel)
l2.SetHandler(func(msg Message) error {
msg.Description = String(strings.ToUpper(msg.Description.String())) msg.Description = String(strings.ToUpper(msg.Description.String()))
return h(msg) return h(msg)
}) })
@ -92,7 +94,8 @@ func TestLogger(t *T) {
assertOut("~ ERROR -- buz\n"), assertOut("~ ERROR -- buz\n"),
)) ))
l3 := l2.WithKV(KV{"a": 1}) l3 := l2.Clone()
l3.SetKV(KV{"a": 1})
l3.Info("foo", KV{"b": 2}) l3.Info("foo", KV{"b": 2})
l3.Info("bar", KV{"a": 2, "b": 3}) l3.Info("bar", KV{"a": 2, "b": 3})
massert.Fatal(t, massert.All( massert.Fatal(t, massert.All(

View File

@ -38,7 +38,8 @@ func MListen(ctx mctx.Context, network, defaultAddr string) *MListener {
addr := mcfg.String(ctx, "listen-addr", defaultAddr, network+" address to listen on in format [host]:port. If port is 0 then a random one will be chosen") addr := mcfg.String(ctx, "listen-addr", defaultAddr, network+" address to listen on in format [host]:port. If port is 0 then a random one will be chosen")
l := new(MListener) l := new(MListener)
l.log = mlog.From(ctx).WithKV(l) l.log = mlog.From(ctx)
l.log.SetKV(l)
mrun.OnStart(ctx, func(mctx.Context) error { mrun.OnStart(ctx, func(mctx.Context) error {
var err error var err error

View File

@ -39,8 +39,11 @@ func TestIsReservedIP(t *T) {
} }
func TestMListen(t *T) { func TestMListen(t *T) {
// TODO mtest.NewCtx
ctx := mctx.ChildOf(mctx.New(), "test") ctx := mctx.ChildOf(mctx.New(), "test")
mlog.CtxSet(ctx, mlog.From(ctx).WithMaxLevel(mlog.DebugLevel)) logger := mlog.From(ctx)
logger.SetMaxLevel(mlog.DebugLevel)
mlog.CtxSet(ctx, logger)
l := MListen(ctx, "", "") l := MListen(ctx, "", "")
if err := mcfg.Populate(ctx, nil); err != nil { if err := mcfg.Populate(ctx, nil); err != nil {