Involved Source Filescontext.gocontext_slog.godiscard.go Package logr defines a general-purpose logging API and abstract interfaces
to back that API. Packages in the Go ecosystem can depend on this package,
while callers can implement logging with whatever backend is appropriate.
# Usage
Logging is done using a Logger instance. Logger is a concrete type with
methods, which defers the actual logging to a LogSink interface. The main
methods of Logger are Info() and Error(). Arguments to Info() and Error()
are key/value pairs rather than printf-style formatted strings, emphasizing
"structured logging".
With Go's standard log package, we might write:
log.Printf("setting target value %s", targetValue)
With logr's structured logging, we'd write:
logger.Info("setting target", "value", targetValue)
Errors are much the same. Instead of:
log.Printf("failed to open the pod bay door for user %s: %v", user, err)
We'd write:
logger.Error(err, "failed to open the pod bay door", "user", user)
Info() and Error() are very similar, but they are separate methods so that
LogSink implementations can choose to do things like attach additional
information (such as stack traces) on calls to Error(). Error() messages are
always logged, regardless of the current verbosity. If there is no error
instance available, passing nil is valid.
# Verbosity
Often we want to log information only when the application in "verbose
mode". To write log lines that are more verbose, Logger has a V() method.
The higher the V-level of a log line, the less critical it is considered.
Log-lines with V-levels that are not enabled (as per the LogSink) will not
be written. Level V(0) is the default, and logger.V(0).Info() has the same
meaning as logger.Info(). Negative V-levels have the same meaning as V(0).
Error messages do not have a verbosity level and are always logged.
Where we might have written:
if flVerbose >= 2 {
log.Printf("an unusual thing happened")
}
We can write:
logger.V(2).Info("an unusual thing happened")
# Logger Names
Logger instances can have name strings so that all messages logged through
that instance have additional context. For example, you might want to add
a subsystem name:
logger.WithName("compactor").Info("started", "time", time.Now())
The WithName() method returns a new Logger, which can be passed to
constructors or other functions for further use. Repeated use of WithName()
will accumulate name "segments". These name segments will be joined in some
way by the LogSink implementation. It is strongly recommended that name
segments contain simple identifiers (letters, digits, and hyphen), and do
not contain characters that could muddle the log output or confuse the
joining operation (e.g. whitespace, commas, periods, slashes, brackets,
quotes, etc).
# Saved Values
Logger instances can store any number of key/value pairs, which will be
logged alongside all messages logged through that instance. For example,
you might want to create a Logger instance per managed object:
With the standard log package, we might write:
log.Printf("decided to set field foo to value %q for object %s/%s",
targetValue, object.Namespace, object.Name)
With logr we'd write:
// Elsewhere: set up the logger to log the object name.
obj.logger = mainLogger.WithValues(
"name", obj.name, "namespace", obj.namespace)
// later on...
obj.logger.Info("setting foo", "value", targetValue)
# Best Practices
Logger has very few hard rules, with the goal that LogSink implementations
might have a lot of freedom to differentiate. There are, however, some
things to consider.
The log message consists of a constant message attached to the log line.
This should generally be a simple description of what's occurring, and should
never be a format string. Variable information can then be attached using
named values.
Keys are arbitrary strings, but should generally be constant values. Values
may be any Go value, but how the value is formatted is determined by the
LogSink implementation.
Logger instances are meant to be passed around by value. Code that receives
such a value can call its methods without having to check whether the
instance is ready for use.
The zero logger (= Logger{}) is identical to Discard() and discards all log
entries. Code that receives a Logger by value can simply call it, the methods
will never crash. For cases where passing a logger is optional, a pointer to Logger
should be used.
# Key Naming Conventions
Keys are not strictly required to conform to any specification or regex, but
it is recommended that they:
- be human-readable and meaningful (not auto-generated or simple ordinals)
- be constant (not dependent on input data)
- contain only printable characters
- not contain whitespace or punctuation
- use lower case for simple keys and lowerCamelCase for more complex ones
These guidelines help ensure that log data is processed properly regardless
of the log implementation. For example, log implementations will try to
output JSON data or will store data for later database (e.g. SQL) queries.
While users are generally free to use key names of their choice, it's
generally best to avoid using the following keys, as they're frequently used
by implementations:
- "caller": the calling information (file/line) of a particular log line
- "error": the underlying error value in the `Error` method
- "level": the log level
- "logger": the name of the associated logger
- "msg": the log message
- "stacktrace": the stack trace associated with a particular log line or
error (often from the `Error` message)
- "ts": the timestamp for a log line
Implementations are encouraged to make use of these keys to represent the
above concepts, when necessary (for example, in a pure-JSON output form, it
would be necessary to represent at least message and timestamp as ordinary
named values).
# Break Glass
Implementations may choose to give callers access to the underlying
logging implementation. The recommended pattern for this is:
// Underlier exposes access to the underlying logging implementation.
// Since callers only have a logr.Logger, they have to know which
// implementation is in use, so this interface is less of an abstraction
// and more of way to test type conversion.
type Underlier interface {
GetUnderlying() <underlying-type>
}
Logger grants access to the sink to enable type assertions like this:
func DoSomethingWithImpl(log logr.Logger) {
if underlier, ok := log.GetSink().(impl.Underlier); ok {
implLogger := underlier.GetUnderlying()
...
}
}
Custom `With*` functions can be implemented by copying the complete
Logger struct and replacing the sink in the copy:
// WithFooBar changes the foobar parameter in the log sink and returns a
// new logger with that modified sink. It does nothing for loggers where
// the sink doesn't support that parameter.
func WithFoobar(log logr.Logger, foobar int) logr.Logger {
if foobarLogSink, ok := log.GetSink().(FoobarSink); ok {
log = log.WithSink(foobarLogSink.WithFooBar(foobar))
}
return log
}
Don't use New to construct a new Logger with a LogSink retrieved from an
existing Logger. Source code attribution might not work correctly and
unexported fields in Logger get lost.
Beware that the same LogSink instance may be shared by different logger
instances. Calling functions that modify the LogSink will affect all of
those.sloghandler.goslogr.goslogsink.go
Code Examples
package main
import (
"fmt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
// NewStdoutLogger returns a logr.Logger that prints to stdout.
func NewStdoutLogger() logr.Logger {
return funcr.New(func(prefix, args string) {
if prefix != "" {
fmt.Printf("%s: %s\n", prefix, args)
} else {
fmt.Println(args)
}
}, funcr.Options{})
}
func main() {
l := NewStdoutLogger()
l.Info("default info log", "stringVal", "value", "intVal", 12345)
l.V(0).Info("V(0) info log", "stringVal", "value", "intVal", 12345)
l.Error(fmt.Errorf("an error"), "error log", "stringVal", "value", "intVal", 12345)
}
package main
import (
"errors"
"log/slog"
"os"
"github.com/go-logr/logr"
)
var debugWithoutTime = &slog.HandlerOptions{
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
if a.Key == "time" {
return slog.Attr{}
}
return a
},
Level: slog.LevelDebug,
}
func main() {
logrLogger := logr.FromSlogHandler(slog.NewTextHandler(os.Stdout, debugWithoutTime))
logrLogger.Info("hello world")
logrLogger.Error(errors.New("fake error"), "ignore me")
logrLogger.WithValues("x", 1, "y", 2).WithValues("str", "abc").WithName("foo").WithName("bar").V(4).Info("with values, verbosity and name")
}
package main
import (
"fmt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
// NewStdoutLogger returns a logr.Logger that prints to stdout.
func NewStdoutLogger() logr.Logger {
return funcr.New(func(prefix, args string) {
if prefix != "" {
fmt.Printf("%s: %s\n", prefix, args)
} else {
fmt.Println(args)
}
}, funcr.Options{})
}
func main() {
l := NewStdoutLogger()
if loggerV := l.V(5); loggerV.Enabled() {
// Do something expensive.
loggerV.Info("this is an expensive log message")
}
}
package main
import (
"fmt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
// NewStdoutLogger returns a logr.Logger that prints to stdout.
func NewStdoutLogger() logr.Logger {
return funcr.New(func(prefix, args string) {
if prefix != "" {
fmt.Printf("%s: %s\n", prefix, args)
} else {
fmt.Println(args)
}
}, funcr.Options{})
}
func main() {
l := NewStdoutLogger()
l.Error(fmt.Errorf("the error"), "this is an error log", "stringVal", "value", "intVal", 12345)
l.Error(nil, "this is an error log with nil error", "stringVal", "value", "intVal", 12345)
}
package main
import (
"fmt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
// NewStdoutLogger returns a logr.Logger that prints to stdout.
func NewStdoutLogger() logr.Logger {
return funcr.New(func(prefix, args string) {
if prefix != "" {
fmt.Printf("%s: %s\n", prefix, args)
} else {
fmt.Println(args)
}
}, funcr.Options{})
}
func main() {
l := NewStdoutLogger()
l.Info("this is a V(0)-equivalent info log", "stringVal", "value", "intVal", 12345)
}
package main
import (
"fmt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
// NewStdoutLogger returns a logr.Logger that prints to stdout.
func NewStdoutLogger() logr.Logger {
return funcr.New(func(prefix, args string) {
if prefix != "" {
fmt.Printf("%s: %s\n", prefix, args)
} else {
fmt.Println(args)
}
}, funcr.Options{})
}
func main() {
l := NewStdoutLogger()
l.V(0).Info("V(0) info log")
l.V(1).Info("V(1) info log")
l.V(2).Info("V(2) info log")
}
package main
import (
"fmt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
// NewStdoutLogger returns a logr.Logger that prints to stdout.
func NewStdoutLogger() logr.Logger {
return funcr.New(func(prefix, args string) {
if prefix != "" {
fmt.Printf("%s: %s\n", prefix, args)
} else {
fmt.Println(args)
}
}, funcr.Options{})
}
func main() {
l := NewStdoutLogger()
l = l.WithName("name1")
l.Info("this is an info log", "stringVal", "value", "intVal", 12345)
l = l.WithName("name2")
l.Info("this is an info log", "stringVal", "value", "intVal", 12345)
}
package main
import (
"fmt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
// NewStdoutLogger returns a logr.Logger that prints to stdout.
func NewStdoutLogger() logr.Logger {
return funcr.New(func(prefix, args string) {
if prefix != "" {
fmt.Printf("%s: %s\n", prefix, args)
} else {
fmt.Println(args)
}
}, funcr.Options{})
}
func main() {
l := NewStdoutLogger()
l = l.WithValues("stringVal", "value", "intVal", 12345)
l = l.WithValues("boolVal", true)
l.Info("this is an info log", "floatVal", 3.1415)
}
package main
import (
"github.com/go-logr/logr"
)
// ObjectRef references a Kubernetes object
type ObjectRef struct {
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
}
func (ref ObjectRef) String() string {
if ref.Namespace != "" {
return ref.Namespace + "/" + ref.Name
}
return ref.Name
}
func (ref ObjectRef) MarshalLog() any {
// We implement fmt.Stringer for non-structured logging, but we want the
// raw struct when using structured logs. Some logr implementations call
// String if it is present, so we want to convert this struct to something
// that doesn't have that method.
type forLog ObjectRef // methods do not survive type definitions
return forLog(ref)
}
var _ logr.Marshaler = ObjectRef{}
func main() {
l := NewStdoutLogger()
pod := ObjectRef{Namespace: "kube-system", Name: "some-pod"}
l.Info("as string", "pod", pod.String())
l.Info("as struct", "pod", pod)
}
package main
import (
"github.com/go-logr/logr"
)
// ComplexObjectRef contains more fields than it wants to get logged.
type ComplexObjectRef struct {
Name string
Namespace string
Secret string
}
func (ref ComplexObjectRef) MarshalLog() any {
return struct {
Name, Namespace string
}{
Name: ref.Name,
Namespace: ref.Namespace,
}
}
var _ logr.Marshaler = ComplexObjectRef{}
func main() {
l := NewStdoutLogger()
secret := ComplexObjectRef{Namespace: "kube-system", Name: "some-secret", Secret: "do-not-log-me"}
l.Info("simplified", "secret", secret)
}
package main
import (
"errors"
"fmt"
"log/slog"
"github.com/go-logr/logr"
"github.com/go-logr/logr/funcr"
)
func main() {
funcrLogger := funcr.New(func(prefix, args string) {
if prefix != "" {
fmt.Println(prefix, args)
} else {
fmt.Println(args)
}
}, funcr.Options{
Verbosity: 10,
})
slogLogger := slog.New(logr.ToSlogHandler(funcrLogger))
slogLogger.Info("hello world")
slogLogger.Error("ignore me", "err", errors.New("fake error"))
slogLogger.With("x", 1, "y", 2).WithGroup("group").With("str", "abc").Warn("with values and group")
slogLogger = slog.New(logr.ToSlogHandler(funcrLogger.V(int(-slog.LevelDebug))))
slogLogger.Info("info message reduced to debug level")
}
Package-Level Type Names (total 8)
/* sort by: | */
CallDepthLogSink represents a LogSink that knows how to climb the call stack
to identify the original call site and can offset the depth by a specified
number of frames. This is useful for users who have helper functions
between the "real" call site and the actual calls to Logger methods.
Implementations that log information about the call site (such as file,
function, or line) would otherwise log information about the intermediate
helper functions.
This is an optional interface and implementations are not required to
support it. WithCallDepth returns a LogSink that will offset the call
stack by the specified number of frames when logging call
site information.
If depth is 0, the LogSink should skip exactly the number
of call frames defined in RuntimeInfo.CallDepth when Info
or Error are called, i.e. the attribution should be to the
direct caller of Logger.Info or Logger.Error.
If depth is 1 the attribution should skip 1 call frame, and so on.
Successive calls to this are additive.
CallStackHelperLogSink represents a LogSink that knows how to climb
the call stack to identify the original call site and can skip
intermediate helper functions if they mark themselves as
helper. Go's testing package uses that approach.
This is useful for users who have helper functions between the
"real" call site and the actual calls to Logger methods.
Implementations that log information about the call site (such as
file, function, or line) would otherwise log information about the
intermediate helper functions.
This is an optional interface and implementations are not required
to support it. Implementations that choose to support this must not
simply implement it as WithCallDepth(1), because
Logger.WithCallStackHelper will call both methods if they are
present. This should only be implemented for LogSinks that actually
need it, as with testing.T. GetCallStackHelper returns a function that must be called
to mark the direct caller as helper function when logging
call site information.
Logger is an interface to an abstract logging implementation. This is a
concrete type for performance reasons, but all the real work is passed on to
a LogSink. Implementations of LogSink should provide their own constructors
that return Logger, not LogSink.
The underlying sink can be accessed through GetSink and be modified through
WithSink. This enables the implementation of custom extensions (see "Break
Glass" in the package documentation). Normally the sink should be used only
indirectly. Enabled tests whether this Logger is enabled. For example, commandline
flags might be used to set the logging verbosity and disable some info logs. Error logs an error, with the given message and key/value pairs as context.
It functions similarly to Info, but may have unique behavior, and should be
preferred for logging errors (see the package documentations for more
information). The log message will always be emitted, regardless of
verbosity level.
The msg argument should be used to add context to any underlying error,
while the err argument should be used to attach the actual error that
triggered this log line, if present. The err parameter is optional
and nil may be passed instead of an error instance. GetSink returns the stored sink. GetV returns the verbosity level of the logger. If the logger's LogSink is
nil as in the Discard logger, this will always return 0. Info logs a non-error message with the given key/value pairs as context.
The msg argument should be used to add some constant description to the log
line. The key/value pairs can then be used to add additional variable
information. The key/value pairs must alternate string keys and arbitrary
values. IsZero returns true if this logger is an uninitialized zero value V returns a new Logger instance for a specific verbosity level, relative to
this Logger. In other words, V-levels are additive. A higher verbosity
level means a log message is less important. Negative V-levels are treated
as 0. WithCallDepth returns a Logger instance that offsets the call stack by the
specified number of frames when logging call site information, if possible.
This is useful for users who have helper functions between the "real" call
site and the actual calls to Logger methods. If depth is 0 the attribution
should be to the direct caller of this function. If depth is 1 the
attribution should skip 1 call frame, and so on. Successive calls to this
are additive.
If the underlying log implementation supports a WithCallDepth(int) method,
it will be called and the result returned. If the implementation does not
support CallDepthLogSink, the original Logger will be returned.
To skip one level, WithCallStackHelper() should be used instead of
WithCallDepth(1) because it works with implementions that support the
CallDepthLogSink and/or CallStackHelperLogSink interfaces. WithCallStackHelper returns a new Logger instance that skips the direct
caller when logging call site information, if possible. This is useful for
users who have helper functions between the "real" call site and the actual
calls to Logger methods and want to support loggers which depend on marking
each individual helper function, like loggers based on testing.T.
In addition to using that new logger instance, callers also must call the
returned function.
If the underlying log implementation supports a WithCallDepth(int) method,
WithCallDepth(1) will be called to produce a new logger. If it supports a
WithCallStackHelper() method, that will be also called. If the
implementation does not support either of these, the original Logger will be
returned. WithName returns a new Logger instance with the specified name element added
to the Logger's name. Successive calls with WithName append additional
suffixes to the Logger's name. It's strongly recommended that name segments
contain only letters, digits, and hyphens (see the package documentation for
more information). WithSink returns a copy of the logger with the new sink. WithValues returns a new Logger instance with additional key/value pairs.
See Info for documentation on how key/value pairs work.
Logger : github.com/robfig/cron/v3.Logger
Logger : gopkg.in/yaml.v3.IsZeroer
func Discard() Logger
func FromContext(ctx context.Context) (Logger, error)
func FromContextOrDiscard(ctx context.Context) Logger
func FromSlogHandler(handler slog.Handler) Logger
func New(sink LogSink) Logger
func Logger.V(level int) Logger
func Logger.WithCallDepth(depth int) Logger
func Logger.WithCallStackHelper() (func(), Logger)
func Logger.WithName(name string) Logger
func Logger.WithSink(sink LogSink) Logger
func Logger.WithValues(keysAndValues ...any) Logger
func github.com/go-logr/logr/funcr.New(fn func(prefix, args string), opts funcr.Options) Logger
func github.com/go-logr/logr/funcr.NewJSON(fn func(obj string), opts funcr.Options) Logger
func github.com/go-logr/stdr.New(std stdr.StdLogger) Logger
func github.com/go-logr/stdr.NewWithOptions(std stdr.StdLogger, opts stdr.Options) Logger
func go.opentelemetry.io/otel/internal/global.GetLogger() Logger
func NewContext(ctx context.Context, logger Logger) context.Context
func ToSlogHandler(logger Logger) slog.Handler
func go.opentelemetry.io/otel.SetLogger(logger Logger)
func go.opentelemetry.io/otel/internal/global.SetLogger(l Logger)
LogSink represents a logging implementation. End-users will generally not
interact with this type. Enabled tests whether this LogSink is enabled at the specified V-level.
For example, commandline flags might be used to set the logging
verbosity and disable some info logs. Error logs an error, with the given message and key/value pairs as
context. See Logger.Error for more details. Info logs a non-error message with the given key/value pairs as context.
The level argument is provided for optional logging. This method will
only be called when Enabled(level) is true. See Logger.Info for more
details. Init receives optional information about the logr library for LogSink
implementations that need it. WithName returns a new LogSink with the specified name appended. See
Logger.WithName for more details. WithValues returns a new LogSink with additional key/value pairs. See
Logger.WithValues for more details.SlogSink(interface)
func CallDepthLogSink.WithCallDepth(depth int) LogSink
func Logger.GetSink() LogSink
func LogSink.WithName(name string) LogSink
func LogSink.WithValues(keysAndValues ...any) LogSink
func SlogSink.WithName(name string) LogSink
func SlogSink.WithValues(keysAndValues ...any) LogSink
func New(sink LogSink) Logger
func Logger.WithSink(sink LogSink) Logger
Marshaler is an optional interface that logged values may choose to
implement. Loggers with structured output, such as JSON, should
log the object return by the MarshalLog method instead of the
original value. MarshalLog can be used to:
- ensure that structs are not logged as strings when the original
value has a String method: return a different type without a
String method
- select which fields of a complex type should get logged:
return a simpler struct with fewer fields
- log unexported fields: return a different struct
with exported fields
It may return any value of any type.
go.opentelemetry.io/otel/attribute.Set
*go.opentelemetry.io/otel/exporters/otlp/otlptrace.Exporter
*go.opentelemetry.io/otel/sdk/resource.Resource
RuntimeInfo holds information that the logr "core" library knows which
LogSinks might want to know. CallDepth is the number of call frames the logr library adds between the
end-user and the LogSink. LogSink implementations which choose to print
the original logging site (e.g. file & line) should climb this many
additional frames to find it.
func LogSink.Init(info RuntimeInfo)
func SlogSink.Init(info RuntimeInfo)
func github.com/go-logr/logr/funcr.(*Formatter).Init(info RuntimeInfo)
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. Enabled tests whether this LogSink is enabled at the specified V-level.
For example, commandline flags might be used to set the logging
verbosity and disable some info logs. Error logs an error, with the given message and key/value pairs as
context. See Logger.Error for more details.( SlogSink) Handle(ctx context.Context, record slog.Record) error Info logs a non-error message with the given key/value pairs as context.
The level argument is provided for optional logging. This method will
only be called when Enabled(level) is true. See Logger.Info for more
details. Init receives optional information about the logr library for LogSink
implementations that need it.( SlogSink) WithAttrs(attrs []slog.Attr) SlogSink( SlogSink) WithGroup(name string) SlogSink WithName returns a new LogSink with the specified name appended. See
Logger.WithName for more details. WithValues returns a new LogSink with additional key/value pairs. See
Logger.WithValues for more details.
SlogSink : LogSink
func SlogSink.WithAttrs(attrs []slog.Attr) SlogSink
func SlogSink.WithGroup(name string) SlogSink
Underlier is implemented by the LogSink returned by NewFromLogHandler. GetUnderlying returns the Handler used by the LogSink.
Package-Level Functions (total 9)
Discard returns a Logger that discards all messages logged to it. It can be
used whenever the caller is not interested in the logs. Logger instances
produced by this function always compare as equal.
FromContext returns a Logger from ctx or an error if no Logger is found.
FromContextAsSlogLogger returns a slog.Logger from ctx or nil if no such Logger is found.
FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
returns a Logger that discards all log messages.
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.
New returns a new Logger instance. This is primarily used by libraries
implementing LogSink, rather than end users. Passing a nil sink will create
a Logger which discards all log lines.
NewContext returns a new Context, derived from ctx, which carries the
provided Logger.
NewContextWithSlogLogger returns a new Context, derived from ctx, which carries the
provided slog.Logger.
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, ...)
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.