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
Josh Bleecher Snyder 3 years ago committed by Josh Bleecher Snyder
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…
Cancel
Save