Structured logging provides a way to emit log records with a message, severity level, and key-value pairs representing attributes. The slog package defines a Logger type with methods like Info and Error, associated with a Handler that determines how log records are formatted and output.
go get golang.org/x/expimport (
"golang.org/x/exp/slog"
"os"
"context"
"io"
)package main
import (
"os"
"golang.org/x/exp/slog"
)
func main() {
// Using the default logger
slog.Info("application started", "version", "1.0")
// Creating a logger with a text handler
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
logger.Info("hello", "count", 3)
// Creating a logger with a JSON handler
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
jsonLogger.Info("event", "type", "login", "user_id", 42)
// Adding context to log calls
ctx := context.Background()
logger.InfoContext(ctx, "processing request", "request_id", "abc123")
}The slog package uses a handler-based architecture where:
Package-level functions that operate on the default Logger:
func Debug(msg string, args ...any)
func DebugContext(ctx context.Context, msg string, args ...any)
func Info(msg string, args ...any)
func InfoContext(ctx context.Context, msg string, args ...any)
func Warn(msg string, args ...any)
func WarnContext(ctx context.Context, msg string, args ...any)
func Error(msg string, args ...any)
func ErrorContext(ctx context.Context, msg string, args ...any)
func Log(ctx context.Context, level Level, msg string, args ...any)
func LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr)
func With(args ...any) *Logger
func SetDefault(l *Logger)Description: These functions provide convenient access to the default Logger for common logging operations. Context-aware variants ending in "Context" should be preferred when a context is available.
Usage Example:
slog.Info("user login", "user_id", 42, "ip", "192.168.1.1")
ctx := context.Background()
slog.InfoContext(ctx, "processing", "request_id", "xyz")func New(h Handler) *Logger
func Default() *LoggerDescription:
New creates a new Logger with the given non-nil Handler and a nil context.Default returns the default Logger that is used by package-level functions.Usage Example:
textHandler := slog.NewTextHandler(os.Stderr, nil)
logger := slog.New(textHandler)
defaultLogger := slog.Default()func (l *Logger) Debug(msg string, args ...any)
func (l *Logger) DebugContext(ctx context.Context, msg string, args ...any)
func (l *Logger) Info(msg string, args ...any)
func (l *Logger) InfoContext(ctx context.Context, msg string, args ...any)
func (l *Logger) Warn(msg string, args ...any)
func (l *Logger) WarnContext(ctx context.Context, msg string, args ...any)
func (l *Logger) Error(msg string, args ...any)
func (l *Logger) ErrorContext(ctx context.Context, msg string, args ...any)
func (l *Logger) Log(ctx context.Context, level Level, msg string, args ...any)
func (l *Logger) LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr)
func (l *Logger) With(args ...any) *Logger
func (l *Logger) WithGroup(name string) *Logger
func (l *Logger) Handler() Handler
func (l *Logger) Enabled(ctx context.Context, level Level) boolDescription:
LogAttrs is more efficient than Log as it accepts only Attrs without requiring conversion.With returns a new Logger with additional attributes that appear in all output.WithGroup qualifies all attributes with a group name.Handler returns the Logger's Handler.Enabled checks if the Logger will emit records at the given level.Usage Example:
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// Simple logging
logger.Info("server started", "port", 8080)
// With context
ctx := context.WithValue(context.Background(), "request_id", "123")
logger.InfoContext(ctx, "request received")
// Using attributes
logger.LogAttrs(nil, slog.LevelInfo, "event",
slog.Int("duration_ms", 150),
slog.String("status", "ok"))
// Building a logger with common attributes
requestLogger := logger.With("request_id", "abc123", "user_id", 42)
requestLogger.Info("processing") // includes request_id and user_id
// Grouping related attributes
groupedLogger := logger.WithGroup("request")
groupedLogger.Info("data", "method", "GET", "path", "/api/users")type Attr struct {
Key string
Value Value
}
func Any(key string, value any) Attr
func Bool(key string, v bool) Attr
func Duration(key string, v time.Duration) Attr
func Float64(key string, v float64) Attr
func Group(key string, args ...any) Attr
func Int(key string, value int) Attr
func Int64(key string, value int64) Attr
func String(key, value string) Attr
func Time(key string, v time.Time) Attr
func Uint64(key string, v uint64) Attr
func (a Attr) Equal(b Attr) bool
func (a Attr) String() stringDescription: Attr represents a key-value pair. Constructor functions exist for common types to avoid allocations. Group creates an Attr for grouping related key-value pairs.
Usage Example:
// Using constructor functions
slog.Info("event",
slog.String("name", "login"),
slog.Int("user_id", 42),
slog.Duration("elapsed", 150*time.Millisecond),
slog.Bool("success", true))
// Using Group for structured data
slog.Info("request",
slog.Group("headers",
"content-type", "application/json",
"authorization", "Bearer token"))
// Using Any for flexible types
var data interface{} = []int{1, 2, 3}
slog.Info("data", slog.Any("values", data))type Level int
const (
LevelDebug Level = -4
LevelInfo Level = 0
LevelWarn Level = 4
LevelError Level = 8
)
func (l Level) Level() Level
func (l Level) String() string
func (l Level) MarshalJSON() ([]byte, error)
func (l Level) MarshalText() ([]byte, error)
func (l *Level) UnmarshalJSON(data []byte) error
func (l *Level) UnmarshalText(data []byte) errorDescription: A Level represents the importance or severity of a log event. Higher levels indicate more severe events. Levels can be marshaled to/from JSON and text formats.
Usage Example:
// Using predefined levels
slog.Log(nil, slog.LevelDebug, "debug message")
slog.Log(nil, slog.LevelError, "error occurred")
// Using custom levels (any int)
customLevel := slog.Level(2) // Between Info(0) and Warn(4)
// Level string representation
fmt.Println(slog.LevelWarn.String()) // "WARN"
fmt.Println((slog.LevelInfo + 2).String()) // "INFO+2"type LevelVar struct {
// Has unexported fields.
}
func (v *LevelVar) Level() Level
func (v *LevelVar) Set(l Level)
func (v *LevelVar) String() string
func (v *LevelVar) MarshalText() ([]byte, error)
func (v *LevelVar) UnmarshalText(data []byte) errorDescription: LevelVar is a Level variable that allows dynamic level changes. It is safe for concurrent use from multiple goroutines and implements the Leveler interface.
Usage Example:
// Create a dynamic level
programLevel := new(slog.LevelVar) // Defaults to LevelInfo
// Create handler with dynamic level
handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel})
logger := slog.New(handler)
// Change level at runtime
programLevel.Set(slog.LevelDebug) // Now logs debug messages
programLevel.Set(slog.LevelError) // Now only logs errorstype Leveler interface {
Level() Level
}Description: Leveler provides a Level value. Both Level and LevelVar implement this interface.
type Record struct {
// The time at which the output method was called.
Time time.Time
// The log message.
Message string
// The level of the event.
Level Level
// The program counter at the time the record was constructed.
PC uintptr
}
func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record
func (r *Record) Add(args ...any)
func (r *Record) AddAttrs(attrs ...Attr)
func (r Record) Attrs(f func(Attr) bool)
func (r Record) Clone() Record
func (r Record) NumAttrs() intDescription: A Record holds information about a log event. NewRecord is intended for custom logging APIs that want to support a Handler as a backend.
Usage Example:
// Creating a record manually (for custom logging wrappers)
record := slog.NewRecord(time.Now(), slog.LevelInfo, "event occurred", 0)
record.AddAttrs(
slog.String("type", "custom"),
slog.Int("value", 123))
// Cloning a record to avoid shared state
originalRecord := slog.NewRecord(time.Now(), slog.LevelInfo, "msg", 0)
clonedRecord := originalRecord.Clone()
// Iterating over attributes
record.Attrs(func(attr slog.Attr) bool {
fmt.Printf("%s: %v\n", attr.Key, attr.Value)
return true // continue iteration
})type Source struct {
// Function is the package path-qualified function name.
Function string
// File and Line are the file name and line number (1-based).
File string
Line int
}Description: Source describes the location of a line of source code, used for capturing where a log call originated.
type Value struct {
// Has unexported fields.
}
func AnyValue(v any) Value
func BoolValue(v bool) Value
func DurationValue(v time.Duration) Value
func Float64Value(v float64) Value
func GroupValue(as ...Attr) Value
func Int64Value(v int64) Value
func IntValue(v int) Value
func StringValue(value string) Value
func TimeValue(v time.Time) Value
func Uint64Value(v uint64) Value
func (v Value) Any() any
func (v Value) Bool() bool
func (v Value) Duration() time.Duration
func (v Value) Equal(w Value) bool
func (v Value) Float64() float64
func (v Value) Group() []Attr
func (v Value) Int64() int64
func (v Value) Kind() Kind
func (v Value) LogValuer() LogValuer
func (v Value) Resolve() Value
func (v Value) String() string
func (v Value) Time() time.Time
func (v Value) Uint64() uint64Description: A Value can represent any Go value but, unlike type any, can represent most small values without an allocation. Specific constructor functions create Values of common types efficiently.
Usage Example:
// Creating values efficiently
stringVal := slog.StringValue("hello")
intVal := slog.Int64Value(42)
boolVal := slog.BoolValue(true)
groupVal := slog.GroupValue(
slog.String("key", "value"),
slog.Int("num", 10))
// Accessing values
fmt.Println(stringVal.String()) // "hello"
fmt.Println(intVal.Int64()) // 42
fmt.Println(boolVal.Bool()) // true
// Checking value kind
fmt.Println(stringVal.Kind()) // KindString
fmt.Println(groupVal.Kind()) // KindGrouptype Kind int
const (
KindAny Kind = iota
KindBool
KindDuration
KindFloat64
KindInt64
KindString
KindTime
KindUint64
KindGroup
KindLogValuer
)
func (k Kind) String() stringDescription: Kind represents the type of value held by a Value.
type LogValuer interface {
LogValue() Value
}Description: LogValuer is any Go value that can convert itself into a Value for logging. This allows custom types to control how they appear in logs, support deferred expensive operations, or expand into multiple attributes.
Usage Example:
type User struct {
ID int
Name string
}
// Implement LogValuer to customize logging
func (u User) LogValue() slog.Value {
return slog.GroupValue(
slog.Int("id", u.ID),
slog.String("name", u.Name))
}
// For deferred expensive operations
type ExpensiveValue struct {
arg int
}
func (e ExpensiveValue) LogValue() slog.Value {
// This is only called if the log event is enabled
return slog.AnyValue(computeExpensiveValue(e.arg))
}
// Usage
user := User{ID: 42, Name: "Alice"}
slog.Info("user action", slog.Any("user", user))
expensive := ExpensiveValue{arg: 10}
slog.Debug("debug", slog.Any("expensive", expensive))type Handler interface {
// Enabled reports whether the handler handles records at the given level.
Enabled(context.Context, Level) bool
// Handle handles the Record.
Handle(context.Context, Record) error
// WithAttrs returns a new Handler with attributes added.
WithAttrs(attrs []Attr) Handler
// WithGroup returns a new Handler with the given group appended.
WithGroup(name string) Handler
}
func NewLogLogger(h Handler, level Level) *log.LoggerDescription: A Handler decides how to handle each log record. It filters records by level, formats them, and outputs them. Users should use Logger methods, not call Handler methods directly.
Usage Example:
// Creating a custom handler (simplified example)
type MyHandler struct {
attrs []slog.Attr
}
func (h *MyHandler) Enabled(_ context.Context, level slog.Level) bool {
return level >= slog.LevelInfo
}
func (h *MyHandler) Handle(_ context.Context, r slog.Record) error {
// Custom handling logic
fmt.Printf("[%s] %s\n", r.Level, r.Message)
return nil
}
func (h *MyHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &MyHandler{attrs: append(h.attrs, attrs...)}
}
func (h *MyHandler) WithGroup(name string) slog.Handler {
return h // Simplified
}
// Bridge from log package to slog
logLogger := slog.NewLogLogger(handler, slog.LevelInfo)
log.SetOutput(ioutil.Discard)
log.SetFlags(0)
log.SetLogger(logLogger) // Log package now uses the handlertype HandlerOptions 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.
// If Level is nil, the handler assumes LevelInfo.
// Use a LevelVar to vary the level dynamically.
Level Leveler
// ReplaceAttr is called to rewrite each non-group attribute before logging.
ReplaceAttr func(groups []string, a Attr) Attr
}Description: HandlerOptions configure TextHandler and JSONHandler behavior including minimum level, source file information, and attribute replacement.
Usage Example:
// Basic options
opts := &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
}
// With dynamic level
levelVar := new(slog.LevelVar)
opts := &slog.HandlerOptions{
Level: levelVar,
}
// With attribute replacement (redact sensitive data)
opts := &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "password" {
return slog.Attr{Key: "password", Value: slog.StringValue("***")}
}
return a
},
}
handler := slog.NewJSONHandler(os.Stdout, opts)type TextHandler struct {
// Has unexported fields.
}
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler
func (h *TextHandler) Enabled(_ context.Context, level Level) bool
func (h *TextHandler) Handle(_ context.Context, r Record) error
func (h *TextHandler) WithAttrs(attrs []Attr) Handler
func (h *TextHandler) WithGroup(name string) HandlerDescription: TextHandler writes Records as a sequence of space-separated key=value pairs. Output format is human-readable and machine-parseable. Keys and values are quoted if they contain spaces, special characters, or non-printing characters.
Usage Example:
// Create a TextHandler
handler := slog.NewTextHandler(os.Stderr, nil)
logger := slog.New(handler)
// Simple message
logger.Info("hello", "count", 3)
// Output: time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3
// With options
opts := &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
}
handler := slog.NewTextHandler(os.Stderr, opts)
logger := slog.New(handler)
logger.Debug("debug message", "key", "value")
// With groups
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
requestLogger := logger.WithGroup("request")
requestLogger.Info("data", "method", "GET", "url", "/api")
// Output: time=... level=INFO msg=data request.method=GET request.url=/apitype JSONHandler struct {
// Has unexported fields.
}
func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler
func (h *JSONHandler) Enabled(_ context.Context, level Level) bool
func (h *JSONHandler) Handle(_ context.Context, r Record) error
func (h *JSONHandler) WithAttrs(attrs []Attr) Handler
func (h *JSONHandler) WithGroup(name string) HandlerDescription: JSONHandler writes Records as line-delimited JSON objects. Each log record becomes a single JSON object on its own line. Groups are represented as nested JSON objects.
Usage Example:
// Create a JSONHandler
handler := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(handler)
// Simple message
logger.Info("hello", "count", 3)
// Output: {"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3}
// With options for source tracking
opts := &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelInfo,
}
handler := slog.NewJSONHandler(os.Stdout, opts)
logger := slog.New(handler)
logger.Info("event")
// Output: {"time":"...","level":"INFO","msg":"event","source":"main.go:42"}
// With groups
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
requestLogger := logger.WithGroup("request")
requestLogger.Info("received", "method", "POST", "path", "/api/users")
// Output: {"time":"...","level":"INFO","msg":"received","request":{"method":"POST","path":"/api/users"}}const (
// TimeKey is the key used by built-in handlers for the time.
TimeKey = "time"
// LevelKey is the key used by built-in handlers for the level.
LevelKey = "level"
// MessageKey is the key used by built-in handlers for the message.
MessageKey = "msg"
// SourceKey is the key used by built-in handlers for source location.
SourceKey = "source"
)Description: These constants define the standard keys used by TextHandler and JSONHandler for built-in attributes.
The golang.org/x/exp/slog/slogtest subpackage provides testing utilities for Handler implementations.
import "golang.org/x/exp/slog/slogtest"func TestHandler(h slog.Handler, results func() []map[string]any) errorDescription: TestHandler tests a slog.Handler for correct behavior. It installs the handler in a slog.Logger, makes several calls to the Logger's output methods, and verifies the output. The results function should return a slice of maps, one for each Logger call, representing the handler's output. Groups should be represented as nested maps using the standard keys (slog.TimeKey, slog.LevelKey, slog.MessageKey).
Usage Example:
// Example: Testing a custom JSON handler
type customJSONHandler struct {
// Custom handler implementation
}
func TestMyHandler(t *testing.T) {
var buf bytes.Buffer
handler := &customJSONHandler{w: &buf}
err := slogtest.TestHandler(handler, func() []map[string]any {
// Parse the JSON output line by line
var records []map[string]any
scanner := bufio.NewScanner(&buf)
for scanner.Scan() {
var record map[string]any
if err := json.Unmarshal(scanner.Bytes(), &record); err != nil {
t.Fatalf("Failed to parse JSON: %v", err)
}
records = append(records, record)
}
return records
})
if err != nil {
t.Fatalf("TestHandler failed: %v", err)
}
}
// Example: Testing the built-in JSONHandler
func TestJSONHandler(t *testing.T) {
var buf bytes.Buffer
handler := slog.NewJSONHandler(&buf, nil)
err := slogtest.TestHandler(handler, func() []map[string]any {
var records []map[string]any
scanner := bufio.NewScanner(&buf)
for scanner.Scan() {
var record map[string]any
json.Unmarshal(scanner.Bytes(), &record)
records = append(records, record)
}
return records
})
if err != nil {
t.Fatal(err)
}
}package main
import (
"os"
"golang.org/x/exp/slog"
)
func main() {
// Using default logger
slog.Info("application started", "version", "1.0", "env", "production")
slog.Warn("deprecated feature used", "feature", "old_api")
slog.Error("failed to connect", "host", "db.example.com", "error", "connection timeout")
}package main
import (
"os"
"golang.org/x/exp/slog"
)
func main() {
handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
})
logger := slog.New(handler)
logger.Debug("debug info", "request_id", "req-123")
logger.Info("user logged in", "user_id", 42, "ip", "192.168.1.1")
logger.Warn("unusual activity", "attempts", 5)
}package main
import (
"os"
"golang.org/x/exp/slog"
)
func main() {
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
})
logger := slog.New(handler)
logger.Info("user action",
slog.Int("user_id", 42),
slog.String("action", "login"),
slog.String("ip", "192.168.1.1"))
logger.Info("request completed",
slog.Group("request",
slog.String("method", "GET"),
slog.String("path", "/api/users"),
slog.Int("status", 200)))
}package main
import (
"os"
"golang.org/x/exp/slog"
)
var programLevel = new(slog.LevelVar)
func main() {
// Create handler with dynamic level
handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: programLevel,
})
logger := slog.New(handler)
slog.SetDefault(logger)
// Start with LevelInfo (default)
slog.Info("startup")
slog.Debug("debug disabled")
// Enable debug logging
programLevel.Set(slog.LevelDebug)
slog.Debug("debug now enabled")
// Only log errors
programLevel.Set(slog.LevelError)
slog.Info("info suppressed")
slog.Error("error still logged")
}package main
import (
"context"
"os"
"golang.org/x/exp/slog"
)
func handleRequest(requestID string) {
logger := slog.Default().With("request_id", requestID)
// All logs from this logger will include request_id
logger.Info("request started")
logger.Info("processing data", "items", 5)
logger.Info("request completed", "status", 200)
}
func main() {
handler := slog.NewJSONHandler(os.Stdout, nil)
slog.SetDefault(slog.New(handler))
ctx := context.Background()
slog.InfoContext(ctx, "server started")
handleRequest("req-123")
handleRequest("req-456")
}package main
import (
"fmt"
"os"
"time"
"golang.org/x/exp/slog"
)
type Request struct {
ID string
Method string
Path string
Status int
}
func (r Request) LogValue() slog.Value {
return slog.GroupValue(
slog.String("id", r.ID),
slog.String("method", r.Method),
slog.String("path", r.Path),
slog.Int("status", r.Status))
}
type User struct {
ID int
Name string
Role string
}
func (u User) LogValue() slog.Value {
return slog.GroupValue(
slog.Int("id", u.ID),
slog.String("name", u.Name),
slog.String("role", u.Role))
}
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
req := Request{ID: "abc123", Method: "GET", Path: "/api", Status: 200}
logger.Info("request", slog.Any("request", req))
user := User{ID: 42, Name: "Alice", Role: "admin"}
logger.Info("user action", slog.Any("user", user), slog.String("action", "login"))
}package main
import (
"os"
"strings"
"golang.org/x/exp/slog"
)
func sanitizeAttr(groups []string, a slog.Attr) slog.Attr {
// Redact sensitive fields
sensitiveKeys := map[string]bool{
"password": true,
"token": true,
"api_key": true,
"secret": true,
}
if sensitiveKeys[a.Key] {
return slog.Attr{Key: a.Key, Value: slog.StringValue("***")}
}
return a
}
func main() {
opts := &slog.HandlerOptions{
ReplaceAttr: sanitizeAttr,
}
handler := slog.NewJSONHandler(os.Stdout, opts)
logger := slog.New(handler)
// Sensitive fields will be redacted
logger.Info("user authenticated",
slog.String("username", "alice"),
slog.String("password", "secret123"),
slog.String("token", "eyJhbGc..."))
}package main
import (
"fmt"
"os"
"golang.org/x/exp/slog"
)
type ExpensiveMetrics struct {
value int
}
func (e ExpensiveMetrics) LogValue() slog.Value {
// This is only called if the log event is enabled
fmt.Println("Computing expensive metrics...")
result := computeMetrics(e.value)
return slog.AnyValue(result)
}
func computeMetrics(v int) map[string]int {
// Expensive operation
return map[string]int{"cpu": 50, "memory": 1024}
}
func main() {
// Create handler that only logs errors
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelError,
})
logger := slog.New(handler)
// This debug log won't be emitted, so computeMetrics won't be called
logger.Debug("metrics", slog.Any("metrics", ExpensiveMetrics{value: 42}))
// This error will be emitted, so computeMetrics will be called
logger.Error("critical", slog.Any("metrics", ExpensiveMetrics{value: 42}))
}package main
import (
"os"
"golang.org/x/exp/slog"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// Inline groups
logger.Info("event",
slog.Group("user",
slog.Int("id", 42),
slog.String("name", "Alice")),
slog.Group("request",
slog.String("method", "POST"),
slog.String("path", "/api/data")))
// Logger-level groups
requestLogger := logger.WithGroup("request")
requestLogger.Info("processing",
slog.String("method", "GET"),
slog.String("path", "/api/users"),
slog.Int("duration_ms", 150))
}package main
import (
"fmt"
"time"
"golang.org/x/exp/slog"
)
func main() {
// Create and modify a record
record := slog.NewRecord(time.Now(), slog.LevelInfo, "event", 0)
// Add attributes
record.AddAttrs(
slog.String("type", "login"),
slog.Int("user_id", 42))
// Clone the record to avoid shared state
cloned := record.Clone()
cloned.Add("extra", "data")
// Iterate over attributes
fmt.Println("Original record attributes:")
record.Attrs(func(a slog.Attr) bool {
fmt.Printf(" %s = %v\n", a.Key, a.Value)
return true
})
fmt.Println("Cloned record attributes:")
cloned.Attrs(func(a slog.Attr) bool {
fmt.Printf(" %s = %v\n", a.Key, a.Value)
return true
})
}Use Logger.With for common attributes: The built-in handlers format attributes added via With only once, improving performance for repeated attributes.
Prefer LogAttrs over Log: Use LogAttrs when possible for better efficiency, as it avoids the overhead of converting arguments to Attrs.
Defer expensive computations: Use the LogValuer interface to defer expensive operations until the log event is actually enabled.
Check Enabled before expensive operations: Use Logger.Enabled to check if a level is enabled before performing expensive computations.
Pass values directly: When possible, pass raw values rather than calling their String methods prematurely, allowing handlers to preserve structure.
Example of checking enabled level:
if logger.Enabled(ctx, slog.LevelDebug) {
logger.DebugContext(ctx, "debug info", slog.Any("expensive", expensiveComputation()))
}