mlog: refactor to use Clone/Set instead of With pattern, implement CtxSetAll
This commit is contained in:
parent
5bd0790f6e
commit
20c6f95091
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
74
mlog/ctx.go
74
mlog/ctx.go
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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"),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
70
mlog/mlog.go
70
mlog/mlog.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user