types/logger: add Context and related helpers
We often need both a log function and a context. We can do this by adding the log function as a context value. This commit adds helper glue to make that easy. It is designed to allow incremental adoption. Updates tailscale/corp#3138 Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>pull/3465/head
parent
f93cf6fa03
commit
deb2f5e793
|
@ -18,6 +18,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Logf is the basic Tailscale logger type: a printf-like func.
|
// Logf is the basic Tailscale logger type: a printf-like func.
|
||||||
|
@ -25,6 +27,27 @@ import (
|
||||||
// Logf functions must be safe for concurrent use.
|
// Logf functions must be safe for concurrent use.
|
||||||
type Logf func(format string, args ...interface{})
|
type Logf func(format string, args ...interface{})
|
||||||
|
|
||||||
|
// A Context is a context.Context that should contain a custom log function, obtainable from FromContext.
|
||||||
|
// If no log function is present, FromContext will return log.Printf.
|
||||||
|
// To construct a Context, use Add
|
||||||
|
type Context context.Context
|
||||||
|
|
||||||
|
type logfKey struct{}
|
||||||
|
|
||||||
|
// FromContext extracts a log function from ctx.
|
||||||
|
func FromContext(ctx Context) Logf {
|
||||||
|
v := ctx.Value(logfKey{})
|
||||||
|
if v == nil {
|
||||||
|
return log.Printf
|
||||||
|
}
|
||||||
|
return v.(Logf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctx constructs a Context from ctx with fn as its custom log function.
|
||||||
|
func Ctx(ctx context.Context, fn Logf) Context {
|
||||||
|
return context.WithValue(ctx, logfKey{}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
// WithPrefix wraps f, prefixing each format with the provided prefix.
|
// WithPrefix wraps f, prefixing each format with the provided prefix.
|
||||||
func WithPrefix(f Logf, prefix string) Logf {
|
func WithPrefix(f Logf, prefix string) Logf {
|
||||||
return func(format string, args ...interface{}) {
|
return func(format string, args ...interface{}) {
|
||||||
|
|
|
@ -7,11 +7,14 @@ package logger
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFuncWriter(t *testing.T) {
|
func TestFuncWriter(t *testing.T) {
|
||||||
|
@ -183,3 +186,28 @@ func TestRateLimitedFnReentrancy(t *testing.T) {
|
||||||
rlogf("boom") // this used to deadlock
|
rlogf("boom") // this used to deadlock
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test that FromContext returns log.Printf when the context has no custom log function.
|
||||||
|
defer log.SetOutput(log.Writer())
|
||||||
|
defer log.SetFlags(log.Flags())
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log.SetOutput(&buf)
|
||||||
|
log.SetFlags(0)
|
||||||
|
logf := FromContext(ctx)
|
||||||
|
logf("a")
|
||||||
|
c.Assert(buf.String(), qt.Equals, "a\n")
|
||||||
|
|
||||||
|
// Test that FromContext and Ctx work together.
|
||||||
|
var called bool
|
||||||
|
markCalled := func(string, ...interface{}) {
|
||||||
|
called = true
|
||||||
|
}
|
||||||
|
ctx = Ctx(ctx, markCalled)
|
||||||
|
logf = FromContext(ctx)
|
||||||
|
logf("a")
|
||||||
|
c.Assert(called, qt.IsTrue)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue