glog: add context variants to most log functions

We export this new API to make the internal and external versions
identical.

The context is currently plumbed through to the internal/logsink
package, but effectively discarded there.

cl/560684897 (google-internal)
cl/579771826 (google-internal)
diff --git a/glog.go b/glog.go
index dd0ed10..8c00e73 100644
--- a/glog.go
+++ b/glog.go
@@ -15,8 +15,26 @@
 // limitations under the License.
 
 // Package glog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup.
-// It provides functions Info, Warning, Error, Fatal, plus formatting variants such as
-// Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags.
+// It provides functions that have a name matched by regex:
+//
+//	(Info|Warning|Error|Fatal)(Context)?(Depth)?(f)?
+//
+// If Context is present, function takes context.Context argument. The
+// context is used to pass through the Trace Context to log sinks that can make use
+// of it.
+// It is recommended to use the context variant of the functions over the non-context
+// variants if a context is available to make sure the Trace Contexts are present
+// in logs.
+//
+// If Depth is present, this function calls log from a different depth in the call stack.
+// This enables a callee to emit logs that use the callsite information of its caller
+// or any other callers in the stack. When depth == 0, the original callee's line
+// information is emitted. When depth > 0, depth frames are skipped in the call stack
+// and the final frame is treated like the original callee to Info.
+//
+// If 'f' is present, function formats according to a format specifier.
+//
+// This package also provides V-style logging controlled by the -v and -vmodule=file=2 flags.
 //
 // Basic examples:
 //
@@ -82,6 +100,7 @@
 
 import (
 	"bytes"
+	"context"
 	"errors"
 	"fmt"
 	stdLog "log"
@@ -182,9 +201,14 @@
 	return format, args
 }
 
-// logf writes a log message for a log function call (or log function wrapper)
-// at the given depth in the current goroutine's stack.
+// logf acts as ctxlogf, but doesn't expect a context.
 func logf(depth int, severity logsink.Severity, verbose bool, stack stack, format string, args ...any) {
+	ctxlogf(nil, depth+1, severity, verbose, stack, format, args...)
+}
+
+// ctxlogf writes a log message for a log function call (or log function wrapper)
+// at the given depth in the current goroutine's stack.
+func ctxlogf(ctx context.Context, depth int, severity logsink.Severity, verbose bool, stack stack, format string, args ...any) {
 	now := timeNow()
 	_, file, line, ok := runtime.Caller(depth + 1)
 	if !ok {
@@ -198,6 +222,7 @@
 
 	metai, meta := metaPoolGet()
 	*meta = logsink.Meta{
+		Context:  ctx,
 		Time:     now,
 		File:     file,
 		Line:     line,
@@ -207,6 +232,9 @@
 		Thread:   int64(pid),
 	}
 	sinkf(meta, format, args...)
+	// Clear pointer fields so they can be garbage collected early.
+	meta.Context = nil
+	meta.Stack = nil
 	metaPool.Put(metai)
 }
 
@@ -418,6 +446,36 @@
 	}
 }
 
+// InfoContext is equivalent to the global InfoContext function, guarded by the value of v.
+// See the documentation of V for usage.
+func (v Verbose) InfoContext(ctx context.Context, args ...any) {
+	v.InfoContextDepth(ctx, 1, args...)
+}
+
+// InfoContextf is equivalent to the global InfoContextf function, guarded by the value of v.
+// See the documentation of V for usage.
+func (v Verbose) InfoContextf(ctx context.Context, format string, args ...any) {
+	if v {
+		ctxlogf(ctx, 1, logsink.Info, true, noStack, format, args...)
+	}
+}
+
+// InfoContextDepth is equivalent to the global InfoContextDepth function, guarded by the value of v.
+// See the documentation of V for usage.
+func (v Verbose) InfoContextDepth(ctx context.Context, depth int, args ...any) {
+	if v {
+		ctxlogf(ctx, depth+1, logsink.Info, true, noStack, defaultFormat(args), args...)
+	}
+}
+
+// InfoContextDepthf is equivalent to the global InfoContextDepthf function, guarded by the value of v.
+// See the documentation of V for usage.
+func (v Verbose) InfoContextDepthf(ctx context.Context, depth int, format string, args ...any) {
+	if v {
+		ctxlogf(ctx, depth+1, logsink.Info, true, noStack, format, args...)
+	}
+}
+
 // Info logs to the INFO log.
 // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
 func Info(args ...any) {
@@ -450,6 +508,30 @@
 	logf(1, logsink.Info, false, noStack, format, args...)
 }
 
+// InfoContext is like [Info], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func InfoContext(ctx context.Context, args ...any) {
+	InfoContextDepth(ctx, 1, args...)
+}
+
+// InfoContextf is like [Infof], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func InfoContextf(ctx context.Context, format string, args ...any) {
+	ctxlogf(ctx, 1, logsink.Info, false, noStack, format, args...)
+}
+
+// InfoContextDepth is like [InfoDepth], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func InfoContextDepth(ctx context.Context, depth int, args ...any) {
+	ctxlogf(ctx, depth+1, logsink.Info, false, noStack, defaultFormat(args), args...)
+}
+
+// InfoContextDepthf is like [InfoDepthf], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func InfoContextDepthf(ctx context.Context, depth int, format string, args ...any) {
+	ctxlogf(ctx, depth+1, logsink.Info, false, noStack, format, args...)
+}
+
 // Warning logs to the WARNING and INFO logs.
 // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
 func Warning(args ...any) {
@@ -480,6 +562,30 @@
 	logf(1, logsink.Warning, false, noStack, format, args...)
 }
 
+// WarningContext is like [Warning], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func WarningContext(ctx context.Context, args ...any) {
+	WarningContextDepth(ctx, 1, args...)
+}
+
+// WarningContextf is like [Warningf], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func WarningContextf(ctx context.Context, format string, args ...any) {
+	ctxlogf(ctx, 1, logsink.Warning, false, noStack, format, args...)
+}
+
+// WarningContextDepth is like [WarningDepth], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func WarningContextDepth(ctx context.Context, depth int, args ...any) {
+	ctxlogf(ctx, depth+1, logsink.Warning, false, noStack, defaultFormat(args), args...)
+}
+
+// WarningContextDepthf is like [WarningDepthf], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func WarningContextDepthf(ctx context.Context, depth int, format string, args ...any) {
+	ctxlogf(ctx, depth+1, logsink.Warning, false, noStack, format, args...)
+}
+
 // Error logs to the ERROR, WARNING, and INFO logs.
 // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
 func Error(args ...any) {
@@ -510,8 +616,32 @@
 	logf(1, logsink.Error, false, noStack, format, args...)
 }
 
-func fatalf(depth int, format string, args ...any) {
-	logf(depth+1, logsink.Fatal, false, withStack, format, args...)
+// ErrorContext is like [Error], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func ErrorContext(ctx context.Context, args ...any) {
+	ErrorContextDepth(ctx, 1, args...)
+}
+
+// ErrorContextf is like [Errorf], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func ErrorContextf(ctx context.Context, format string, args ...any) {
+	ctxlogf(ctx, 1, logsink.Error, false, noStack, format, args...)
+}
+
+// ErrorContextDepth is like [ErrorDepth], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func ErrorContextDepth(ctx context.Context, depth int, args ...any) {
+	ctxlogf(ctx, depth+1, logsink.Error, false, noStack, defaultFormat(args), args...)
+}
+
+// ErrorContextDepthf is like [ErrorDepthf], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func ErrorContextDepthf(ctx context.Context, depth int, format string, args ...any) {
+	ctxlogf(ctx, depth+1, logsink.Error, false, noStack, format, args...)
+}
+
+func ctxfatalf(ctx context.Context, depth int, format string, args ...any) {
+	ctxlogf(ctx, depth+1, logsink.Fatal, false, withStack, format, args...)
 	sinks.file.Flush()
 
 	err := abortProcess() // Should not return.
@@ -523,6 +653,10 @@
 	os.Exit(2) // Exit with the same code as the default SIGABRT handler.
 }
 
+func fatalf(depth int, format string, args ...any) {
+	ctxfatalf(nil, depth+1, format, args...)
+}
+
 // Fatal logs to the FATAL, ERROR, WARNING, and INFO logs,
 // including a stack trace of all running goroutines, then calls os.Exit(2).
 // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
@@ -556,12 +690,39 @@
 	fatalf(1, format, args...)
 }
 
-func exitf(depth int, format string, args ...any) {
-	logf(depth+1, logsink.Fatal, false, noStack, format, args...)
+// FatalContext is like [Fatal], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func FatalContext(ctx context.Context, args ...any) {
+	FatalContextDepth(ctx, 1, args...)
+}
+
+// FatalContextf is like [Fatalf], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func FatalContextf(ctx context.Context, format string, args ...any) {
+	ctxfatalf(ctx, 1, format, args...)
+}
+
+// FatalContextDepth is like [FatalDepth], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func FatalContextDepth(ctx context.Context, depth int, args ...any) {
+	ctxfatalf(ctx, depth+1, defaultFormat(args), args...)
+}
+
+// FatalContextDepthf is like [FatalDepthf], but with an extra [context.Context] parameter.
+func FatalContextDepthf(ctx context.Context, depth int, format string, args ...any) {
+	ctxfatalf(ctx, depth+1, format, args...)
+}
+
+func ctxexitf(ctx context.Context, depth int, format string, args ...any) {
+	ctxlogf(ctx, depth+1, logsink.Fatal, false, noStack, format, args...)
 	sinks.file.Flush()
 	os.Exit(1)
 }
 
+func exitf(depth int, format string, args ...any) {
+	ctxexitf(nil, depth+1, format, args...)
+}
+
 // Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1).
 // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
 func Exit(args ...any) {
@@ -590,3 +751,27 @@
 func Exitf(format string, args ...any) {
 	exitf(1, format, args...)
 }
+
+// ExitContext is like [Exit], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func ExitContext(ctx context.Context, args ...any) {
+	ExitContextDepth(ctx, 1, args...)
+}
+
+// ExitContextf is like [Exitf], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func ExitContextf(ctx context.Context, format string, args ...any) {
+	ctxexitf(ctx, 1, format, args...)
+}
+
+// ExitContextDepth is like [ExitDepth], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func ExitContextDepth(ctx context.Context, depth int, args ...any) {
+	ctxexitf(ctx, depth+1, defaultFormat(args), args...)
+}
+
+// ExitContextDepthf is like [ExitDepthf], but with an extra [context.Context] parameter. The
+// context is used to pass the Trace Context to log sinks.
+func ExitContextDepthf(ctx context.Context, depth int, format string, args ...any) {
+	ctxexitf(ctx, depth+1, format, args...)
+}
diff --git a/glog_context_test.go b/glog_context_test.go
new file mode 100644
index 0000000..9dbac83
--- /dev/null
+++ b/glog_context_test.go
@@ -0,0 +1,62 @@
+package glog
+
+import (
+	"context"
+	"flag"
+	"testing"
+
+	"github.com/golang/glog/internal/logsink"
+)
+
+type contextKey string
+type fakeLogSink struct {
+	context context.Context
+}
+
+var ctxKey = contextKey("key")
+var ctxValue = "some-value"
+var originalSinks = logsink.StructuredSinks
+
+func (s *fakeLogSink) Printf(meta *logsink.Meta, format string, args ...any) (int, error) {
+	s.context = meta.Context
+	return 0, nil
+}
+
+// Test that log.(Info|Error|Warning)Context functions behave the same as non context variants
+// and pass right context.
+func TestLogContext(t *testing.T) {
+	fakeLogSink := &fakeLogSink{}
+	logsink.StructuredSinks = append([]logsink.Structured{fakeLogSink}, originalSinks...)
+
+	funcs := map[string]func(ctx context.Context, args ...any){
+		"InfoContext":      InfoContext,
+		"InfoContextDepth": func(ctx context.Context, args ...any) { InfoContextDepth(ctx, 2, args) },
+		"ErrorContext":     ErrorContext,
+		"WarningContext":   WarningContext,
+	}
+
+	ctx := context.WithValue(context.Background(), ctxKey, ctxValue)
+	for name, f := range funcs {
+		f(ctx, "test")
+		want := ctxValue
+		if got := fakeLogSink.context.Value(ctxKey); got != want {
+			t.Errorf("%s: context value unexpectedly missing: got %q, want %q", name, got, want)
+		}
+	}
+}
+
+// Test that V.InfoContext behaves the same as V.Info and passes right context.
+func TestVInfoContext(t *testing.T) {
+	fakeLogSink := &fakeLogSink{}
+	logsink.StructuredSinks = append([]logsink.Structured{fakeLogSink}, originalSinks...)
+	if err := flag.Lookup("v").Value.Set("2"); err != nil {
+		t.Fatalf("Failed to set -v=2: %v", err)
+	}
+	defer flag.Lookup("v").Value.Set("0")
+	ctx := context.WithValue(context.Background(), ctxKey, ctxValue)
+	V(2).InfoContext(ctx, "test")
+	want := ctxValue
+	if got := fakeLogSink.context.Value(ctxKey); got != want {
+		t.Errorf("V.InfoContext: context value unexpectedly missing: got %q, want %q", got, want)
+	}
+}
diff --git a/glog_test.go b/glog_test.go
index 81c43ce..54762c5 100644
--- a/glog_test.go
+++ b/glog_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"bytes"
+	"context"
 	"flag"
 	"fmt"
 	"io/ioutil"
@@ -36,6 +37,7 @@
 }
 
 func (f *flushBuffer) Flush() error {
+	f.Buffer.Reset()
 	return nil
 }
 
@@ -63,6 +65,16 @@
 	return s.swap(severityWriters{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})
 }
 
+func (s *fileSink) resetBuffers() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	for _, buf := range s.file {
+		if buf != nil {
+			buf.Flush()
+		}
+	}
+}
+
 // contents returns the specified log value as a string.
 func contents(s logsink.Severity) string {
 	return sinks.file.file[s].(*flushBuffer).String()
@@ -82,12 +94,20 @@
 func TestInfo(t *testing.T) {
 	setFlags()
 	defer sinks.file.swap(sinks.file.newBuffers())
-	Info("test")
-	if !contains(logsink.Info, "I", t) {
-		t.Errorf("Info has wrong character: %q", contents(logsink.Info))
+	funcs := []func(args ...any){
+		Info,
+		func(args ...any) { InfoContext(context.Background(), args) },
 	}
-	if !contains(logsink.Info, "test", t) {
-		t.Error("Info failed")
+
+	for _, f := range funcs {
+		sinks.file.resetBuffers()
+		f("test")
+		if !contains(logsink.Info, "I", t) {
+			t.Errorf("Info has wrong character: %q", contents(logsink.Info))
+		}
+		if !contains(logsink.Info, "test", t) {
+			t.Error("Info failed")
+		}
 	}
 }
 
@@ -95,42 +115,50 @@
 	setFlags()
 	defer sinks.file.swap(sinks.file.newBuffers())
 
-	f := func() { InfoDepth(1, "depth-test1") }
-
-	// The next three lines must stay together
-	_, _, wantLine, _ := runtime.Caller(0)
-	InfoDepth(0, "depth-test0")
-	f()
-
-	msgs := strings.Split(strings.TrimSuffix(contents(logsink.Info), "\n"), "\n")
-	if len(msgs) != 2 {
-		t.Fatalf("Got %d lines, expected 2", len(msgs))
+	funcs := []func(d int, args ...any){
+		InfoDepth,
+		func(d int, args ...any) { InfoContextDepth(context.Background(), d+1, args) },
 	}
 
-	for i, m := range msgs {
-		if !strings.HasPrefix(m, "I") {
-			t.Errorf("InfoDepth[%d] has wrong character: %q", i, m)
-		}
-		w := fmt.Sprintf("depth-test%d", i)
-		if !strings.Contains(m, w) {
-			t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m)
+	for _, infoDepth := range funcs {
+		sinks.file.resetBuffers()
+		f := func() { infoDepth(1, "depth-test1") }
+
+		// The next three lines must stay together
+		_, _, wantLine, _ := runtime.Caller(0)
+		infoDepth(0, "depth-test0")
+		f()
+
+		msgs := strings.Split(strings.TrimSuffix(contents(logsink.Info), "\n"), "\n")
+		if len(msgs) != 2 {
+			t.Fatalf("Got %d lines, expected 2", len(msgs))
 		}
 
-		// pull out the line number (between : and ])
-		msg := m[strings.LastIndex(m, ":")+1:]
-		x := strings.Index(msg, "]")
-		if x < 0 {
-			t.Errorf("InfoDepth[%d]: missing ']': %q", i, m)
-			continue
-		}
-		line, err := strconv.Atoi(msg[:x])
-		if err != nil {
-			t.Errorf("InfoDepth[%d]: bad line number: %q", i, m)
-			continue
-		}
-		wantLine++
-		if wantLine != line {
-			t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine)
+		for i, m := range msgs {
+			if !strings.HasPrefix(m, "I") {
+				t.Errorf("InfoDepth[%d] has wrong character: %q", i, m)
+			}
+			w := fmt.Sprintf("depth-test%d", i)
+			if !strings.Contains(m, w) {
+				t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m)
+			}
+
+			// pull out the line number (between : and ])
+			msg := m[strings.LastIndex(m, ":")+1:]
+			x := strings.Index(msg, "]")
+			if x < 0 {
+				t.Errorf("InfoDepth[%d]: missing ']': %q", i, m)
+				continue
+			}
+			line, err := strconv.Atoi(msg[:x])
+			if err != nil {
+				t.Errorf("InfoDepth[%d]: bad line number: %q", i, m)
+				continue
+			}
+			wantLine++
+			if wantLine != line {
+				t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine)
+			}
 		}
 	}
 }
@@ -204,19 +232,28 @@
 func TestError(t *testing.T) {
 	setFlags()
 	defer sinks.file.swap(sinks.file.newBuffers())
-	Error("test")
-	if !contains(logsink.Error, "E", t) {
-		t.Errorf("Error has wrong character: %q", contents(logsink.Error))
+
+	funcs := []func(args ...any){
+		Error,
+		func(args ...any) { ErrorContext(context.Background(), args) },
 	}
-	if !contains(logsink.Error, "test", t) {
-		t.Error("Error failed")
-	}
-	str := contents(logsink.Error)
-	if !contains(logsink.Warning, str, t) {
-		t.Error("Warning failed")
-	}
-	if !contains(logsink.Info, str, t) {
-		t.Error("Info failed")
+
+	for _, error := range funcs {
+		sinks.file.resetBuffers()
+		error("test")
+		if !contains(logsink.Error, "E", t) {
+			t.Errorf("Error has wrong character: %q", contents(logsink.Error))
+		}
+		if !contains(logsink.Error, "test", t) {
+			t.Error("Error failed")
+		}
+		str := contents(logsink.Error)
+		if !contains(logsink.Warning, str, t) {
+			t.Error("Warning failed")
+		}
+		if !contains(logsink.Info, str, t) {
+			t.Error("Info failed")
+		}
 	}
 }
 
@@ -226,16 +263,25 @@
 func TestWarning(t *testing.T) {
 	setFlags()
 	defer sinks.file.swap(sinks.file.newBuffers())
-	Warning("test")
-	if !contains(logsink.Warning, "W", t) {
-		t.Errorf("Warning has wrong character: %q", contents(logsink.Warning))
+
+	funcs := []func(args ...any){
+		Warning,
+		func(args ...any) { WarningContext(context.Background(), args) },
 	}
-	if !contains(logsink.Warning, "test", t) {
-		t.Error("Warning failed")
-	}
-	str := contents(logsink.Warning)
-	if !contains(logsink.Info, str, t) {
-		t.Error("Info failed")
+
+	for _, warning := range funcs {
+		sinks.file.resetBuffers()
+		warning("test")
+		if !contains(logsink.Warning, "W", t) {
+			t.Errorf("Warning has wrong character: %q", contents(logsink.Warning))
+		}
+		if !contains(logsink.Warning, "test", t) {
+			t.Error("Warning failed")
+		}
+		str := contents(logsink.Warning)
+		if !contains(logsink.Info, str, t) {
+			t.Error("Info failed")
+		}
 	}
 }
 
@@ -248,12 +294,19 @@
 	}
 	defer flag.Lookup("v").Value.Set("0")
 
-	V(2).Info("test")
-	if !contains(logsink.Info, "I", t) {
-		t.Errorf("Info has wrong character: %q", contents(logsink.Info))
+	funcs := []func(args ...any){
+		V(2).Info,
+		func(args ...any) { V(2).InfoContext(context.Background(), args) },
 	}
-	if !contains(logsink.Info, "test", t) {
-		t.Error("Info failed")
+	for _, info := range funcs {
+		sinks.file.resetBuffers()
+		info("test")
+		if !contains(logsink.Info, "I", t) {
+			t.Errorf("Info has wrong character: %q", contents(logsink.Info))
+		}
+		if !contains(logsink.Info, "test", t) {
+			t.Error("Info failed")
+		}
 	}
 }
 
diff --git a/internal/logsink/logsink.go b/internal/logsink/logsink.go
index 53758e1..28c38a6 100644
--- a/internal/logsink/logsink.go
+++ b/internal/logsink/logsink.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"strconv"
 	"strings"
@@ -77,6 +78,11 @@
 
 // Meta is metadata about a logging call.
 type Meta struct {
+	// The context with which the log call was made (or nil). If set, the context
+	// is only valid during the logsink.Structured.Printf call, it should not be
+	// retained.
+	Context context.Context
+
 	// Time is the time at which the log call was made.
 	Time time.Time