Просмотр исходного кода

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 <[email protected]>
Josh Bleecher Snyder 4 лет назад
Родитель
Сommit
deb2f5e793
2 измененных файлов с 51 добавлено и 0 удалено
  1. 23 0
      types/logger/logger.go
  2. 28 0
      types/logger/logger_test.go

+ 23 - 0
types/logger/logger.go

@@ -18,6 +18,8 @@ import (
 	"strings"
 	"sync"
 	"time"
+
+	"context"
 )
 
 // Logf is the basic Tailscale logger type: a printf-like func.
@@ -25,6 +27,27 @@ import (
 // Logf functions must be safe for concurrent use.
 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.
 func WithPrefix(f Logf, prefix string) Logf {
 	return func(format string, args ...interface{}) {

+ 28 - 0
types/logger/logger_test.go

@@ -7,11 +7,14 @@ package logger
 import (
 	"bufio"
 	"bytes"
+	"context"
 	"fmt"
 	"log"
 	"sync"
 	"testing"
 	"time"
+
+	qt "github.com/frankban/quicktest"
 )
 
 func TestFuncWriter(t *testing.T) {
@@ -183,3 +186,28 @@ func TestRateLimitedFnReentrancy(t *testing.T) {
 		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)
+}