53 changed files with 1426 additions and 294 deletions
@ -0,0 +1,33 @@
|
||||
/* |
||||
Copyright 2023 The logr Authors. |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
*/ |
||||
|
||||
package logr |
||||
|
||||
// contextKey is how we find Loggers in a context.Context. With Go < 1.21,
|
||||
// the value is always a Logger value. With Go >= 1.21, the value can be a
|
||||
// Logger value or a slog.Logger pointer.
|
||||
type contextKey struct{} |
||||
|
||||
// notFoundError exists to carry an IsNotFound method.
|
||||
type notFoundError struct{} |
||||
|
||||
func (notFoundError) Error() string { |
||||
return "no logr.Logger was present" |
||||
} |
||||
|
||||
func (notFoundError) IsNotFound() bool { |
||||
return true |
||||
} |
||||
@ -0,0 +1,49 @@
|
||||
//go:build !go1.21
|
||||
// +build !go1.21
|
||||
|
||||
/* |
||||
Copyright 2019 The logr Authors. |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
*/ |
||||
|
||||
package logr |
||||
|
||||
import ( |
||||
"context" |
||||
) |
||||
|
||||
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||
func FromContext(ctx context.Context) (Logger, error) { |
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok { |
||||
return v, nil |
||||
} |
||||
|
||||
return Logger{}, notFoundError{} |
||||
} |
||||
|
||||
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||
// returns a Logger that discards all log messages.
|
||||
func FromContextOrDiscard(ctx context.Context) Logger { |
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok { |
||||
return v |
||||
} |
||||
|
||||
return Discard() |
||||
} |
||||
|
||||
// NewContext returns a new Context, derived from ctx, which carries the
|
||||
// provided Logger.
|
||||
func NewContext(ctx context.Context, logger Logger) context.Context { |
||||
return context.WithValue(ctx, contextKey{}, logger) |
||||
} |
||||
@ -0,0 +1,83 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/* |
||||
Copyright 2019 The logr Authors. |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
*/ |
||||
|
||||
package logr |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"log/slog" |
||||
) |
||||
|
||||
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||
func FromContext(ctx context.Context) (Logger, error) { |
||||
v := ctx.Value(contextKey{}) |
||||
if v == nil { |
||||
return Logger{}, notFoundError{} |
||||
} |
||||
|
||||
switch v := v.(type) { |
||||
case Logger: |
||||
return v, nil |
||||
case *slog.Logger: |
||||
return FromSlogHandler(v.Handler()), nil |
||||
default: |
||||
// Not reached.
|
||||
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v)) |
||||
} |
||||
} |
||||
|
||||
// FromContextAsSlogLogger returns a slog.Logger from ctx or nil if no such Logger is found.
|
||||
func FromContextAsSlogLogger(ctx context.Context) *slog.Logger { |
||||
v := ctx.Value(contextKey{}) |
||||
if v == nil { |
||||
return nil |
||||
} |
||||
|
||||
switch v := v.(type) { |
||||
case Logger: |
||||
return slog.New(ToSlogHandler(v)) |
||||
case *slog.Logger: |
||||
return v |
||||
default: |
||||
// Not reached.
|
||||
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v)) |
||||
} |
||||
} |
||||
|
||||
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||
// returns a Logger that discards all log messages.
|
||||
func FromContextOrDiscard(ctx context.Context) Logger { |
||||
if logger, err := FromContext(ctx); err == nil { |
||||
return logger |
||||
} |
||||
return Discard() |
||||
} |
||||
|
||||
// NewContext returns a new Context, derived from ctx, which carries the
|
||||
// provided Logger.
|
||||
func NewContext(ctx context.Context, logger Logger) context.Context { |
||||
return context.WithValue(ctx, contextKey{}, logger) |
||||
} |
||||
|
||||
// NewContextWithSlogLogger returns a new Context, derived from ctx, which carries the
|
||||
// provided slog.Logger.
|
||||
func NewContextWithSlogLogger(ctx context.Context, logger *slog.Logger) context.Context { |
||||
return context.WithValue(ctx, contextKey{}, logger) |
||||
} |
||||
@ -0,0 +1,105 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/* |
||||
Copyright 2023 The logr Authors. |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
*/ |
||||
|
||||
package funcr |
||||
|
||||
import ( |
||||
"context" |
||||
"log/slog" |
||||
|
||||
"github.com/go-logr/logr" |
||||
) |
||||
|
||||
var _ logr.SlogSink = &fnlogger{} |
||||
|
||||
const extraSlogSinkDepth = 3 // 2 for slog, 1 for SlogSink
|
||||
|
||||
func (l fnlogger) Handle(_ context.Context, record slog.Record) error { |
||||
kvList := make([]any, 0, 2*record.NumAttrs()) |
||||
record.Attrs(func(attr slog.Attr) bool { |
||||
kvList = attrToKVs(attr, kvList) |
||||
return true |
||||
}) |
||||
|
||||
if record.Level >= slog.LevelError { |
||||
l.WithCallDepth(extraSlogSinkDepth).Error(nil, record.Message, kvList...) |
||||
} else { |
||||
level := l.levelFromSlog(record.Level) |
||||
l.WithCallDepth(extraSlogSinkDepth).Info(level, record.Message, kvList...) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (l fnlogger) WithAttrs(attrs []slog.Attr) logr.SlogSink { |
||||
kvList := make([]any, 0, 2*len(attrs)) |
||||
for _, attr := range attrs { |
||||
kvList = attrToKVs(attr, kvList) |
||||
} |
||||
l.AddValues(kvList) |
||||
return &l |
||||
} |
||||
|
||||
func (l fnlogger) WithGroup(name string) logr.SlogSink { |
||||
l.startGroup(name) |
||||
return &l |
||||
} |
||||
|
||||
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
|
||||
// and other details of slog.
|
||||
func attrToKVs(attr slog.Attr, kvList []any) []any { |
||||
attrVal := attr.Value.Resolve() |
||||
if attrVal.Kind() == slog.KindGroup { |
||||
groupVal := attrVal.Group() |
||||
grpKVs := make([]any, 0, 2*len(groupVal)) |
||||
for _, attr := range groupVal { |
||||
grpKVs = attrToKVs(attr, grpKVs) |
||||
} |
||||
if attr.Key == "" { |
||||
// slog says we have to inline these
|
||||
kvList = append(kvList, grpKVs...) |
||||
} else { |
||||
kvList = append(kvList, attr.Key, PseudoStruct(grpKVs)) |
||||
} |
||||
} else if attr.Key != "" { |
||||
kvList = append(kvList, attr.Key, attrVal.Any()) |
||||
} |
||||
|
||||
return kvList |
||||
} |
||||
|
||||
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
|
||||
// It ensures that the result is >= 0. This is necessary because the result is
|
||||
// passed to a LogSink and that API did not historically document whether
|
||||
// levels could be negative or what that meant.
|
||||
//
|
||||
// Some example usage:
|
||||
//
|
||||
// logrV0 := getMyLogger()
|
||||
// logrV2 := logrV0.V(2)
|
||||
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
|
||||
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
|
||||
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
|
||||
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
|
||||
func (l fnlogger) levelFromSlog(level slog.Level) int { |
||||
result := -level |
||||
if result < 0 { |
||||
result = 0 // because LogSink doesn't expect negative V levels
|
||||
} |
||||
return int(result) |
||||
} |
||||
@ -0,0 +1,192 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/* |
||||
Copyright 2023 The logr Authors. |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
*/ |
||||
|
||||
package logr |
||||
|
||||
import ( |
||||
"context" |
||||
"log/slog" |
||||
) |
||||
|
||||
type slogHandler struct { |
||||
// May be nil, in which case all logs get discarded.
|
||||
sink LogSink |
||||
// Non-nil if sink is non-nil and implements SlogSink.
|
||||
slogSink SlogSink |
||||
|
||||
// groupPrefix collects values from WithGroup calls. It gets added as
|
||||
// prefix to value keys when handling a log record.
|
||||
groupPrefix string |
||||
|
||||
// levelBias can be set when constructing the handler to influence the
|
||||
// slog.Level of log records. A positive levelBias reduces the
|
||||
// slog.Level value. slog has no API to influence this value after the
|
||||
// handler got created, so it can only be set indirectly through
|
||||
// Logger.V.
|
||||
levelBias slog.Level |
||||
} |
||||
|
||||
var _ slog.Handler = &slogHandler{} |
||||
|
||||
// groupSeparator is used to concatenate WithGroup names and attribute keys.
|
||||
const groupSeparator = "." |
||||
|
||||
// GetLevel is used for black box unit testing.
|
||||
func (l *slogHandler) GetLevel() slog.Level { |
||||
return l.levelBias |
||||
} |
||||
|
||||
func (l *slogHandler) Enabled(_ context.Context, level slog.Level) bool { |
||||
return l.sink != nil && (level >= slog.LevelError || l.sink.Enabled(l.levelFromSlog(level))) |
||||
} |
||||
|
||||
func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error { |
||||
if l.slogSink != nil { |
||||
// Only adjust verbosity level of log entries < slog.LevelError.
|
||||
if record.Level < slog.LevelError { |
||||
record.Level -= l.levelBias |
||||
} |
||||
return l.slogSink.Handle(ctx, record) |
||||
} |
||||
|
||||
// No need to check for nil sink here because Handle will only be called
|
||||
// when Enabled returned true.
|
||||
|
||||
kvList := make([]any, 0, 2*record.NumAttrs()) |
||||
record.Attrs(func(attr slog.Attr) bool { |
||||
kvList = attrToKVs(attr, l.groupPrefix, kvList) |
||||
return true |
||||
}) |
||||
if record.Level >= slog.LevelError { |
||||
l.sinkWithCallDepth().Error(nil, record.Message, kvList...) |
||||
} else { |
||||
level := l.levelFromSlog(record.Level) |
||||
l.sinkWithCallDepth().Info(level, record.Message, kvList...) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// sinkWithCallDepth adjusts the stack unwinding so that when Error or Info
|
||||
// are called by Handle, code in slog gets skipped.
|
||||
//
|
||||
// This offset currently (Go 1.21.0) works for calls through
|
||||
// slog.New(ToSlogHandler(...)). There's no guarantee that the call
|
||||
// chain won't change. Wrapping the handler will also break unwinding. It's
|
||||
// still better than not adjusting at all....
|
||||
//
|
||||
// This cannot be done when constructing the handler because FromSlogHandler needs
|
||||
// access to the original sink without this adjustment. A second copy would
|
||||
// work, but then WithAttrs would have to be called for both of them.
|
||||
func (l *slogHandler) sinkWithCallDepth() LogSink { |
||||
if sink, ok := l.sink.(CallDepthLogSink); ok { |
||||
return sink.WithCallDepth(2) |
||||
} |
||||
return l.sink |
||||
} |
||||
|
||||
func (l *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { |
||||
if l.sink == nil || len(attrs) == 0 { |
||||
return l |
||||
} |
||||
|
||||
clone := *l |
||||
if l.slogSink != nil { |
||||
clone.slogSink = l.slogSink.WithAttrs(attrs) |
||||
clone.sink = clone.slogSink |
||||
} else { |
||||
kvList := make([]any, 0, 2*len(attrs)) |
||||
for _, attr := range attrs { |
||||
kvList = attrToKVs(attr, l.groupPrefix, kvList) |
||||
} |
||||
clone.sink = l.sink.WithValues(kvList...) |
||||
} |
||||
return &clone |
||||
} |
||||
|
||||
func (l *slogHandler) WithGroup(name string) slog.Handler { |
||||
if l.sink == nil { |
||||
return l |
||||
} |
||||
if name == "" { |
||||
// slog says to inline empty groups
|
||||
return l |
||||
} |
||||
clone := *l |
||||
if l.slogSink != nil { |
||||
clone.slogSink = l.slogSink.WithGroup(name) |
||||
clone.sink = clone.slogSink |
||||
} else { |
||||
clone.groupPrefix = addPrefix(clone.groupPrefix, name) |
||||
} |
||||
return &clone |
||||
} |
||||
|
||||
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
|
||||
// and other details of slog.
|
||||
func attrToKVs(attr slog.Attr, groupPrefix string, kvList []any) []any { |
||||
attrVal := attr.Value.Resolve() |
||||
if attrVal.Kind() == slog.KindGroup { |
||||
groupVal := attrVal.Group() |
||||
grpKVs := make([]any, 0, 2*len(groupVal)) |
||||
prefix := groupPrefix |
||||
if attr.Key != "" { |
||||
prefix = addPrefix(groupPrefix, attr.Key) |
||||
} |
||||
for _, attr := range groupVal { |
||||
grpKVs = attrToKVs(attr, prefix, grpKVs) |
||||
} |
||||
kvList = append(kvList, grpKVs...) |
||||
} else if attr.Key != "" { |
||||
kvList = append(kvList, addPrefix(groupPrefix, attr.Key), attrVal.Any()) |
||||
} |
||||
|
||||
return kvList |
||||
} |
||||
|
||||
func addPrefix(prefix, name string) string { |
||||
if prefix == "" { |
||||
return name |
||||
} |
||||
if name == "" { |
||||
return prefix |
||||
} |
||||
return prefix + groupSeparator + name |
||||
} |
||||
|
||||
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
|
||||
// It ensures that the result is >= 0. This is necessary because the result is
|
||||
// passed to a LogSink and that API did not historically document whether
|
||||
// levels could be negative or what that meant.
|
||||
//
|
||||
// Some example usage:
|
||||
//
|
||||
// logrV0 := getMyLogger()
|
||||
// logrV2 := logrV0.V(2)
|
||||
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
|
||||
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
|
||||
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
|
||||
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
|
||||
func (l *slogHandler) levelFromSlog(level slog.Level) int { |
||||
result := -level |
||||
result += l.levelBias // in case the original Logger had a V level
|
||||
if result < 0 { |
||||
result = 0 // because LogSink doesn't expect negative V levels
|
||||
} |
||||
return int(result) |
||||
} |
||||
@ -0,0 +1,100 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/* |
||||
Copyright 2023 The logr Authors. |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
*/ |
||||
|
||||
package logr |
||||
|
||||
import ( |
||||
"context" |
||||
"log/slog" |
||||
) |
||||
|
||||
// FromSlogHandler returns a Logger which writes to the slog.Handler.
|
||||
//
|
||||
// The logr verbosity level is mapped to slog levels such that V(0) becomes
|
||||
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
|
||||
func FromSlogHandler(handler slog.Handler) Logger { |
||||
if handler, ok := handler.(*slogHandler); ok { |
||||
if handler.sink == nil { |
||||
return Discard() |
||||
} |
||||
return New(handler.sink).V(int(handler.levelBias)) |
||||
} |
||||
return New(&slogSink{handler: handler}) |
||||
} |
||||
|
||||
// ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger.
|
||||
//
|
||||
// The returned logger writes all records with level >= slog.LevelError as
|
||||
// error log entries with LogSink.Error, regardless of the verbosity level of
|
||||
// the Logger:
|
||||
//
|
||||
// logger := <some Logger with 0 as verbosity level>
|
||||
// slog.New(ToSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
|
||||
//
|
||||
// The level of all other records gets reduced by the verbosity
|
||||
// level of the Logger and the result is negated. If it happens
|
||||
// to be negative, then it gets replaced by zero because a LogSink
|
||||
// is not expected to handled negative levels:
|
||||
//
|
||||
// slog.New(ToSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
|
||||
// slog.New(ToSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
|
||||
// slog.New(ToSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
|
||||
// slog.New(ToSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
|
||||
func ToSlogHandler(logger Logger) slog.Handler { |
||||
if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 { |
||||
return sink.handler |
||||
} |
||||
|
||||
handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())} |
||||
if slogSink, ok := handler.sink.(SlogSink); ok { |
||||
handler.slogSink = slogSink |
||||
} |
||||
return handler |
||||
} |
||||
|
||||
// SlogSink is an optional interface that a LogSink can implement to support
|
||||
// logging through the slog.Logger or slog.Handler APIs better. It then should
|
||||
// also support special slog values like slog.Group. When used as a
|
||||
// slog.Handler, the advantages are:
|
||||
//
|
||||
// - stack unwinding gets avoided in favor of logging the pre-recorded PC,
|
||||
// as intended by slog
|
||||
// - proper grouping of key/value pairs via WithGroup
|
||||
// - verbosity levels > slog.LevelInfo can be recorded
|
||||
// - less overhead
|
||||
//
|
||||
// Both APIs (Logger and slog.Logger/Handler) then are supported equally
|
||||
// well. Developers can pick whatever API suits them better and/or mix
|
||||
// packages which use either API in the same binary with a common logging
|
||||
// implementation.
|
||||
//
|
||||
// This interface is necessary because the type implementing the LogSink
|
||||
// interface cannot also implement the slog.Handler interface due to the
|
||||
// different prototype of the common Enabled method.
|
||||
//
|
||||
// An implementation could support both interfaces in two different types, but then
|
||||
// additional interfaces would be needed to convert between those types in FromSlogHandler
|
||||
// and ToSlogHandler.
|
||||
type SlogSink interface { |
||||
LogSink |
||||
|
||||
Handle(ctx context.Context, record slog.Record) error |
||||
WithAttrs(attrs []slog.Attr) SlogSink |
||||
WithGroup(name string) SlogSink |
||||
} |
||||
@ -0,0 +1,120 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/* |
||||
Copyright 2023 The logr Authors. |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
*/ |
||||
|
||||
package logr |
||||
|
||||
import ( |
||||
"context" |
||||
"log/slog" |
||||
"runtime" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
_ LogSink = &slogSink{} |
||||
_ CallDepthLogSink = &slogSink{} |
||||
_ Underlier = &slogSink{} |
||||
) |
||||
|
||||
// Underlier is implemented by the LogSink returned by NewFromLogHandler.
|
||||
type Underlier interface { |
||||
// GetUnderlying returns the Handler used by the LogSink.
|
||||
GetUnderlying() slog.Handler |
||||
} |
||||
|
||||
const ( |
||||
// nameKey is used to log the `WithName` values as an additional attribute.
|
||||
nameKey = "logger" |
||||
|
||||
// errKey is used to log the error parameter of Error as an additional attribute.
|
||||
errKey = "err" |
||||
) |
||||
|
||||
type slogSink struct { |
||||
callDepth int |
||||
name string |
||||
handler slog.Handler |
||||
} |
||||
|
||||
func (l *slogSink) Init(info RuntimeInfo) { |
||||
l.callDepth = info.CallDepth |
||||
} |
||||
|
||||
func (l *slogSink) GetUnderlying() slog.Handler { |
||||
return l.handler |
||||
} |
||||
|
||||
func (l *slogSink) WithCallDepth(depth int) LogSink { |
||||
newLogger := *l |
||||
newLogger.callDepth += depth |
||||
return &newLogger |
||||
} |
||||
|
||||
func (l *slogSink) Enabled(level int) bool { |
||||
return l.handler.Enabled(context.Background(), slog.Level(-level)) |
||||
} |
||||
|
||||
func (l *slogSink) Info(level int, msg string, kvList ...interface{}) { |
||||
l.log(nil, msg, slog.Level(-level), kvList...) |
||||
} |
||||
|
||||
func (l *slogSink) Error(err error, msg string, kvList ...interface{}) { |
||||
l.log(err, msg, slog.LevelError, kvList...) |
||||
} |
||||
|
||||
func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) { |
||||
var pcs [1]uintptr |
||||
// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
|
||||
runtime.Callers(3+l.callDepth, pcs[:]) |
||||
|
||||
record := slog.NewRecord(time.Now(), level, msg, pcs[0]) |
||||
if l.name != "" { |
||||
record.AddAttrs(slog.String(nameKey, l.name)) |
||||
} |
||||
if err != nil { |
||||
record.AddAttrs(slog.Any(errKey, err)) |
||||
} |
||||
record.Add(kvList...) |
||||
_ = l.handler.Handle(context.Background(), record) |
||||
} |
||||
|
||||
func (l slogSink) WithName(name string) LogSink { |
||||
if l.name != "" { |
||||
l.name += "/" |
||||
} |
||||
l.name += name |
||||
return &l |
||||
} |
||||
|
||||
func (l slogSink) WithValues(kvList ...interface{}) LogSink { |
||||
l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...)) |
||||
return &l |
||||
} |
||||
|
||||
func kvListToAttrs(kvList ...interface{}) []slog.Attr { |
||||
// We don't need the record itself, only its Add method.
|
||||
record := slog.NewRecord(time.Time{}, 0, "", 0) |
||||
record.Add(kvList...) |
||||
attrs := make([]slog.Attr, 0, record.NumAttrs()) |
||||
record.Attrs(func(attr slog.Attr) bool { |
||||
attrs = append(attrs, attr) |
||||
return true |
||||
}) |
||||
return attrs |
||||
} |
||||
@ -1,3 +1,4 @@
|
||||
# Patterns for files created by this project. |
||||
# For other files, use global gitignore. |
||||
*.s3db |
||||
.idea |
||||
|
||||
@ -0,0 +1,46 @@
|
||||
package msgpack |
||||
|
||||
import ( |
||||
"reflect" |
||||
"sync" |
||||
) |
||||
|
||||
var cachedValues struct { |
||||
m map[reflect.Type]chan reflect.Value |
||||
sync.RWMutex |
||||
} |
||||
|
||||
func cachedValue(t reflect.Type) reflect.Value { |
||||
cachedValues.RLock() |
||||
ch := cachedValues.m[t] |
||||
cachedValues.RUnlock() |
||||
if ch != nil { |
||||
return <-ch |
||||
} |
||||
|
||||
cachedValues.Lock() |
||||
defer cachedValues.Unlock() |
||||
if ch = cachedValues.m[t]; ch != nil { |
||||
return <-ch |
||||
} |
||||
|
||||
ch = make(chan reflect.Value, 256) |
||||
go func() { |
||||
for { |
||||
ch <- reflect.New(t) |
||||
} |
||||
}() |
||||
if cachedValues.m == nil { |
||||
cachedValues.m = make(map[reflect.Type]chan reflect.Value, 8) |
||||
} |
||||
cachedValues.m[t] = ch |
||||
return <-ch |
||||
} |
||||
|
||||
func (d *Decoder) newValue(t reflect.Type) reflect.Value { |
||||
if d.flags&usePreallocateValues == 0 { |
||||
return reflect.New(t) |
||||
} |
||||
|
||||
return cachedValue(t) |
||||
} |
||||
@ -1,4 +1,4 @@
|
||||
{ |
||||
"name": "msgpack", |
||||
"version": "5.3.5" |
||||
"version": "5.4.1" |
||||
} |
||||
|
||||
Loading…
Reference in new issue