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