check / check (push) Successful in 19s
ConsoleHandler found the file and line it prints by walking a fixed number of stack frames up from itself, which only fit a record logged through a slog.Logger and delivered through MultiplexHandler. It now reads them from the record's PC, the call site slog stores, so a ConsoleHandler used with slog.New and a record passed to Handle directly print the right location too. A record whose PC is zero prints ???:0 as before. The README drops its warning about the wrong location and says how to set PC instead. Model: opus-5-5
103 lines
2.5 KiB
Go
103 lines
2.5 KiB
Go
package simplelog
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// These tests sit inside the package so they can read the console line
|
|
// from a buffer instead of from stdout.
|
|
|
|
// lineOf calls logCall and returns "file:line" for the line lineOf was
|
|
// called from. Each test writes its log call inside logCall on that same
|
|
// line, so the result is the location the console line must name.
|
|
func lineOf(logCall func()) string {
|
|
_, file, line, _ := runtime.Caller(1)
|
|
|
|
logCall()
|
|
|
|
return fmt.Sprintf("%s:%d", file, line)
|
|
}
|
|
|
|
// wantLocation fails the test unless the console line names location as
|
|
// where the record "casting" was logged.
|
|
func wantLocation(t *testing.T, output, location string) {
|
|
t.Helper()
|
|
|
|
if !strings.Contains(output, location+": casting") {
|
|
t.Fatalf("console line %q does not name %s", output, location)
|
|
}
|
|
}
|
|
|
|
// The default handler as simplelog installs it when stdout is a terminal:
|
|
// a MultiplexHandler holding a ConsoleHandler.
|
|
func TestConsoleHandlerNamesCallSiteThroughDefaultHandler(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var output bytes.Buffer
|
|
|
|
logger := slog.New(&MultiplexHandler{handlers: []ExtendedHandler{
|
|
&ConsoleHandler{out: &output},
|
|
}})
|
|
|
|
want := lineOf(func() { logger.Info("casting") })
|
|
|
|
wantLocation(t, output.String(), want)
|
|
}
|
|
|
|
func TestConsoleHandlerNamesCallSiteUsedWithSlogNew(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var output bytes.Buffer
|
|
|
|
logger := slog.New(&ConsoleHandler{out: &output})
|
|
|
|
want := lineOf(func() { logger.Info("casting") })
|
|
|
|
wantLocation(t, output.String(), want)
|
|
}
|
|
|
|
// The record is built as the log/slog package documentation shows for a
|
|
// function that logs on its caller's behalf: runtime.Callers supplies the
|
|
// PC.
|
|
func TestConsoleHandlerNamesCallSiteOfRecordPassedToHandle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
output bytes.Buffer
|
|
pcs [1]uintptr
|
|
)
|
|
|
|
want := lineOf(func() { runtime.Callers(1, pcs[:]) })
|
|
|
|
record := slog.NewRecord(time.Now(), slog.LevelInfo, "casting", pcs[0])
|
|
|
|
err := (&ConsoleHandler{out: &output}).Handle(context.Background(), record)
|
|
if err != nil {
|
|
t.Fatalf("Handle: %v", err)
|
|
}
|
|
|
|
wantLocation(t, output.String(), want)
|
|
}
|
|
|
|
func TestConsoleHandlerPrintsPlaceholderWithoutPC(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var output bytes.Buffer
|
|
|
|
record := slog.NewRecord(time.Now(), slog.LevelInfo, "casting", 0)
|
|
|
|
err := (&ConsoleHandler{out: &output}).Handle(context.Background(), record)
|
|
if err != nil {
|
|
t.Fatalf("Handle: %v", err)
|
|
}
|
|
|
|
wantLocation(t, output.String(), "???:0")
|
|
}
|