From c5783cbd7ec3c48615e9251f1f7ac08c7cb4510c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 00:12:16 +0000 Subject: [PATCH] Bump github.com/go-pkgz/lgr from 0.11.1 to 0.12.1 Bumps [github.com/go-pkgz/lgr](https://github.com/go-pkgz/lgr) from 0.11.1 to 0.12.1. - [Release notes](https://github.com/go-pkgz/lgr/releases) - [Commits](https://github.com/go-pkgz/lgr/compare/v0.11.1...v0.12.1) --- updated-dependencies: - dependency-name: github.com/go-pkgz/lgr dependency-version: 0.12.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 +- go.sum | 5 +- vendor/github.com/go-pkgz/lgr/.golangci.yml | 159 ++++++++------ vendor/github.com/go-pkgz/lgr/CODEOWNERS | 5 + vendor/github.com/go-pkgz/lgr/README.md | 81 ++++++- vendor/github.com/go-pkgz/lgr/interface.go | 4 +- vendor/github.com/go-pkgz/lgr/logger.go | 48 +++-- vendor/github.com/go-pkgz/lgr/options.go | 35 +++ vendor/github.com/go-pkgz/lgr/slog.go | 228 ++++++++++++++++++++ vendor/modules.txt | 4 +- 10 files changed, 487 insertions(+), 86 deletions(-) create mode 100644 vendor/github.com/go-pkgz/lgr/CODEOWNERS create mode 100644 vendor/github.com/go-pkgz/lgr/slog.go diff --git a/go.mod b/go.mod index f08d44c..c047f65 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/umputun/rlb -go 1.20 +go 1.21 require ( github.com/didip/tollbooth/v7 v7.0.2 github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f github.com/go-chi/chi/v5 v5.2.0 github.com/go-chi/render v1.0.3 - github.com/go-pkgz/lgr v0.11.1 + github.com/go-pkgz/lgr v0.12.1 github.com/go-pkgz/rest v1.20.2 github.com/jessevdk/go-flags v1.6.1 github.com/lithammer/shortuuid/v4 v4.0.0 diff --git a/go.sum b/go.sum index e9eb028..c96ed80 100644 --- a/go.sum +++ b/go.sum @@ -15,13 +15,14 @@ github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIo github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs= github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw= github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec= -github.com/go-pkgz/lgr v0.11.1 h1:hXFhZcznehI6imLhEa379oMOKFz7TQUmisAqb3oLOSM= -github.com/go-pkgz/lgr v0.11.1/go.mod h1:tgDF4RXQnBfIgJqjgkv0yOeTQ3F1yewWIZkpUhHnAkU= +github.com/go-pkgz/lgr v0.12.1 h1:8GVfG2rSARq3Eaj5PP158rtBR2LHVGkwioIkQBGbvKg= +github.com/go-pkgz/lgr v0.12.1/go.mod h1:A4AxjOthFVFK6jRnVYMeusno5SeDAxcLVHd0kI/lN/Y= github.com/go-pkgz/rest v1.20.2 h1:6wYWo85H7xFU09FadVKKc5LKIfIpCStBXJj9F/P4COc= github.com/go-pkgz/rest v1.20.2/go.mod h1:NC2xNN/y1rIs0PY13FowKoH8rk9RhJNJ0tTbkBg8Yks= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c= diff --git a/vendor/github.com/go-pkgz/lgr/.golangci.yml b/vendor/github.com/go-pkgz/lgr/.golangci.yml index c0e04d0..2ad5ea8 100644 --- a/vendor/github.com/go-pkgz/lgr/.golangci.yml +++ b/vendor/github.com/go-pkgz/lgr/.golangci.yml @@ -1,67 +1,104 @@ -linters-settings: - govet: - check-shadowing: true - golint: - min-confidence: 0 - gocyclo: - min-complexity: 15 - maligned: - suggest-new: true - dupl: - threshold: 100 - goconst: - min-len: 2 - min-occurrences: 2 - misspell: - locale: US - lll: - line-length: 140 - gocritic: - enabled-tags: - - performance - - style - - experimental - disabled-checks: - - wrapperFunc - - hugeParam - +version: "2" +run: + concurrency: 4 linters: + default: none enable: - - megacheck - - revive - - govet - - unconvert - - megacheck - - gas - - gocyclo - - dupl - - misspell - - unparam - - unused - - typecheck - - ineffassign - - stylecheck - gochecknoinits - - exportloopref - gocritic + - gosec + - govet + - ineffassign - nakedret - - gosimple - prealloc - fast: false - disable-all: true - -run: - output: - format: tab - skip-dirs: - - vendor - -issues: - exclude-rules: - - text: "should have a package comment, unless it's in another file for this package" - linters: - - golint - - text: "at least one file in a package should have a package comment" - linters: - - stylecheck - exclude-use-default: false + - revive + - staticcheck + - unconvert + - unparam + - unused + - nestif + - testifylint + settings: + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 2 + gocritic: + disabled-checks: + - wrapperFunc + - hugeParam + - rangeValCopy + enabled-tags: + - performance + - style + - experimental + gocyclo: + min-complexity: 15 + govet: + enable: + - shadow + lll: + line-length: 140 + misspell: + locale: US + exclusions: + generated: lax + rules: + - linters: + - golint + text: should have a package comment, unless it's in another file for this package + - linters: + - gocritic + text: 'exitAfterDefer:' + - linters: + - gocritic + text: 'whyNoLint: include an explanation for nolint directive' + - linters: + - govet + text: go.mongodb.org/mongo-driver/bson/primitive.E + - linters: + - gosec + text: weak cryptographic primitive + - linters: + - gosec + text: integer overflow conversion + - linters: + - revive + text: should have a package comment + - linters: + - staticcheck + text: at least one file in a package should have a package comment + - linters: + - gocritic + text: 'commentedOutCode: may want to remove commented-out code' + - linters: + - gocritic + text: 'unnamedResult: consider giving a name to these results' + - linters: + - revive + text: 'var-naming: don''t use an underscore in package name' + - linters: + - staticcheck + text: should not use underscores in package names + - linters: + - govet + text: struct literal uses unkeyed fields + - linters: + - revive + - unparam + - unused + path: _test\.go$ + text: unused-parameter + paths: + - vendor + - third_party$ + - builtin$ + - examples$ +formatters: + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/go-pkgz/lgr/CODEOWNERS b/vendor/github.com/go-pkgz/lgr/CODEOWNERS new file mode 100644 index 0000000..91c94cb --- /dev/null +++ b/vendor/github.com/go-pkgz/lgr/CODEOWNERS @@ -0,0 +1,5 @@ +# These owners will be the default owners for everything in the repo. +# Unless a later match takes precedence, @umputun will be requested for +# review when someone opens a pull request. + +* @umputun diff --git a/vendor/github.com/go-pkgz/lgr/README.md b/vendor/github.com/go-pkgz/lgr/README.md index f01f999..87494b8 100644 --- a/vendor/github.com/go-pkgz/lgr/README.md +++ b/vendor/github.com/go-pkgz/lgr/README.md @@ -37,15 +37,16 @@ _Without `lgr.Caller*` it will drop `{caller}` part_ - `lgr.Trace` - turn trace mode on to allow messages with "TRACE" abd "DEBUG" levels both (filtered otherwise) - `lgr.Out(io.Writer)` - sets the output writer, default `os.Stdout` - `lgr.Err(io.Writer)` - sets the error writer, default `os.Stderr` -- `lgr.CallerFile` - adds the caller file info -- `lgr.CallerFunc` - adds the caller function info -- `lgr.CallerPkg` - adds the caller package +- `lgr.CallerFile` - adds the caller file info (only affects lgr's native text format, not slog output) +- `lgr.CallerFunc` - adds the caller function info (only affects lgr's native text format, not slog output) +- `lgr.CallerPkg` - adds the caller package (only affects lgr's native text format, not slog output) - `lgr.LevelBraces` - wraps levels with "[" and "]" - `lgr.Msec` - adds milliseconds to timestamp - `lgr.Format` - sets a custom template, overwrite all other formatting modifiers. - `lgr.Secret(secret ...)` - sets list of the secrets to hide from the logging outputs. - `lgr.Map(mapper)` - sets mapper functions to change elements of the logging output based on levels. - `lgr.StackTraceOnError` - turns on stack trace for ERROR level. +- `lgr.SlogHandler(h slog.Handler)` - delegates logging to the provided slog handler. example: `l := lgr.New(lgr.Debug, lgr.Msec)` @@ -106,15 +107,87 @@ example with [fatih/color](https://github.com/fatih/color): ``` ### adaptors -`lgr` logger can be converted to `io.Writer` or `*log.Logger` +`lgr` logger can be converted to `io.Writer`, `*log.Logger`, or `slog.Handler` - `lgr.ToWriter(l lgr.L, level string) io.Writer` - makes io.Writer forwarding write ops to underlying `lgr.L` - `lgr.ToStdLogger(l lgr.L, level string) *log.Logger` - makes standard logger on top of `lgr.L` +- `lgr.ToSlogHandler(l lgr.L) slog.Handler` - converts lgr.L to a slog.Handler for use with slog _`level` parameter is optional, if defined (non-empty) will enforce the level._ - `lgr.SetupStdLogger(opts ...Option)` initializes std global logger (`log.std`) with lgr logger and given options. All standard methods like `log.Print`, `log.Println`, `log.Fatal` and so on will be forwarder to lgr. +- `lgr.SetupWithSlog(logger *slog.Logger)` sets up the global logger with a slog logger. + +### slog integration + +In addition to the standard logger interface, lgr provides seamless integration with Go's `log/slog` package: + +#### Using lgr with slog + +```go +// Create lgr logger +lgrLogger := lgr.New(lgr.Debug, lgr.Msec) + +// Convert to slog handler and create slog logger +handler := lgr.ToSlogHandler(lgrLogger) +logger := slog.New(handler) + +// Use standard slog API with lgr formatting +logger.Info("message", "key1", "value1") +// Output: 2023/09/15 10:34:56.789 INFO message key1="value1" +``` + +#### Using slog with lgr interface + +```go +// Create slog handler +jsonHandler := slog.NewJSONHandler(os.Stdout, nil) + +// Wrap it with lgr interface +logger := lgr.FromSlogHandler(jsonHandler) + +// Use lgr API with slog backend +logger.Logf("INFO message with %s", "structured data") +// Output: {"time":"2023-09-15T10:34:56.789Z","level":"INFO","msg":"message with structured data"} +``` + +#### Using slog directly in lgr + +```go +// Create a logger that uses slog directly +jsonHandler := slog.NewJSONHandler(os.Stdout, nil) +logger := lgr.New(lgr.SlogHandler(jsonHandler)) + +// Use lgr API with slog backend +logger.Logf("INFO message") +// Output: {"time":"2023-09-15T10:34:56.789Z","level":"INFO","msg":"message"} +``` + +#### JSON output with caller information + +To get caller information in JSON output when using slog handlers, create the handler with `AddSource: true`: + +```go +// Create JSON handler with source information (caller info) +jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + AddSource: true, // This enables caller information in JSON output +}) + +// Use handler with lgr +logger := lgr.New(lgr.SlogHandler(jsonHandler)) + +logger.Logf("INFO message with caller info") +// Output will include source file, line and function in JSON +``` + +Note: The lgr caller options (`lgr.CallerFile`, `lgr.CallerFunc`, `lgr.CallerPkg`) only work with lgr's native text format +and don't affect JSON output from slog handlers. To include caller information in JSON logs: + +1. For slog JSON handlers: Create the handler with `AddSource: true` as shown above +2. For text-based logs: Use lgr's native caller options without slog integration + +This behavior is designed to respect each logging system's conventions for representing caller information. ### global logger diff --git a/vendor/github.com/go-pkgz/lgr/interface.go b/vendor/github.com/go-pkgz/lgr/interface.go index 46b961a..11ecd01 100644 --- a/vendor/github.com/go-pkgz/lgr/interface.go +++ b/vendor/github.com/go-pkgz/lgr/interface.go @@ -18,7 +18,7 @@ type Func func(format string, args ...interface{}) func (f Func) Logf(format string, args ...interface{}) { f(format, args...) } // NoOp logger -var NoOp = Func(func(format string, args ...interface{}) {}) +var NoOp = Func(func(format string, args ...interface{}) {}) //nolint:revive // Std logger sends to std default logger directly var Std = Func(func(format string, args ...interface{}) { stdlog.Printf(format, args...) }) @@ -30,7 +30,7 @@ func Printf(format string, args ...interface{}) { // Print simplifies replacement of std logger func Print(line string) { - def.logf(line) + def.logf(line) //nolint:govet } // Fatalf simplifies replacement of std logger diff --git a/vendor/github.com/go-pkgz/lgr/logger.go b/vendor/github.com/go-pkgz/lgr/logger.go index 3759437..2d8471c 100644 --- a/vendor/github.com/go-pkgz/lgr/logger.go +++ b/vendor/github.com/go-pkgz/lgr/logger.go @@ -11,8 +11,10 @@ package lgr import ( "bytes" + "context" "fmt" "io" + "log/slog" "os" "path" "regexp" @@ -51,18 +53,19 @@ var ( // Logger provided simple logger with basic support of levels. Thread safe type Logger struct { // set with Option calls - stdout, stderr io.Writer // destination writes for out and err - sameStream bool // stdout and stderr are the same stream - dbg bool // allows reporting for DEBUG level - trace bool // allows reporting for TRACE and DEBUG levels - callerFile bool // reports caller file with line number, i.e. foo/bar.go:89 - callerFunc bool // reports caller function name, i.e. bar.myFunc - callerPkg bool // reports caller package name - levelBraces bool // encloses level with [], i.e. [INFO] - callerDepth int // how many stack frames to skip, relative to the real (reported) frame - format string // layout template - secrets [][]byte // sub-strings to secrets by matching - mapper Mapper // map (alter) output based on levels + stdout, stderr io.Writer // destination writes for out and err + sameStream bool // stdout and stderr are the same stream + dbg bool // allows reporting for DEBUG level + trace bool // allows reporting for TRACE and DEBUG levels + callerFile bool // reports caller file with line number, i.e. foo/bar.go:89 + callerFunc bool // reports caller function name, i.e. bar.myFunc + callerPkg bool // reports caller package name + levelBraces bool // encloses level with [], i.e. [INFO] + callerDepth int // how many stack frames to skip, relative to the real (reported) frame + format string // layout template + secrets [][]byte // sub-strings to secrets by matching + mapper Mapper // map (alter) output based on levels + slogHandler slog.Handler // optional slog handler to delegate logging // internal use now nowFn @@ -161,6 +164,25 @@ func (l *Logger) logf(format string, args ...interface{}) { return } + // if slog handler is set, use it + if l.slogHandler != nil { + // use NewRecord for consistency with adapter setup + // skip=0 because we don't need caller information from this context + record := slog.NewRecord(l.now(), stringToLevel(lv), msg, 0) + _ = l.slogHandler.Handle(context.Background(), record) + + // handle FATAL and PANIC levels as they have special behavior + if lv == "FATAL" || lv == "PANIC" { + if lv == "PANIC" { + // print panic stack trace + stack := getDump() + _, _ = l.stderr.Write([]byte(fmt.Sprintf("\n*** PANIC: %s\n\n%s", msg, stack))) + } + l.fatal() + } + return + } + var ci callerInfo if l.callerOn { // optimization to avoid expensive caller evaluation if caller info not in the template ci = l.reportCaller(l.callerDepth) @@ -231,7 +253,7 @@ func (l *Logger) logf(format string, args ...interface{}) { func (l *Logger) hideSecrets(data []byte) []byte { for _, h := range l.secrets { - data = bytes.Replace(data, h, secretReplacement, -1) + data = bytes.ReplaceAll(data, h, secretReplacement) } return data } diff --git a/vendor/github.com/go-pkgz/lgr/options.go b/vendor/github.com/go-pkgz/lgr/options.go index 16fadac..5225d91 100644 --- a/vendor/github.com/go-pkgz/lgr/options.go +++ b/vendor/github.com/go-pkgz/lgr/options.go @@ -2,6 +2,7 @@ package lgr import ( "io" + "log/slog" "strings" ) @@ -48,11 +49,13 @@ func Format(f string) Option { } // CallerFunc adds caller info with function name. Ignored if Format option used. +// Note: This option only affects lgr's native text format and is ignored when using SlogHandler. func CallerFunc(l *Logger) { l.callerFunc = true } // CallerPkg adds caller's package name. Ignored if Format option used. +// Note: This option only affects lgr's native text format and is ignored when using SlogHandler. func CallerPkg(l *Logger) { l.callerPkg = true } @@ -63,6 +66,7 @@ func LevelBraces(l *Logger) { } // CallerFile adds caller info with file, and line number. Ignored if Format option used. +// Note: This option only affects lgr's native text format and is ignored when using SlogHandler. func CallerFile(l *Logger) { l.callerFile = true } @@ -96,3 +100,34 @@ func Map(m Mapper) Option { func StackTraceOnError(l *Logger) { l.errorDump = true } + +// SlogHandler sets slog.Handler to delegate logging to. When using this option, +// the output format will be controlled by the slog.Handler provided, not by lgr's +// format options. +// +// IMPORTANT: When using lgr.SlogHandler: +// +// 1. To get caller information in JSON output, you must create the handler with +// slog.HandlerOptions{AddSource: true}. +// +// 2. The lgr caller info options (lgr.CallerFile, lgr.CallerFunc) do NOT affect +// JSON output from slog handlers. They only work with lgr's native text format. +// +// Example of correct setup for JSON with caller info: +// +// // create handler with AddSource enabled +// jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ +// AddSource: true, // This enables caller information in JSON output +// }) +// +// // use handler with lgr +// logger := lgr.New(lgr.SlogHandler(jsonHandler)) +// +// For text format with caller info, use lgr's native caller options: +// +// logger := lgr.New(lgr.CallerFile, lgr.CallerFunc) +func SlogHandler(h slog.Handler) Option { + return func(l *Logger) { + l.slogHandler = h + } +} diff --git a/vendor/github.com/go-pkgz/lgr/slog.go b/vendor/github.com/go-pkgz/lgr/slog.go new file mode 100644 index 0000000..b68af07 --- /dev/null +++ b/vendor/github.com/go-pkgz/lgr/slog.go @@ -0,0 +1,228 @@ +package lgr + +import ( + "context" + "fmt" + "log/slog" + "os" + "runtime" + "strings" + "time" +) + +// ToSlogHandler converts lgr.L to slog.Handler +func ToSlogHandler(l L) slog.Handler { + return &lgrSlogHandler{lgr: l} +} + +// FromSlogHandler creates lgr.L wrapper around slog.Handler +func FromSlogHandler(h slog.Handler) L { + return &slogLgrAdapter{handler: h} +} + +// SetupWithSlog sets up the global logger with a slog logger +func SetupWithSlog(logger *slog.Logger) { + options := []Option{SlogHandler(logger.Handler())} + + // check if the slog handler is enabled for debug level + // if so, enable debug mode in lgr to prevent filtering + if logger.Handler().Enabled(context.Background(), slog.LevelDebug) { + options = append(options, Debug) + } + + Setup(options...) +} + +// lgrSlogHandler implements slog.Handler using lgr.L +type lgrSlogHandler struct { + lgr L + attrs []slog.Attr + groups []string +} + +// Enabled implements slog.Handler +func (h *lgrSlogHandler) Enabled(_ context.Context, level slog.Level) bool { + switch { + case level < slog.LevelInfo: // debug, Trace + // check if underlying lgr logger is configured to show debug + // since we can't directly query lgr's debug status, we assume enabled + return true + default: + return true + } +} + +// Handle implements slog.Handler +func (h *lgrSlogHandler) Handle(_ context.Context, record slog.Record) error { + level := levelToString(record.Level) + + // build message with attributes + msg := record.Message + + // add time if record has it, otherwise current time is used by lgr + var timeStr string + if !record.Time.IsZero() { + timeStr = record.Time.Format("2006/01/02 15:04:05.000 ") + } + + // format attributes as key=value pairs + var attrs strings.Builder + if len(h.attrs) > 0 || record.NumAttrs() > 0 { + attrs.WriteString(" ") + } + + // add pre-defined attributes + for _, attr := range h.attrs { + attrs.WriteString(formatAttr(attr, h.groups)) + } + + // add record attributes + record.Attrs(func(attr slog.Attr) bool { + attrs.WriteString(formatAttr(attr, h.groups)) + return true + }) + + // combine everything into final message + logMsg := fmt.Sprintf("%s%s %s%s", timeStr, level, msg, attrs.String()) + h.lgr.Logf(logMsg) + return nil +} + +// WithAttrs implements slog.Handler +func (h *lgrSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + newHandler := &lgrSlogHandler{ + lgr: h.lgr, + attrs: append(h.attrs, attrs...), + groups: h.groups, + } + return newHandler +} + +// WithGroup implements slog.Handler +func (h *lgrSlogHandler) WithGroup(name string) slog.Handler { + newHandler := &lgrSlogHandler{ + lgr: h.lgr, + attrs: h.attrs, + groups: append(h.groups, name), + } + return newHandler +} + +// slogLgrAdapter implements lgr.L using slog.Handler +type slogLgrAdapter struct { + handler slog.Handler +} + +// Logf implements lgr.L interface +func (a *slogLgrAdapter) Logf(format string, args ...interface{}) { + // parse log level from the beginning of the message + msg := fmt.Sprintf(format, args...) + level, msg := extractLevel(msg) + + // create a record with caller information + // skip level is critical: + // - 0 = this line + // - 1 = this function (Logf) + // - 2 = caller of Logf (user code) + // + // note: We use PC=0 to ensure slog.Record.PC() returns 0, + // which causes slog to skip obtaining the caller info itself + record := slog.NewRecord(time.Now(), stringToLevel(level), msg, 2) + + // we need to manually add the source information ourselves, since + // slog.Handler might have AddSource=true but won't get the caller + // right due to how we're adapting lgr → slog + pc, file, line, ok := runtime.Caller(2) // skip to caller of Logf + if ok { + // only add source info if we can find it + funcName := runtime.FuncForPC(pc).Name() + record.AddAttrs( + slog.Group("source", + slog.String("function", funcName), + slog.String("file", file), + slog.Int("line", line), + ), + ) + } + + // handle the record + if err := a.handler.Handle(context.Background(), record); err != nil { + // if handling fails, fallback to stderr + fmt.Fprintf(os.Stderr, "slog handler error: %v\n", err) + } +} + +// Helper functions + +// levelToString converts slog.Level to string representation used by lgr +func levelToString(level slog.Level) string { + switch { + case level < slog.LevelInfo: + if level <= slog.LevelDebug-4 { + return "TRACE" + } + return "DEBUG" + case level < slog.LevelWarn: + return "INFO" + case level < slog.LevelError: + return "WARN" + default: + return "ERROR" + } +} + +// stringToLevel converts lgr level string to slog.Level +func stringToLevel(level string) slog.Level { + switch level { + case "TRACE": + return slog.LevelDebug - 4 + case "DEBUG": + return slog.LevelDebug + case "INFO": + return slog.LevelInfo + case "WARN": + return slog.LevelWarn + case "ERROR", "PANIC", "FATAL": + return slog.LevelError + default: + return slog.LevelInfo + } +} + +// extractLevel parses lgr-style log message to extract level prefix +func extractLevel(msg string) (level, message string) { + for _, lvl := range levels { + prefix := lvl + " " + bracketPrefix := "[" + lvl + "] " + + if strings.HasPrefix(msg, prefix) { + return lvl, strings.TrimPrefix(msg, prefix) + } + if strings.HasPrefix(msg, bracketPrefix) { + return lvl, strings.TrimPrefix(msg, bracketPrefix) + } + } + + return "INFO", msg +} + +// formatAttr converts slog.Attr to string representation +func formatAttr(attr slog.Attr, groups []string) string { + if attr.Equal(slog.Attr{}) { + return "" + } + + key := attr.Key + if len(groups) > 0 { + key = strings.Join(groups, ".") + "." + key + } + + val := attr.Value.String() + + // handle string values specially by quoting them + if attr.Value.Kind() == slog.KindString { + val = fmt.Sprintf("%q", attr.Value.String()) + } + + return fmt.Sprintf("%s=%s ", key, val) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 33055bb..b0ac954 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -24,8 +24,8 @@ github.com/go-chi/render # github.com/go-pkgz/expirable-cache/v3 v3.0.0 ## explicit; go 1.20 github.com/go-pkgz/expirable-cache/v3 -# github.com/go-pkgz/lgr v0.11.1 -## explicit; go 1.20 +# github.com/go-pkgz/lgr v0.12.1 +## explicit; go 1.21 github.com/go-pkgz/lgr # github.com/go-pkgz/rest v1.20.2 ## explicit; go 1.21