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
113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
package simplelog
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
)
|
|
|
|
// ConsoleHandler writes human-readable, colored log lines to stdout.
|
|
type ConsoleHandler struct {
|
|
// out is where records are written. Nil means os.Stdout, looked up on
|
|
// every write so that a reassigned os.Stdout is followed.
|
|
out io.Writer
|
|
attrs handlerAttrs
|
|
}
|
|
|
|
// NewConsoleHandler returns a new ConsoleHandler.
|
|
func NewConsoleHandler() *ConsoleHandler {
|
|
return &ConsoleHandler{}
|
|
}
|
|
|
|
// Handle writes the record to stdout as a colored, timestamped line
|
|
// including the caller file and line, followed by the attributes as
|
|
// key=value pairs. A failed write is returned as an error.
|
|
func (c *ConsoleHandler) Handle(
|
|
_ context.Context,
|
|
record slog.Record,
|
|
) error {
|
|
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000Z07:00")
|
|
|
|
var colorFunc func(format string, a ...any) string
|
|
|
|
switch record.Level {
|
|
case slog.LevelDebug:
|
|
colorFunc = color.New(color.FgWhite).SprintfFunc()
|
|
case slog.LevelInfo:
|
|
colorFunc = color.New(color.FgBlue).SprintfFunc()
|
|
case slog.LevelWarn:
|
|
colorFunc = color.New(color.FgYellow).SprintfFunc()
|
|
case slog.LevelError:
|
|
colorFunc = color.New(color.FgRed).SprintfFunc()
|
|
default:
|
|
colorFunc = color.New(color.FgWhite).SprintfFunc()
|
|
}
|
|
|
|
// The file and line come from the record's PC, the call site; a record
|
|
// without one prints a placeholder.
|
|
file, line := "???", 0
|
|
|
|
if record.PC != 0 {
|
|
frame, _ := runtime.CallersFrames([]uintptr{record.PC}).Next()
|
|
file, line = frame.File, frame.Line
|
|
}
|
|
|
|
out := c.out
|
|
if out == nil {
|
|
out = os.Stdout
|
|
}
|
|
|
|
_, err := fmt.Fprintln(
|
|
out,
|
|
colorFunc(
|
|
"%s [%s] %s:%d: %s%s",
|
|
timestamp,
|
|
record.Level,
|
|
file,
|
|
line,
|
|
record.Message,
|
|
attrsToText(c.attrs.forRecord(record)),
|
|
),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("error writing log record: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Enabled reports whether the handler processes records at the given
|
|
// level; it always returns true.
|
|
func (c *ConsoleHandler) Enabled(
|
|
_ context.Context,
|
|
_ slog.Level,
|
|
) bool {
|
|
return true
|
|
}
|
|
|
|
// WithAttrs returns a new handler that also emits attrs, qualified by the
|
|
// groups open now. The receiver is not modified.
|
|
func (c *ConsoleHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
if len(attrs) == 0 {
|
|
return c
|
|
}
|
|
|
|
return &ConsoleHandler{out: c.out, attrs: c.attrs.withAttrs(attrs)}
|
|
}
|
|
|
|
// WithGroup returns a new handler that qualifies later attributes with
|
|
// the group name. An empty name returns the receiver.
|
|
func (c *ConsoleHandler) WithGroup(name string) slog.Handler {
|
|
if name == "" {
|
|
return c
|
|
}
|
|
|
|
return &ConsoleHandler{out: c.out, attrs: c.attrs.withGroup(name)}
|
|
}
|