Documentation
¶
Overview ¶
Package slogx contains extensions for log/slog.
Handlers ¶
- LayoutHandler is a drop-in replacement for slog.TextHandler with configurable key formatting and ordering, designed for compact and easy to read output. See LayoutHandlerOptions for details and examples.
- NewContextHandler creates a handler that delegates to another handler stored in a context. This allows to add attributes and groups into context using ContextWith, ContextWithAttrs, and ContextWithGroup functions and have them automatically included in log records when using default logger functions like slog.InfoContext etc. NewDefaultContextLogger provides a compatibility with third-party libraries which use non-Context-aware slog functions/methods. NewContextMiddleware provides a compatibility with github.com/samber/slog-multi.Pipe.
- WrapHandler is a building block for handlers that want to wrap another handler. See WrapHandlerConfig for details. NewWrapMiddleware provides a compatibility with github.com/samber/slog-multi.Pipe.
Helpers ¶
- ChainReplaceAttr allows to run multiple functions using slog.HandlerOptions.ReplaceAttr.
- Stack is a pre-defined attribute that resolves to a stack trace formatted as panic output.
- NewError and NewErrorAttrs attach slog attributes to an error, to log them later (when the error is logged) using ReplaceAttr function returned by ErrorAttrs. Use ErrorStack to add a stack trace to the error attributes.
- LogSkip and LogAttrsSkip are building blocks for your own logging helpers, allowing to skip stack frames when reporting the caller by slog.HandlerOptions.AddSource. Use StackSkip to add a stack trace in such helpers.
Index ¶
- Constants
- Variables
- func ChainReplaceAttr(fs ...func([]string, slog.Attr) slog.Attr) func([]string, slog.Attr) slog.Attr
- func ContextWith(ctx context.Context, args ...any) context.Context
- func ContextWithAttrs(ctx context.Context, attrs ...slog.Attr) context.Context
- func ContextWithGroup(ctx context.Context, group string) context.Context
- func ErrorAttrs(opts ...ErrorAttrsOption) func(groups []string, attr slog.Attr) slog.Attr
- func ErrorStack() slog.Attr
- func LogAttrsSkip(ctx context.Context, skip int, handler slog.Handler, level slog.Level, ...)
- func LogSkip(ctx context.Context, skip int, handler slog.Handler, level slog.Level, ...)
- func NewContextHandler(ctx context.Context, next slog.Handler, opts ...ContextHandlerOption) (ctxWithNext context.Context, contextHandler slog.Handler)
- func NewDefaultContextLogger(defaultCtx context.Context, logger *slog.Logger) *slog.Logger
- func NewError(err error, args ...any) error
- func NewErrorAttrs(err error, attrs ...slog.Attr) error
- func NewLayoutHandler(w io.Writer, opts *LayoutHandlerOptions) slog.Handler
- func SetDefaultContextHandler(ctx context.Context, next slog.Handler, opts ...ContextHandlerOption) context.Context
- func StackSkip(skip int) slog.Attr
- type ContextHandlerOption
- type ErrorAttrsOption
- type GroupOrAttrs
- type LayoutHandler
- type LayoutHandlerOptions
- type Middleware
- type WrapHandler
- type WrapHandlerConfig
Examples ¶
- LayoutHandlerOptions (FormatColorAttr)
- LayoutHandlerOptions (FormatCustomAttrs)
- LayoutHandlerOptions (FormatRedact)
- LayoutHandlerOptions (FormatRemoveAttr)
- LayoutHandlerOptions (FormatShortenLevel)
- LayoutHandlerOptions (FormatTruncate)
- LayoutHandlerOptions (FormatUnquoted)
- LayoutHandlerOptions (PrefixVerticalAlign)
- LayoutHandlerOptions (TimeFormat)
Constants ¶
const StackKey = "stack"
StackKey is the key used by the Stack, StackSkip and ErrorStack for the stack trace attribute.
Variables ¶
var Stack = StackSkip(0) // Const.
Stack is a pre-defined attribute that resolves to a stack trace formatted as panic output.
Unlike ErrorStack it captures the stack trace when calling slogx.Stack.Value.Resolve(), which usually happens during log call processing. This makes Stack suitable for use in log calls that may be disabled by the current log level, but it also means that the stack trace will point to the log call location, not to the location where Stack was referenced (as in ErrorStack case).
It excludes stack frames up to and including the last log/slog call from the stack trace.
Example usage:
// Will format the stack trace only if Debug log level is enabled.
slog.Debug("something", slogx.Stack)
Functions ¶
func ChainReplaceAttr ¶
func ChainReplaceAttr(fs ...func([]string, slog.Attr) slog.Attr) func([]string, slog.Attr) slog.Attr
ChainReplaceAttr returns a function suitable for using in slog.HandlerOptions.ReplaceAttr which actually executes several such functions in a chain. All these functions will get same first arg, but second arg will be value returned by previous function in a chain.
If one of chained functions will return zero slog.Attr or an attr with value of kind slog.KindGroup then it skips next functions in a chain and returns this attr.
func ContextWith ¶ added in v0.2.0
ContextWith applies slog attrs specified by args to a handler stored in ctx by NewContextHandler.
Panics if ctx does not contain a handler.
func ContextWithAttrs ¶
ContextWithAttrs applies attrs to a handler stored in ctx by NewContextHandler.
Panics if ctx does not contain a handler.
func ContextWithGroup ¶
ContextWithGroup applies group to a handler stored in ctx by NewContextHandler.
Panics if ctx does not contain a handler.
func ErrorAttrs ¶ added in v0.2.0
ErrorAttrs returns an slog.HandlerOptions.ReplaceAttr function that will replace attr's Value of error type with slog.GroupValue containing all attrs attached (by NewError or NewErrorAttrs) to any of recursively unwrapped errors plus attr with error (stripped of attached attrs) itself at the end.
By default returned attr's Key depends on groups: if groups are empty then Key will be empty, otherwise Key will be attr's Key. In other words, error attrs are inlined at top level and grouped at sub levels. This behaviour may be changed by given options.
If attr's Value is not of error type or error has no attached attrs then returns original attr.
func ErrorStack ¶ added in v0.2.0
ErrorStack returns a stack trace formatted as panic output.
It formats a stack trace when called, so it is not a good choice for using in log calls that are disabled by the current log level because of the performance penalty (in this case use Stack instead). ErrorStack is intended to be used as a parameter to NewError or NewErrorAttrs to capture a stack trace at the point where an error happens.
It excludes a call of ErrorStack itself from the stack trace.
Example usage:
err := doOperation()
if err != nil {
return slogx.NewErrorAttrs(err, slogx.ErrorStack())
}
// Later in the call chain, higher up the stack:
slog.Error("operation failed", "err", err) // Will include stack trace from return line.
func LogAttrsSkip ¶
func LogAttrsSkip(ctx context.Context, skip int, handler slog.Handler, level slog.Level, msg string, attrs ...slog.Attr)
LogAttrsSkip emits a log record using handler with the current time and the given level and message. It is useful for logging from your own logging helpers.
Value skip=0 works exactly like *slog.Logger.LogAttrs, value skip=1 skips caller of LogAttrsSkip etc.
func LogSkip ¶
func LogSkip(ctx context.Context, skip int, handler slog.Handler, level slog.Level, msg string, args ...any)
LogSkip emits a log record using handler with the current time and the given level and message. It is useful for logging from your own logging helpers.
Value skip=0 works exactly like *slog.Logger.Log, value skip=1 skips caller of LogSkip etc.
func NewContextHandler ¶ added in v0.2.0
func NewContextHandler(ctx context.Context, next slog.Handler, opts ...ContextHandlerOption) ( ctxWithNext context.Context, contextHandler slog.Handler, )
NewContextHandler creates an slog.Handler and a context with next handler inside.
Returned contextHandler provides a way to use an slog.Handler stored in a context. This makes possible to store attrs and groups inside a (handler in a) context and make it work with default logger functions (like slog.InfoContext) without extra efforts (like getting logger from context first or providing logger explicitly in function arguments).
Use ContextWith, ContextWithAttrs and ContextWithGroup functions to add attrs and groups to a handler stored in a context.
Follow these rules to use contextHandler correctly:
- You should set returned contextHandler as a default logger's handler. Without this it will be useless, because if you'll use non-default logger instance everythere then you can add attrs to it directly and there is no need in contextHandler.
- You should use returned ctxWithNext as a base context for your application.
- Your application code should use Context-aware slog functions/methods for logging.
- Use NewDefaultContextLogger to create a logger instance for third-party libraries which do not support Context-aware logging functions but support custom logger instance.
- Do not use ContextWith, ContextWithAttrs and ContextWithGroup functions after slog.With* functions or (*slog.Logger).With* methods calls.
By default contextHandler will add attr with key "!BADCTX" and current context value if it does not contain a handler - this helps to detect violations of the rules above. Use LaxContextHandler option to disable this behaviour when using third-party libraries which do not support Context-aware logging functions and do not accept custom logger instance.
If you won't use Context-aware slog functions/methods for logging or will use them with a context not based on ctxWithNext then next handler will be used as a fallback (thus attrs and groups stored in context will be ignored). Even if your application code is correct, this may still happen in third-party libraries which do not support Context-aware logging functions and do not accept custom logger instance.
Example usage in an HTTP server:
func main() {
handler := slog.NewJSONHandler(os.Stdout, nil)
ctx := slogx.SetDefaultContextHandler(context.Background(), handler)
ctx = slogx.ContextWith(ctx, "app", "example")
// ...
srv := &http.Server{
BaseContext: func(net.Listener) context.Context { return ctx },
//...
}
srv.ListenAndServe()
slog.InfoContext(ctx, "finished") // Will log "app" attribute.
}
func (handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = slogx.ContextWith(ctx, "remote_addr", r.RemoteAddr)
slog.InfoContext(ctx, "message") // Will log "app" and "remote_addr" attributes.
}
func NewDefaultContextLogger ¶ added in v0.2.0
NewDefaultContextLogger creates a new *slog.Logger which uses defaultCtx when calling logging methods without context (like *slog.Logger.Info).
This is useful to provide logger for third-party libraries which do not use context-aware logging methods but support custom logger instance.
func NewError ¶ added in v0.2.0
NewError returns err with attached slog attrs specified by args.
You should use slog.HandlerOptions.ReplaceAttr function returned by ErrorAttrs to make slog log these attrs.
If err is nil then returns nil.
func NewErrorAttrs ¶ added in v0.2.0
NewErrorAttrs returns err with attached slog attrs.
You should use slog.HandlerOptions.ReplaceAttr function returned by ErrorAttrs to make slog log these attrs.
If err is nil then returns nil.
func NewLayoutHandler ¶ added in v0.2.0
func NewLayoutHandler(w io.Writer, opts *LayoutHandlerOptions) slog.Handler
NewLayoutHandler creates a new LayoutHandler that writes to w, using the given options.
Panics if opts.Format contains an invalid format.
func SetDefaultContextHandler ¶ added in v0.2.0
func SetDefaultContextHandler(ctx context.Context, next slog.Handler, opts ...ContextHandlerOption) context.Context
SetDefaultContextHandler sets a contextHandler returned by NewContextHandler as a default logger's handler and returns a context with next handler inside. It is a shortcut for NewContextHandler + slog.SetDefault.
See NewContextHandler for details and usage rules.
func StackSkip ¶ added in v0.2.0
StackSkip returns an attribute that resolves to a stack trace formatted as panic output.
It works like Stack, but skips the given number of extra stack frames. Negative skip values are treated as zero.
Example usage:
func LogHelper(msg string, args ...any) {
const skip = 1 // LogHelper added one extra stack frame.
ctx := context.Background()
h := slog.Default().Handler()
// Skip LogHelper frame from stack.
args = append(args, slogx.StackSkip(skip))
// Skip LogHelper frame from source.
slogx.LogSkip(ctx, skip, h, slog.LevelInfo, msg, args...)
}
Types ¶
type ContextHandlerOption ¶ added in v0.2.0
type ContextHandlerOption func(*contextHandlerConfig)
ContextHandlerOption is an option for NewContextHandler.
func LaxContextHandler ¶ added in v0.2.0
func LaxContextHandler() ContextHandlerOption
LaxContextHandler is an option for disable adding !BADCTX attr.
type ErrorAttrsOption ¶ added in v0.2.0
type ErrorAttrsOption func(*errorAttrsConfig)
ErrorAttrsOption is an option for ErrorAttrs.
func GroupTopErrorAttrs ¶ added in v0.2.0
func GroupTopErrorAttrs() ErrorAttrsOption
GroupTopErrorAttrs makes error attrs to be grouped at top level (when groups is empty).
func InlineSubErrorAttrs ¶ added in v0.2.0
func InlineSubErrorAttrs() ErrorAttrsOption
InlineSubErrorAttrs makes error attrs to be inlined at sub levels (when groups is not empty).
type GroupOrAttrs ¶ added in v0.2.0
type GroupOrAttrs struct {
// contains filtered or unexported fields
}
GroupOrAttrs holds a sequence of WithGroup and WithAttrs calls.
It is a useful helper for implementing slog.Handler.
Zero value and nil are valid and represent no groups or attrs.
func (*GroupOrAttrs) All ¶ added in v0.2.0
All yields all groups and attrs in the order they were added. It yields either an attr with an empty group name, or a group name with an empty attr. If there are no groups or attrs, All yields nothing.
func (*GroupOrAttrs) Record ¶ added in v0.2.0
func (g *GroupOrAttrs) Record(r slog.Record) slog.Record
Record returns a record that includes all groups and attrs in g and r.
func (*GroupOrAttrs) Total ¶ added in v0.2.0
func (g *GroupOrAttrs) Total() int
Total returns the total number of attrs and groups added to g.
func (*GroupOrAttrs) WithAttrs ¶ added in v0.2.0
func (g *GroupOrAttrs) WithAttrs(attrs []slog.Attr) *GroupOrAttrs
WithAttrs returns a GroupOrAttrs that includes the given attrs. If there are no attrs or all attrs are empty groups, g is returned unchanged.
func (*GroupOrAttrs) WithGroup ¶ added in v0.2.0
func (g *GroupOrAttrs) WithGroup(name string) *GroupOrAttrs
WithGroup returns a GroupOrAttrs that includes the given group. If name is empty, g is returned unchanged.
type LayoutHandler ¶ added in v0.2.0
type LayoutHandler struct {
// contains filtered or unexported fields
}
LayoutHandler is a handler created by NewLayoutHandler that writes slog.Record to an io.Writer in a text format designed for compact and easy to read output.
It is a drop-in replacement for slog.TextHandler and implemented using modified slog.TextHandler code, so it has exactly same behaviour and similar performance when not using any of the extra options.
To get improved output you should define order and formatting for some of the attributes you use in your application (see LayoutHandlerOptions for details and examples).
func (*LayoutHandler) Enabled ¶ added in v0.2.0
Enabled implements slog.Handler interface.
func (*LayoutHandler) Handle ¶ added in v0.2.0
Handle implements slog.Handler interface.
func (*LayoutHandler) WithAttrs ¶ added in v0.2.0
func (h *LayoutHandler) WithAttrs(attrs []slog.Attr) slog.Handler
WithAttrs implements slog.Handler interface.
func (*LayoutHandler) WithGroup ¶ added in v0.2.0
func (h *LayoutHandler) WithGroup(name string) slog.Handler
WithGroup implements slog.Handler interface.
type LayoutHandlerOptions ¶ added in v0.2.0
type LayoutHandlerOptions struct {
// AddSource causes the handler to compute the source code position
// of the log statement and add a SourceKey attribute to the output.
AddSource bool
// Level reports the minimum record level that will be logged.
// The handler discards records with lower levels.
// If Level is nil, the handler assumes LevelInfo.
// The handler calls Level.Level for each record processed;
// to adjust the minimum level dynamically, use a LevelVar.
Level slog.Leveler
// ReplaceAttr is called to rewrite each non-group attribute before it is logged.
// The attribute's value has been resolved (see [Value.Resolve]).
// If ReplaceAttr returns a zero Attr, the attribute is discarded.
//
// The built-in attributes with keys "time", "level", "source", and "msg"
// are passed to this function, except that time is omitted
// if zero, and source is omitted if AddSource is false.
//
// The first argument is a list of currently open groups that contain the
// Attr. It must not be retained or modified. ReplaceAttr is never called
// for Group attributes, only their contents. For example, the attribute
// list
//
// Int("a", 1), Group("g", Int("b", 2)), Int("c", 3)
//
// results in consecutive calls to ReplaceAttr with the following arguments:
//
// nil, Int("a", 1)
// []string{"g"}, Int("b", 2)
// nil, Int("c", 3)
//
// ReplaceAttr can be used to change the default keys of the built-in
// attributes, convert types (for example, to replace a `time.Time` with the
// integer seconds since the Unix epoch), sanitize personal information, or
// remove attributes from the output.
ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
// RecordTimeFormat specifies the time format for the built-in slog.TimeKey attribute
// instead of default (RFC3339 with millisecond precision).
RecordTimeFormat string
// TimeFormat specifies the time format for user-defined time.Time attributes
// instead of default (RFC3339 with millisecond precision).
TimeFormat string
// Format specifies per-attribute formatting options.
//
// If an attribute's key is present in the map,
// the corresponding formatting options are applied when outputting the attribute,
// otherwise the attribute is output in the default slog.TextHandler format.
//
// Key should be the full key, including group prefixes separated by '.'.
//
// All attributes included in Format are output without attribute separator (' '),
// key and '='. Include these parts in format as prefix if needed.
//
// Use empty string format to remove the attr from output.
// Use format without %v or %s verb to hide the actual value.
//
// The format is mostly a subset (just one extension) of the fmt package formats:
//
// - Single '%v' or '%s' verb with optional flags, minimum and maximum width.
// - '%v' is value with default slog.TextHandler formatting (with quoting as needed).
// - '%s' is value with slog.TextHandler formatting without quoting.
// - Flag '-' for left alignment (default is right alignment).
// - Minimum width for padding value with spaces.
// - Positive maximum width for truncating value from the end if longer.
// - Negative maximum width for truncating value from the beginning if longer.
// (This is the only extension beyond fmt formats: accepting '-' after '.'.)
// - '%%' for a '%'
// - Other characters are output verbatim.
//
// Examples:
//
// "%-5v" - only value without attr separator, left aligned, minimum width 5
// " %10v" - only value, right aligned, minimum width 10
// " %.10v" - only value, maximum width 10 (output is truncated if longer)
// " %.-10v" - same as above, but value is truncated from the beginning
// " key=%-10.8v" - left aligned, min width 10, max width 8 (right padded 2+ spaces)
// " group.key=%v" - when used for key "group.key" will result in default output
// (but always with a space prefix even if it's the first attribute)
// " pass=REDACTED"- when used for key "pass" will hide the actual value
// "" - attribute is removed from output
// "\n%s" - unquoted multiline value starting on a new line
//
// Special cases:
// - For slog.LevelKey minimum=3 and maximum=3 will result in short level names:
// "DBG", "INF", "WRN", "ERR", "D±n", "I+n", "W+n", "E+n".
//
// If two keys are output next to each other (e.g. "host" and "port") then it is
// useful to include a custom separator (e.g. ':') in the format of the second key.
// For example: {"host": " [%s", "port": ":%s]"} will output " [example.com:80]".
//
// NewLayoutHandler will panic is format is invalid
// (unknown flag/verb after '%', more than one verb).
Format map[string]string
// PrefixKeys specifies keys that, if present, output just before the message key,
// in order given by the slice.
//
// Key should be the full key, including group prefixes separated by '.'.
//
// If multiple attributes have the same key only the last one is output.
// If slog.MessageKey is present in PrefixKeys, it is ignored.
// If same key is present multiple times in PrefixKeys, all but the first are ignored.
// If same key is present in both PrefixKeys and SuffixKeys, it is output as a prefix.
//
// Keys not present in PrefixKeys and SuffixKeys are output as usual,
// between the message and the suffix keys, in order they were added.
PrefixKeys []string
// SuffixKeys specifies keys that, if present, output after all other attributes,
// in order given by the slice.
//
// Key should be the full key, including group prefixes separated by '.'.
//
// If multiple attributes have the same key only the last one is output.
// If slog.MessageKey is present in SuffixKeys, it is ignored.
// If same key is present multiple times in SuffixKeys, all but the first are ignored.
// If same key is present in both PrefixKeys and SuffixKeys, it is output as a prefix.
//
// Keys not present in PrefixKeys and SuffixKeys are output as usual,
// between the message and the suffix keys, in order they were added.
SuffixKeys []string
}
LayoutHandlerOptions contains options for NewLayoutHandler. These options extend slog.HandlerOptions to define output layout by controlling attributes order and formatting.
PrefixKeys and SuffixKeys makes it possible to reorder attributes (including built-in attributes except slog.MessageKey) to appear right before message or at the end of the output respectively, in the fixed order defined by these slices.
Format makes it possible to:
- Remove attribute from output (including built-in attributes). This is more convenient than using ReplaceAttr for this purpose.
- Hide sensitive attribute value (e.g. secret). This can be used as an additional layer of protection besides slog.LogValuer and ReplaceAttr. Unlike removing the attribute, it makes possible to notice the attempt to log the sensitive value without exposing the actual value.
- Ensure vertical alignment for attributes output before message by adding padding to the left or right of the value and truncating value from the end or the beginning.
- Output bare values without "key=" and/or attribute separator. This allows more compact output for attributes which meaning is obvious from their value or position (e.g. time, level, HTTP method, host:port, etc).
- Compact output for built-in attribute level by using short level names.
- Disable quoting to avoid cluttering the output with extra escaping backslashes when the value is already in a safe format (e.g. JSON, Go syntax, etc). This should be used with care to avoid misleading output.
- Disable quoting to output multiline values (e.g. stack traces, JSON, Go syntax, etc). This should be used with care to avoid misleading output.
- Add custom prefix/suffix to attribute value (ANSI colors, brackets, etc).
Here is an example configuration which ensures vertical alignment for message:
Format: map[string]string{
slog.LevelKey: " level=%3.3s", // Use alternative short level names.
},
SuffixKeys: []string{
slog.SourceKey, // Can be truncated and padded instead of moving to the end.
},
Example (FormatColorAttr) ¶
package main
import (
"io"
"log/slog"
"os"
"github.com/powerman/slogx"
)
func main() {
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by adding color to specific fields.
opts := slogx.LayoutHandlerOptions{
Format: map[string]string{
slog.TimeKey: "", // Omit time field for predictable output.
"err": " err=\x1b[91m%v\x1b[0m", // Bright red color.
},
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
logger.Error("Test message", "status", 500, "err", io.EOF)
}
Output: level=ERROR msg="Test message" status=500 err=�[91mEOF�[0m
Example (FormatCustomAttrs) ¶
package main
import (
"log/slog"
"os"
"github.com/powerman/slogx"
)
func main() {
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by adding custom formatted fields.
opts := slogx.LayoutHandlerOptions{
Format: map[string]string{
slog.TimeKey: "", // Omit time field for predictable output.
"host": " %v", // Omit field name.
"port": ":%v", // Omit field name and separate with colon.
},
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
// Order of host and port attributes does matter - port must follow host.
logger.Info("Test message", "a", 1, "host", "localhost", "port", 8080, "z", true)
}
Output: level=INFO msg="Test message" a=1 localhost:8080 z=true
Example (FormatRedact) ¶
package main
import (
"log/slog"
"os"
"github.com/powerman/slogx"
)
func main() {
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by redacting sensitive fields.
opts := slogx.LayoutHandlerOptions{
Format: map[string]string{
slog.TimeKey: "", // Omit time field for predictable output.
"pass": " pass=REDACTED", // Replace field value with constant.
},
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
logger.Info("Test message", "user", "alice", "pass", "s3cr3t")
}
Output: level=INFO msg="Test message" user=alice pass=REDACTED
Example (FormatRemoveAttr) ¶
package main
import (
"log/slog"
"os"
"github.com/powerman/slogx"
)
func main() {
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by omitting the time field.
opts := slogx.LayoutHandlerOptions{
Format: map[string]string{
slog.TimeKey: "", // Omit field.
},
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
logger.Info("Test message")
}
Output: level=INFO msg="Test message"
Example (FormatShortenLevel) ¶
package main
import (
"context"
"log/slog"
"os"
"github.com/powerman/slogx"
)
func main() {
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by shortening the level field to 3 characters.
opts := slogx.LayoutHandlerOptions{
Level: slog.LevelDebug - 2, // Output all levels.
Format: map[string]string{
slog.TimeKey: "", // Omit time field for predictable output.
slog.LevelKey: "level=%3.3v", // Use alternate level format.
},
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
for level := slog.LevelDebug - 1; level <= slog.LevelError+1; level++ {
logger.LogAttrs(context.Background(), level, "Test message")
}
}
Output: level=D-1 msg="Test message" level=DBG msg="Test message" level=D+1 msg="Test message" level=D+2 msg="Test message" level=D+3 msg="Test message" level=INF msg="Test message" level=I+1 msg="Test message" level=I+2 msg="Test message" level=I+3 msg="Test message" level=WRN msg="Test message" level=W+1 msg="Test message" level=W+2 msg="Test message" level=W+3 msg="Test message" level=ERR msg="Test message" level=E+1 msg="Test message"
Example (FormatTruncate) ¶
package main
import (
"log/slog"
"os"
"github.com/powerman/slogx"
)
func main() {
uuidV1s := []string{
"6f31402a-a14d-11f0-a01f-169545a16ec7",
"6f942726-a14d-11f0-9fa9-169545a16ec7",
"6ffbe866-a14d-11f0-8efe-169545a16ec7",
}
uuidV7s := []string{
"0199b06f-3a9d-7634-9ed4-5a0927ebfb89",
"0199b06f-3ead-75d3-9080-c9b2a0aac316",
"0199b06f-424d-74e9-8f89-fa5b2cc24170",
}
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by truncating given fields to a maximum length.
opts := slogx.LayoutHandlerOptions{
Format: map[string]string{
slog.TimeKey: "", // Omit time field for predictable output.
"uuid_v1": " uuid_v1=%.9v", // Truncate to 9 characters.
"uuid_v7": " uuid_v7=%.-13v", // Truncate to last 13 characters.
},
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
for i := range 3 {
logger.Info("Test message", "uuid_v1", uuidV1s[i], "uuid_v7", uuidV7s[i])
}
}
Output: level=INFO msg="Test message" uuid_v1=6f31402a… uuid_v7=…5a0927ebfb89 level=INFO msg="Test message" uuid_v1=6f942726… uuid_v7=…c9b2a0aac316 level=INFO msg="Test message" uuid_v1=6ffbe866… uuid_v7=…fa5b2cc24170
Example (FormatUnquoted) ¶
package main
import (
"encoding/json"
"fmt"
"log/slog"
"os"
"github.com/powerman/slogx"
)
func main() {
type Data struct {
Key1 string `json:"key1"`
Key2 int `json:"key2"`
Key3 bool `json:"key3"`
}
data := Data{
Key1: "A Key4:B",
Key2: 2,
Key3: true,
}
val, _ := json.Marshal(data)
valIndent, _ := json.MarshalIndent(data, "", " ")
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by outputting JSON without extra quotes.
opts := slogx.LayoutHandlerOptions{
Level: slog.LevelDebug,
Format: map[string]string{
slog.TimeKey: "", // Omit time field for predictable output.
"debug": " %s", // Unquoted value without key.
"json": " json=%s", // Unquoted value.
"multiline": "\n%s", // Unquoted value on a new line.
},
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
logger.Debug("Test message", "debug", fmt.Sprintf("%#+v", data))
logger.Debug("Test message", "default", data)
logger.Info("Test message", "json", val)
logger.Info("Test message", "multiline", valIndent)
logger.Info("Test message", "default", val)
}
Output: level=DEBUG msg="Test message" slogx_test.Data{Key1:"A Key4:B", Key2:2, Key3:true} level=DEBUG msg="Test message" default="{Key1:A Key4:B Key2:2 Key3:true}" level=INFO msg="Test message" json={"key1":"A Key4:B","key2":2,"key3":true} level=INFO msg="Test message" { "key1": "A Key4:B", "key2": 2, "key3": true } level=INFO msg="Test message" default="{\"key1\":\"A Key4:B\",\"key2\":2,\"key3\":true}"
Example (PrefixVerticalAlign) ¶
package main
import (
"io"
"log/slog"
"os"
"github.com/powerman/slogx"
)
func main() {
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by vertically aligning the prefix fields.
opts := slogx.LayoutHandlerOptions{
Format: map[string]string{
slog.TimeKey: "", // Omit time field for predictable output.
slog.LevelKey: "%-5v", // Set fixed width for level.
slog.MessageKey: " %v", // Omit field name.
"app": " %v", // Omit field name.
"server": " [%7v]", // Set padding for server name.
"remote_ip": " %-15v", // Set left align and padding for remote IP.
"http_method": " %7v", // Set padding for HTTP method.
"http_code": " %3s", // Set padding for HTTP code placeholder.
"user_id": " @%v", // Replace field name with "@" mark.
},
PrefixKeys: []string{
"app", // App name is fixed for all log records.
"server", // "OpenAPI", "gRPC", "Metrics", etc.
"remote_ip",
"http_method",
"http_code",
},
SuffixKeys: []string{
slog.SourceKey, // Move here to keep prefix small and fixed width.
"user_id", // Place at EOL to easily spot user-related records.
},
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
logger = logger.
// Set in main().
With("app", "MyApp").
// Set in OpenAPI middleware.
With(
"server", "OpenAPI",
"remote_ip", "192.168.100.1",
"http_method", "GET",
"http_code", "", // A placeholder, will be set later.
).
// Set in auth middleware if known.
With("user_id", "alice")
logger.Warn("Something is wrong", "err", io.EOF)
logger.Error("Request handled", "http_code", 500)
}
Output: WARN MyApp [OpenAPI] 192.168.100.1 GET "Something is wrong" err=EOF @alice ERROR MyApp [OpenAPI] 192.168.100.1 GET 500 "Request handled" @alice
Example (TimeFormat) ¶
package main
import (
"log/slog"
"os"
"time"
"github.com/powerman/slogx"
)
func main() {
now, _ := time.Parse(time.RFC3339Nano, "2006-01-02T15:04:05.789123456+01:00")
setRecordTime := func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
a.Value = slog.TimeValue(now)
}
return a
}
// This example demonstrates how to use the LayoutHandlerOptions to customize
// the log output format by changing the time format.
opts := slogx.LayoutHandlerOptions{
ReplaceAttr: setRecordTime, // Fix time for the example output.
RecordTimeFormat: time.Kitchen,
TimeFormat: time.TimeOnly,
}
logger := slog.New(slogx.NewLayoutHandler(os.Stdout, &opts))
logger.Info("Test message", "something", now)
}
Output: time=3:04PM level=INFO msg="Test message" something=15:04:05
type Middleware ¶ added in v0.2.0
Middleware is a function that wraps an slog.Handler. It is a convenient type for building handler chains, compatible with github.com/samber/slog-multi.Middleware, allowing to use github.com/samber/slog-multi.Pipe with handlers from this package.
func NewContextMiddleware ¶ added in v0.2.0
func NewContextMiddleware(ctx context.Context, setBase func(context.Context), opts ...ContextHandlerOption) Middleware
NewContextMiddleware turns a NewContextHandler into a Middleware.
It accepts a setBase callback to deliver a base context with next handler inside to the caller, because Middleware does not provide a way to return it.
Example usage with github.com/samber/slog-multi:
setBase := func(baseCtx context.Context) { ctx = baseCtx }
slogmulti.
...
Pipe(slogx.NewContextMiddleware(ctx, setBase)).
...
Handler(slog.NewTextHandler(os.Stdout, nil))
func NewWrapMiddleware ¶ added in v0.2.0
func NewWrapMiddleware(cfg WrapHandlerConfig) Middleware
NewWrapMiddleware turns a NewWrapHandler into a Middleware.
Example usage with github.com/samber/slog-multi:
slogmulti.
...
Pipe(slogx.NewWrapMiddleware(slogx.WrapHandlerConfig{…})).
...
Handler(slog.NewTextHandler(os.Stdout, nil))
type WrapHandler ¶ added in v0.2.0
type WrapHandler struct {
// contains filtered or unexported fields
}
WrapHandler is an slog.Handler that wraps another slog.Handler. It is a useful building block for handlers that want to wrap another handler.
It is able to either proxy or collect WithAttrs and WithGroup calls using GroupOrAttrs and to call optional Enabled and Handle callbacks depending on configuration.
func NewWrapHandler ¶ added in v0.2.0
func NewWrapHandler(next slog.Handler, cfg WrapHandlerConfig) *WrapHandler
NewWrapHandler returns a new WrapHandler that delegates to next handler using the provided configuration. You need to provide at least one callback in the configuration for it to be useful.
func (*WrapHandler) Enabled ¶ added in v0.2.0
Enabled implements slog.Handler interface.
func (*WrapHandler) Handle ¶ added in v0.2.0
Handle implements slog.Handler interface.
func (*WrapHandler) WithAttrs ¶ added in v0.2.0
func (h *WrapHandler) WithAttrs(as []slog.Attr) slog.Handler
WithAttrs implements slog.Handler interface.
func (*WrapHandler) WithGroup ¶ added in v0.2.0
func (h *WrapHandler) WithGroup(name string) slog.Handler
WithGroup implements slog.Handler interface.
type WrapHandlerConfig ¶ added in v0.2.0
type WrapHandlerConfig struct {
Enabled func(context.Context, slog.Level, *GroupOrAttrs, slog.Handler) bool
Handle func(context.Context, slog.Record, *GroupOrAttrs, slog.Handler) error
ProxyWith bool // Proxy both WithAttrs and WithGroup calls.
ProxyWithAttrs bool // Proxy WithAttrs calls before first WithGroup call.
}
A WrapHandlerConfig contains a configuration for WrapHandler.
All fields are optional, but without at least one callback it won't be very useful.
When ProxyWith is true, both WithAttrs and WithGroup calls are proxied to the next handler. This mode is useful for transparent proxies that just want to intercept Enabled and/or Handle calls and do not need to handle top-level attributes. In this mode, Enabled and Handle callbacks get nil *GroupOrAttrs.
When ProxyWith is false and ProxyWithAttrs is true, WithAttrs calls are proxied to the next handler until the first WithGroup call. This mode is useful for handlers that want to add top-level attributes but want to proxy initial WithAttrs calls to the next handler to keep possible optimizations in the next handler (e.g. pre-rendered prefix in slog.TextHandler and slog.JSONHandler). In this mode, Enabled and Handle callbacks get nil *GroupOrAttrs before the first WithGroup call with non-empty group.
If both ProxyWith and ProxyWithAttrs are false, all WithAttrs and WithGroup calls are accumulated in a *GroupOrAttrs passed to Enabled and Handle callbacks. This mode is useful for handlers that want to see all attributes and groups.
If Enabled callback is nil then the next handler's Enabled method is called.
If Handle callback is nil then the Record is passed to the next handler after applying accumulated *GroupOrAttrs to it.
If both callbacks are nil and ProxyWith is true then WrapHandler behaves like a (useless) transparent proxy.
Example implementation of Enabled and Handle callbacks equivalent to nil callbacks:
Enabled: func(ctx context.Context, l slog.Level, _ *slogx.GroupOrAttrs, next slog.Handler) bool {
return next.Enabled(ctx, l)
},
Handle: func(ctx context.Context, r slog.Record, goa *slogx.GroupOrAttrs, next slog.Handler) error {
return next.Handle(ctx, goa.Record(r))
}
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
benchmarks
Package benchmarks contains benchmarks for slog.
|
Package benchmarks contains benchmarks for slog. |
|
buffer
Package buffer provides a pool-allocated byte buffer.
|
Package buffer provides a pool-allocated byte buffer. |
|
race
Package race contains helper functions for manually instrumenting code for the race detector.
|
Package race contains helper functions for manually instrumenting code for the race detector. |