Files
go-libp2p/gologshim/gologshim.go
2025-08-25 14:00:01 -07:00

147 lines
3.5 KiB
Go

package gologshim
import (
"fmt"
"log/slog"
"os"
"strings"
"sync"
)
var lvlToLower = map[slog.Level]slog.Value{
slog.LevelDebug: slog.StringValue("debug"),
slog.LevelInfo: slog.StringValue("info"),
slog.LevelWarn: slog.StringValue("warn"),
slog.LevelError: slog.StringValue("error"),
}
// Logger returns a *slog.Logger with a logging level defined by the
// GOLOG_LOG_LEVEL env var. Supports different levels for different systems. e.g.
// GOLOG_LOG_LEVEL=foo=info,bar=debug,warn
// sets the foo system at level info, the bar system at level debug and the
// fallback level to warn.
//
// Prefer a parameterized logger over a global logger.
func Logger(system string) *slog.Logger {
var h slog.Handler
c := ConfigFromEnv()
handlerOpts := &slog.HandlerOptions{
Level: c.LevelForSystem(system),
AddSource: c.addSource,
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
// ipfs go-log uses "ts" for time
a.Key = "ts"
} else if a.Key == slog.LevelKey {
// ipfs go-log uses lowercase level names
if lvl, ok := a.Value.Any().(slog.Level); ok {
if s, ok := lvlToLower[lvl]; ok {
a.Value = s
}
}
}
return a
},
}
if c.format == logFormatText {
h = slog.NewTextHandler(os.Stderr, handlerOpts)
} else {
h = slog.NewJSONHandler(os.Stderr, handlerOpts)
}
attrs := make([]slog.Attr, 1+len(c.labels))
attrs = append(attrs, slog.String("logger", system))
attrs = append(attrs, c.labels...)
h = h.WithAttrs(attrs)
return slog.New(h)
}
type logFormat = int
const (
logFormatText logFormat = iota
logFormatJSON
)
type Config struct {
fallbackLvl slog.Level
systemToLevel map[string]slog.Level
format logFormat
addSource bool
labels []slog.Attr
}
func (c *Config) LevelForSystem(system string) slog.Level {
if lvl, ok := c.systemToLevel[system]; ok {
return lvl
}
return c.fallbackLvl
}
var ConfigFromEnv func() *Config = sync.OnceValue(func() *Config {
fallback, systemToLevel, err := parseIPFSGoLogEnv(os.Getenv("GOLOG_LOG_LEVEL"))
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse GOLOG_LOG_LEVEL: %v", err)
fallback = slog.LevelInfo
}
c := &Config{
fallbackLvl: fallback,
systemToLevel: systemToLevel,
addSource: true,
}
logFmt := os.Getenv("GOLOG_LOG_FORMAT")
if logFmt == "" {
logFmt = os.Getenv("GOLOG_LOG_FMT")
}
if logFmt == "json" {
c.format = logFormatJSON
}
logFmt = os.Getenv("GOLOG_LOG_ADD_SOURCE")
if logFmt == "0" || logFmt == "false" {
c.addSource = false
}
labels := os.Getenv("GOLOG_LOG_LABELS")
if labels != "" {
labels := strings.Split(labels, ",")
if len(labels) > 0 {
for _, label := range labels {
kv := strings.SplitN(label, "=", 2)
if len(kv) == 2 {
c.labels = append(c.labels, slog.String(kv[0], kv[1]))
} else {
fmt.Fprintf(os.Stderr, "Invalid label format: %s", label)
}
}
}
}
return c
})
func parseIPFSGoLogEnv(loggingLevelEnvStr string) (slog.Level, map[string]slog.Level, error) {
fallbackLvl := slog.LevelError
var systemToLevel map[string]slog.Level
if loggingLevelEnvStr != "" {
for _, kvs := range strings.Split(loggingLevelEnvStr, ",") {
kv := strings.SplitN(kvs, "=", 2)
var lvl slog.Level
err := lvl.UnmarshalText([]byte(kv[len(kv)-1]))
if err != nil {
return lvl, nil, err
}
switch len(kv) {
case 1:
fallbackLvl = lvl
case 2:
if systemToLevel == nil {
systemToLevel = make(map[string]slog.Level)
}
systemToLevel[kv[0]] = lvl
}
}
}
return fallbackLvl, systemToLevel, nil
}