Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Test
run: go test -race -bench . -benchmem ./...
- name: Test CBOR
run: go test -tags binary_log ./...
run: go test -tags binary_log -race ./...
coverage:
runs-on: ubuntu-latest
steps:
Expand Down
18 changes: 18 additions & 0 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,18 @@ func BenchmarkLogFieldType(b *testing.B) {
"Strs": func(e *Event) *Event {
return e.Strs("k", fixtures.Strings)
},
"StrsV": func(e *Event) *Event {
return e.StrsV("k", fixtures.Strings...)
},
"Stringer": func(e *Event) *Event {
return e.Stringer("k", fixtures.Stringers[0])
},
"Stringers": func(e *Event) *Event {
return e.Stringers("k", fixtures.Stringers)
},
"StringersV": func(e *Event) *Event {
return e.StringersV("k", fixtures.Stringers...)
},
"Err": func(e *Event) *Event {
return e.Err(fixtures.Errs[0])
},
Expand Down Expand Up @@ -187,6 +193,9 @@ func BenchmarkLogFieldType(b *testing.B) {
"Objects": func(e *Event) *Event {
return e.Objects("k", fixtures.Objects)
},
"ObjectsV": func(e *Event) *Event {
return e.ObjectsV("k", fixtures.Objects...)
},
"Timestamp": func(e *Event) *Event {
return e.Timestamp()
},
Expand Down Expand Up @@ -270,12 +279,18 @@ func BenchmarkContextFieldType(b *testing.B) {
"Strs": func(c Context) Context {
return c.Strs("k", fixtures.Strings)
},
"StrsV": func(c Context) Context {
return c.StrsV("k", fixtures.Strings...)
},
"Stringer": func(c Context) Context {
return c.Stringer("k", fixtures.Stringers[0])
},
"Stringers": func(c Context) Context {
return c.Stringers("k", fixtures.Stringers)
},
"StringersV": func(c Context) Context {
return c.StringersV("k", fixtures.Stringers...)
},
"Err": func(c Context) Context {
return c.Err(fixtures.Errs[0])
},
Expand Down Expand Up @@ -315,6 +330,9 @@ func BenchmarkContextFieldType(b *testing.B) {
"Objects": func(c Context) Context {
return c.Objects("k", fixtures.Objects)
},
"ObjectsV": func(c Context) Context {
return c.ObjectsV("k", fixtures.Objects...)
},
"Timestamp": func(c Context) Context {
return c.Timestamp()
},
Expand Down
44 changes: 35 additions & 9 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ func (c Context) Object(key string, obj LogObjectMarshaler) Context {
return c
}

// Object marshals an object that implement the LogObjectMarshaler interface.
// Objects adds the field key with objs to the logger context as an array of
// objects that implement the LogObjectMarshaler interface.
//
// This is the array version that accepts a slice of LogObjectMarshaler objects.
func (c Context) Objects(key string, objs []LogObjectMarshaler) Context {
e := c.l.scratchEvent()
e.Objects(key, objs)
Expand All @@ -87,6 +90,14 @@ func (c Context) Objects(key string, objs []LogObjectMarshaler) Context {
return c
}

// ObjectsV adds the field key with objs to the logger context as an array of
// objects that implement the LogObjectMarshaler interface.
//
// This is a variadic version that accepts a list of individual LogObjectMarshaler objects.
func (c Context) ObjectsV(key string, objs ...LogObjectMarshaler) Context {
return c.Objects(key, objs)
}

// EmbedObject marshals and Embeds an object that implement the LogObjectMarshaler interface.
func (c Context) EmbedObject(obj LogObjectMarshaler) Context {
e := c.l.scratchEvent()
Expand All @@ -103,11 +114,20 @@ func (c Context) Str(key, val string) Context {
}

// Strs adds the field key with val as a string to the logger context.
//
// This is the array version that accepts a slice of string values.
func (c Context) Strs(key string, vals []string) Context {
c.l.context = enc.AppendStrings(enc.AppendKey(c.l.context, key), vals)
return c
}

// StrsV adds the field key with vals as a []string to the logger context.
//
// This is a variadic version that accepts a list of individual strings.
func (c Context) StrsV(key string, vals ...string) Context {
return c.Strs(key, vals)
}

// Stringer adds the field key with val.String() (or null if val is nil) to the logger context.
func (c Context) Stringer(key string, val fmt.Stringer) Context {
if val != nil {
Expand All @@ -119,18 +139,24 @@ func (c Context) Stringer(key string, val fmt.Stringer) Context {
return c
}

// Stringers adds the field key with vals as an array of strings by calling .String() on each entry
// to the logger context.
// Stringers adds the field key with vals to the logger context where each
// individual val is added by calling val.String().
//
// This is the array version that accepts a slice of fmt.Stringer values.
func (c Context) Stringers(key string, vals []fmt.Stringer) Context {
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context.Stringers changed behavior for vals == nil: it now encodes an empty array ([]) instead of null (previously done via AppendInterface(..., nil)). If this is intentional for consistency with other plural helpers, it should be called out explicitly as a behavior change (it can affect downstream log consumers). If it’s not intended, restore the nil special-case.

Suggested change
func (c Context) Stringers(key string, vals []fmt.Stringer) Context {
func (c Context) Stringers(key string, vals []fmt.Stringer) Context {
if vals == nil {
c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), nil)
return c
}

Copilot uses AI. Check for mistakes.
if vals != nil {
c.l.context = enc.AppendStringers(enc.AppendKey(c.l.context, key), vals)
return c
}

c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), nil)
c.l.context = enc.AppendStringers(enc.AppendKey(c.l.context, key), vals)
return c
}

// StringersV adds the field key with vals to the logger context where each
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s an extra double-space in the doc comment ("vals to"). Consider tightening it to a single space for readability.

Suggested change
// StringersV adds the field key with vals to the logger context where each
// StringersV adds the field key with vals to the logger context where each

Copilot uses AI. Check for mistakes.
// individual val is added by calling val.String().
//
// This is a variadic version that accepts a list of individual
// fmt.Stringer values.
func (c Context) StringersV(key string, vals ...fmt.Stringer) Context {
return c.Stringers(key, vals)
}

// Bytes adds the field key with val as a []byte to the logger context.
func (c Context) Bytes(key string, val []byte) Context {
c.l.context = enc.AppendBytes(enc.AppendKey(c.l.context, key), val)
Expand Down
45 changes: 39 additions & 6 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,10 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
return e
}

// Objects adds the field key with obj as an array of objects that implement the LogObjectMarshaler interface.
// Objects adds the field key with objs as an array of objects that
// implement the LogObjectMarshaler interface to the event.
//
// This is the array version that accepts a slice of LogObjectMarshaler objects.
func (e *Event) Objects(key string, objs []LogObjectMarshaler) *Event {
if e == nil {
return e
Expand All @@ -280,6 +283,14 @@ func (e *Event) Objects(key string, objs []LogObjectMarshaler) *Event {
return e
}

// ObjectsV adds the field key with objs as an array of objects that
// implement the LogObjectMarshaler interface to the event.
//
// This is a variadic version that accepts a list of individual LogObjectMarshaler objects.
func (e *Event) ObjectsV(key string, objs ...LogObjectMarshaler) *Event {
return e.Objects(key, objs)
}

// Func allows an anonymous func to run only if the event is enabled.
func (e *Event) Func(f func(e *Event)) *Event {
if e != nil && e.Enabled() {
Expand Down Expand Up @@ -310,6 +321,8 @@ func (e *Event) Str(key, val string) *Event {
}

// Strs adds the field key with vals as a []string to the *Event context.
//
// This is the array version that accepts a slice of string values.
func (e *Event) Strs(key string, vals []string) *Event {
if e == nil {
return e
Expand All @@ -318,8 +331,16 @@ func (e *Event) Strs(key string, vals []string) *Event {
return e
}

// Stringer adds the field key with val.String() (or null if val is nil)
// to the *Event context.
// StrsV adds the field key with vals as a []string to the *Event context.
//
// This is a variadic version that accepts a list of individual strings.
func (e *Event) StrsV(key string, vals ...string) *Event {
return e.Strs(key, vals)
}

// Stringer adds the field key and a val to the *Event context.
// If val is not nil, it is added by calling val.String().
// If val is nil, it is encoded as null without calling String().
func (e *Event) Stringer(key string, val fmt.Stringer) *Event {
if e == nil {
return e
Expand All @@ -328,9 +349,11 @@ func (e *Event) Stringer(key string, val fmt.Stringer) *Event {
return e
}

// Stringers adds the field key with vals where each individual val
// is used as val.String() (or null if val is empty) to the *Event
// context.
// Stringers adds the field key with vals to the *Event context.
// If a val is not nil, it is added by calling val.String().
// If a val is nil, it is encoded as null without calling String().
//
// This is the array version that accepts a slice of fmt.Stringer values.
func (e *Event) Stringers(key string, vals []fmt.Stringer) *Event {
if e == nil {
return e
Expand All @@ -339,6 +362,16 @@ func (e *Event) Stringers(key string, vals []fmt.Stringer) *Event {
return e
}

// StringersV adds the field key with vals to the *Event context.
// If a val is not nil, it is added by calling val.String().
// If a val is nil, it is encoded as null without calling String().
//
// This is a variadic version that accepts a list of individual
// fmt.Stringer values.
func (e *Event) StringersV(key string, vals ...fmt.Stringer) *Event {
return e.Stringers(key, vals)
}

// Bytes adds the field key with val as a string to the *Event context.
//
// Runes outside of normal ASCII ranges will be hex-encoded in the resulting
Expand Down
9 changes: 9 additions & 0 deletions event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,15 @@ func TestEvent_WithNilEvent(t *testing.T) {
"Strs": func() *Event {
return e.Strs("k", fixtures.Strings)
},
"StrsV": func() *Event {
return e.StrsV("k", fixtures.Strings...)
},
"Stringers": func() *Event {
return e.Stringers("k", fixtures.Stringers)
},
"StringersV": func() *Event {
return e.StringersV("k", fixtures.Stringers...)
},
"Err": func() *Event {
return e.Err(fixtures.Errs[0])
},
Expand Down Expand Up @@ -306,6 +312,9 @@ func TestEvent_WithNilEvent(t *testing.T) {
"Objects": func() *Event {
return e.Objects("k", fixtures.Objects)
},
"ObjectsV": func() *Event {
return e.ObjectsV("k", fixtures.Objects...)
},
"EmbedObject": func() *Event {
return e.EmbedObject(fixtures.Objects[0])
},
Expand Down
30 changes: 30 additions & 0 deletions globals_118.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//go:build go1.18
// +build go1.18

package zerolog

import (
"fmt"
)

func AsLogObjectMarshalers[T LogObjectMarshaler](objs []T) []LogObjectMarshaler {
if objs == nil {
return nil
}
s := make([]LogObjectMarshaler, len(objs))
for i, v := range objs {
s[i] = v
}
return s
}

func AsStringers[T fmt.Stringer](objs []T) []fmt.Stringer {
if objs == nil {
return nil
}
s := make([]fmt.Stringer, len(objs))
for i, v := range objs {
s[i] = v
}
return s
Comment on lines +10 to +29
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AsLogObjectMarshalers and AsStringers are exported, but they don’t have GoDoc comments. If this repo runs any linting (or for pkg.go.dev quality), add doc comments starting with the function name and describing the allocation/copy behavior and intended use case.

Copilot uses AI. Check for mistakes.
}
2 changes: 1 addition & 1 deletion internal/cbor/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (Encoder) AppendString(dst []byte, s string) []byte {
// AppendStringers encodes and adds an array of Stringer values
// to the dst byte array.
func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte {
if len(vals) == 0 {
if vals == nil || len(vals) == 0 {
return e.AppendArrayEnd(e.AppendArrayStart(dst))
}
dst = e.AppendArrayStart(dst)
Expand Down
2 changes: 1 addition & 1 deletion internal/json/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (Encoder) AppendString(dst []byte, s string) []byte {
// AppendStringers encodes the provided Stringer list to json and
// appends the encoded Stringer list to the input byte slice.
func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte {
if len(vals) == 0 {
if vals == nil || len(vals) == 0 {
return append(dst, '[', ']')
}
dst = append(dst, '[')
Expand Down
Loading
Loading