Files
simplelog/console_handler_internal_test.go
T
clawbot 9710d3f05f
check / check (push) Successful in 30s
check / check (pull_request) Successful in 22s
Take the console location from the record (closes #36)
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
2026-10-06 12:02:43 +00:00

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")
}