Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ type ctxFields struct {
b []byte
}

// WithContext returns a new context carrying the given fields.
// Any fields previously attached via WithContext are preserved;
// the new fields are appended after them. Any fields already
// added to the logger are not attached to the context.
// WithContext returns a copy of ctx carrying the given fields.
// Fields already attached to ctx by WithContext are kept and the new fields
// follow them. Fields added to log with [Logger.With] are not attached.
//
// If the logger discards output or no fields are given, ctx is returned
// unchanged.
// The fields are evaluated once, when WithContext is called, and formatted
// with the [Formatter] of log. They must only be retrieved with
// [Logger.FromContext] on a [Logger] that uses the same kind of Formatter.
//
// If log discards output or no fields are given, ctx is returned unchanged.
func WithContext(ctx context.Context, log *Logger, fields ...Field) context.Context {
if log.isDiscard || len(fields) == 0 {
return ctx
Expand All @@ -37,12 +39,12 @@ func WithContext(ctx context.Context, log *Logger, fields ...Field) context.Cont
return context.WithValue(ctx, contextKey{}, &ctxFields{b: b})
}

// FromContext returns a new Logger extended with any fields attached to ctx
// via WithContext. The context fields appear after the logger's own pre-rendered
// fields (set via With) and before per-call fields.
// FromContext returns a [Logger] extended with the fields attached to ctx by
// [WithContext]. The fields follow those added with [Logger.With] and precede
// those passed to the log methods.
//
// If the logger discards output or ctx carries no fields, the receiver is
// returned unchanged.
// If l discards output or ctx carries no fields, FromContext returns l.
// The receiver is never modified.
func (l *Logger) FromContext(ctx context.Context) *Logger {
if l.isDiscard {
return l
Expand Down
74 changes: 47 additions & 27 deletions ctx/ctx.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Package ctx implements log context convenience functions.
// Package ctx provides functions that create the [logger.Field] values passed
// to the log methods and [logger.Logger.With] of a [logger.Logger].
package ctx

import (
Expand All @@ -12,140 +13,150 @@ import (
"go.opentelemetry.io/otel/trace"
)

// Str returns a string context field.
// Str returns a [logger.Field] with key k and the string value s.
func Str(k, s string) logger.Field {
return func(e *logger.Event) {
e.AppendString(k, s)
}
}

// Strs returns a string slice context field.
// Strs returns a [logger.Field] with key k and the strings s as an array.
func Strs(k string, s []string) logger.Field {
return func(e *logger.Event) {
e.AppendStrings(k, s)
}
}

// Bytes returns a byte slice context field.
// Bytes returns a [logger.Field] with key k and the bytes p as an array of
// integers, not as a string.
func Bytes(k string, p []byte) logger.Field {
return func(e *logger.Event) {
e.AppendBytes(k, p)
}
}

// Bool returns a boolean context field.
// Bool returns a [logger.Field] with key k and the bool value b.
func Bool(k string, b bool) logger.Field {
return func(e *logger.Event) {
e.AppendBool(k, b)
}
}

// Int returns an int context field.
// Int returns a [logger.Field] with key k and the int value i.
func Int(k string, i int) logger.Field {
return func(e *logger.Event) {
e.AppendInt(k, int64(i))
}
}

// Ints returns an int slice context field.
// Ints returns a [logger.Field] with key k and the ints a as an array.
func Ints(k string, a []int) logger.Field {
return func(e *logger.Event) {
e.AppendInts(k, a)
}
}

// Int8 returns an int8 context field.
// Int8 returns a [logger.Field] with key k and the int8 value i.
func Int8(k string, i int8) logger.Field {
return func(e *logger.Event) {
e.AppendInt(k, int64(i))
}
}

// Int16 returns an int16 context field.
// Int16 returns a [logger.Field] with key k and the int16 value i.
func Int16(k string, i int16) logger.Field {
return func(e *logger.Event) {
e.AppendInt(k, int64(i))
}
}

// Int32 returns an int32 context field.
// Int32 returns a [logger.Field] with key k and the int32 value i.
func Int32(k string, i int32) logger.Field {
return func(e *logger.Event) {
e.AppendInt(k, int64(i))
}
}

// Int64 returns an int64 context field.
// Int64 returns a [logger.Field] with key k and the int64 value i.
func Int64(k string, i int64) logger.Field {
return func(e *logger.Event) {
e.AppendInt(k, i)
}
}

// Uint returns a uint context field.
// Uint returns a [logger.Field] with key k and the uint value i.
func Uint(k string, i uint) logger.Field {
return func(e *logger.Event) {
e.AppendUint(k, uint64(i))
}
}

// Uint8 returns a uint8 context field.
// Uint8 returns a [logger.Field] with key k and the uint8 value i.
func Uint8(k string, i uint8) logger.Field {
return func(e *logger.Event) {
e.AppendUint(k, uint64(i))
}
}

// Uint16 returns a uint16 context field.
// Uint16 returns a [logger.Field] with key k and the uint16 value i.
func Uint16(k string, i uint16) logger.Field {
return func(e *logger.Event) {
e.AppendUint(k, uint64(i))
}
}

// Uint32 returns a uint32 context field.
// Uint32 returns a [logger.Field] with key k and the uint32 value i.
func Uint32(k string, i uint32) logger.Field {
return func(e *logger.Event) {
e.AppendUint(k, uint64(i))
}
}

// Uint64 returns a uint64 context field.
// Uint64 returns a [logger.Field] with key k and the uint64 value i.
func Uint64(k string, i uint64) logger.Field {
return func(e *logger.Event) {
e.AppendUint(k, i)
}
}

// Float32 returns a float32 context field.
// Float32 returns a [logger.Field] with key k and the float32 value f.
// The value is converted to a float64 before it is rendered, so the output
// may show digits that are not significant in f.
func Float32(k string, f float32) logger.Field {
return func(e *logger.Event) {
e.AppendFloat(k, float64(f))
}
}

// Float64 returns a float64 context field.
// Float64 returns a [logger.Field] with key k and the float64 value f.
func Float64(k string, f float64) logger.Field {
return func(e *logger.Event) {
e.AppendFloat(k, f)
}
}

// Error returns an error context field.
// Error returns a [logger.Field] with key k and the text of err.
// The field panics when it is applied if err is nil.
func Error(k string, err error) logger.Field {
return func(e *logger.Event) {
e.AppendString(k, err.Error())
}
}

// Err returns an error context field with the key set to "error".
// Err returns a [logger.Field] with key "error" and the text of err.
// The field panics when it is applied if err is nil.
func Err(err error) logger.Field {
return func(e *logger.Event) {
e.AppendString("error", err.Error())
}
}

// Stack return a stack string context field.
// Stack returns a [logger.Field] with key k and the call stack of the code
// that logged the message.
//
// Stack must be passed directly to a log method of a [logger.Logger]. Used
// in other ways, such as with [logger.Logger.With] or inside another
// [logger.Field], it does not report the caller's stack.
func Stack(k string) logger.Field {
return func(e *logger.Event) {
cs := stack.Trace().TrimRuntime()
Expand All @@ -157,7 +168,13 @@ func Stack(k string) logger.Field {
}
}

// Caller returns a caller string context field.
// Caller returns a [logger.Field] with key k and the "file:line" of the code
// that logged the message. The field is omitted if the location cannot be
// determined.
//
// Caller must be passed directly to a log method of a [logger.Logger]. Used
// in other ways, such as with [logger.Logger.With] or inside another
// [logger.Field], it does not report the caller's location.
func Caller(k string) logger.Field {
return func(e *logger.Event) {
_, file, line, ok := runtime.Caller(3)
Expand All @@ -167,34 +184,37 @@ func Caller(k string) logger.Field {
}
}

// Time returns a time context field.
// Time returns a [logger.Field] with key k and the time value t.
func Time(k string, t time.Time) logger.Field {
return func(e *logger.Event) {
e.AppendTime(k, t)
}
}

// Duration returns a duration context field.
// Duration returns a [logger.Field] with key k and the duration value d.
func Duration(k string, d time.Duration) logger.Field {
return func(e *logger.Event) {
e.AppendDuration(k, d)
}
}

// Interface returns an interface context field.
// Interface returns a [logger.Field] with key k and the value v, rendered as
// text. A nil v renders as null in JSON and as nothing in other formats.
func Interface(k string, v any) logger.Field {
return func(e *logger.Event) {
e.AppendInterface(k, v)
}
}

// Span represents an open telemetry span.
// Span is the subset of the OpenTelemetry [trace.Span] interface used by
// [TraceID].
type Span interface {
IsRecording() bool
SpanContext() trace.SpanContext
}

// TraceID returns an open telemetry trace ID context field.
// TraceID returns a [logger.Field] with key k and the OpenTelemetry trace ID
// of span. The field is omitted if span is not recording or has no trace ID.
func TraceID(k string, span Span) logger.Field {
if !span.IsRecording() || !span.SpanContext().HasTraceID() {
return func(*logger.Event) {}
Expand Down
41 changes: 28 additions & 13 deletions doc.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
/*
Package logger implements a logging package.

Example usage:

log := logger.New(os.Stdout, logger.LogfmtFormat(), logger.Info)

// Logger can have scoped context
log = log.With(ctx.Str("env", "prod"))

// All messages can have a context
log.Error("connection error", ctx.Str("redis", conn.Name()), ctx.Int("timeout", conn.Timeout()))
*/
// Package logger implements leveled, structured logging.
//
// A [Logger] writes one message per line to an [io.Writer], rendered by a
// [Formatter]: [JSONFormat], [LogfmtFormat] or [ConsoleFormat]. Messages carry
// key-value fields, created with the functions in the ctx subpackage, either
// attached to the Logger with [Logger.With] or passed with each message.
// Messages more verbose than the Logger's [Level] are dropped.
//
// A Logger may be used by multiple goroutines if its writer allows it; wrap
// writers that do not with [NewSyncWriter]. Fields can also travel in a
// [context.Context]; see [WithContext] and [Logger.FromContext].
//
// Example usage:
//
// import (
// "os"
//
// "github.com/hamba/logger/v2"
// "github.com/hamba/logger/v2/ctx"
// )
//
// log := logger.New(os.Stdout, logger.LogfmtFormat(), logger.Info)
//
// // Logger can have scoped context
// log = log.With(ctx.Str("env", "prod"))
//
// // All messages can have a context
// log.Error("connection error", ctx.Str("redis", conn.Name()), ctx.Int("timeout", conn.Timeout()))
package logger
Loading
Loading