srs/internal/logger/log.go
Winlin 30fc7775a5
Proxy: Modernize internal packages on stdlib and add unit tests. v7.0.145 (#4667)
Modernizes several `internal/*` packages under the Go proxy, replaces
third-party forks with standard-library primitives, and brings the
test suite from near-zero to high coverage across the touched packages.

Package changes

- **`internal/errors`** — Rewrites the `pkg/errors` fork as a thin
wrapper
  over stdlib `errors`. A single `withStack` struct captures stack
  traces via `runtime.Callers`; `fmt.Errorf("%w", ...)` handles all
  message wrapping. Restores `errors.Is`/`As`/`Unwrap` chain traversal
  (silently broken in the fork) and deletes ~190 lines of stack/frame
  formatting. `Is`, `As`, `Unwrap`, and `Join` are re-exported so
  callers need a single import.
- **`internal/logger`** — Swaps stdlib `log.Logger` for `log/slog` JSON
  handlers with UTC timestamps and custom level labels (`verb`, `debug`,
  `warn`, `error`). Hides `withContextID` (no external callers).
- **`internal/sync`** — Converts `Map[K, V]` from a concrete struct to
  an interface with a `NewMap` constructor for testability.
- **`internal/signal`** — Adds `signalNotify` / `osExit` indirections so
  `InstallSignals` and `InstallForceQuit` can be exercised without real
  OS signals or process termination.
- **`internal/utils`** — Drops deprecated `io/ioutil` and the stdlib
  `errors` alias (the internal `errors` package re-exports what's
  needed).
- **`internal/version`** — No code changes; fully covered by new tests.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:25:48 -04:00

123 lines
2.6 KiB
Go

// Copyright (c) 2026 Winlin
//
// SPDX-License-Identifier: MIT
package logger
import (
"context"
"fmt"
"io"
"log/slog"
"os"
)
type logger interface {
Printf(ctx context.Context, format string, v ...any)
}
type loggerPlus struct {
logger *slog.Logger
level slog.Level
}
func newLoggerPlus(opts ...func(*loggerPlus)) *loggerPlus {
v := &loggerPlus{}
for _, opt := range opts {
opt(v)
}
return v
}
func (v *loggerPlus) Printf(ctx context.Context, f string, a ...any) {
attrs := []slog.Attr{slog.Int("pid", os.Getpid())}
if cid := ContextID(ctx); cid != "" {
attrs = append(attrs, slog.String("cid", cid))
}
v.logger.LogAttrs(ctx, v.level, fmt.Sprintf(f, a...), attrs...)
}
var verboseLogger logger
func Vf(ctx context.Context, format string, a ...any) {
verboseLogger.Printf(ctx, format, a...)
}
var debugLogger logger
func Df(ctx context.Context, format string, a ...any) {
debugLogger.Printf(ctx, format, a...)
}
var warnLogger logger
func Wf(ctx context.Context, format string, a ...any) {
warnLogger.Printf(ctx, format, a...)
}
var errorLogger logger
func Ef(ctx context.Context, format string, a ...any) {
errorLogger.Printf(ctx, format, a...)
}
const (
levelVerb slog.Level = slog.LevelDebug - 4
levelDebug slog.Level = slog.LevelDebug
levelWarn slog.Level = slog.LevelWarn
levelError slog.Level = slog.LevelError
)
// newJSONLogger builds a slog.Logger that writes JSON records to w, renders the
// time in UTC, and maps our custom levels to short lowercase labels.
func newJSONLogger(w io.Writer) *slog.Logger {
h := slog.NewJSONHandler(w, &slog.HandlerOptions{
Level: levelVerb,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if len(groups) != 0 {
return a
}
switch a.Key {
case slog.TimeKey:
return slog.Time(slog.TimeKey, a.Value.Time().UTC())
case slog.LevelKey:
return slog.String(slog.LevelKey, levelLabel(a.Value.Any().(slog.Level)))
}
return a
},
})
return slog.New(h)
}
func levelLabel(l slog.Level) string {
switch l {
case levelVerb:
return "verb"
case levelDebug:
return "debug"
case levelWarn:
return "warn"
case levelError:
return "error"
}
return l.String()
}
func init() {
verboseLogger = newLoggerPlus(func(l *loggerPlus) {
l.logger = newJSONLogger(io.Discard)
l.level = levelVerb
})
debugLogger = newLoggerPlus(func(l *loggerPlus) {
l.logger = newJSONLogger(os.Stdout)
l.level = levelDebug
})
warnLogger = newLoggerPlus(func(l *loggerPlus) {
l.logger = newJSONLogger(os.Stderr)
l.level = levelWarn
})
errorLogger = newLoggerPlus(func(l *loggerPlus) {
l.logger = newJSONLogger(os.Stderr)
l.level = levelError
})
}