or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-types.mdcontextual-logging.mdcore-logging.mddiode-writer.mdfield-methods.mdglobal-configuration.mdglobal-logger.mdhooks.mdhttp-middleware.mdindex.mdjournald-integration.mdpkgerrors-integration.mdsampling.mdwriters-and-output.md
tile.json

advanced-types.mddocs/

Advanced Types

Zerolog provides advanced types for logging complex data structures including arrays, nested objects, and custom types through marshaler interfaces.

Imports

import (
    "net"
    "time"
    "github.com/rs/zerolog"
)

Array Type

The Array type allows you to build arrays of mixed types that can be added to log events. Arrays are pooled and reused for efficiency.

Creating Arrays

// Create new array
func Arr() *Array

Example:

arr := zerolog.Arr().
    Str("first").
    Str("second").
    Int(42)

logger.Info().
    Array("items", arr).
    Msg("array example")
// Output: "items":["first","second",42]

Array Methods

All Array methods return *Array for chaining.

String Methods

// Add string to array
func (a *Array) Str(val string) *Array

// Add bytes as string to array
func (a *Array) Bytes(val []byte) *Array

// Add bytes as hex string to array
func (a *Array) Hex(val []byte) *Array

// Add pre-encoded JSON to array
func (a *Array) RawJSON(val []byte) *Array

Example:

arr := zerolog.Arr().
    Str("hello").
    Bytes([]byte("world")).
    Hex([]byte{0x01, 0x02, 0x03})

logger.Info().Array("data", arr).Msg("mixed data")
// Output: "data":["hello","world","010203"]

Boolean Methods

// Add boolean to array
func (a *Array) Bool(b bool) *Array

Example:

arr := zerolog.Arr().
    Bool(true).
    Bool(false).
    Bool(true)

logger.Info().Array("flags", arr).Msg("boolean array")
// Output: "flags":[true,false,true]

Integer Methods

// Add integers to array
func (a *Array) Int(i int) *Array
func (a *Array) Int8(i int8) *Array
func (a *Array) Int16(i int16) *Array
func (a *Array) Int32(i int32) *Array
func (a *Array) Int64(i int64) *Array

// Add unsigned integers to array
func (a *Array) Uint(i uint) *Array
func (a *Array) Uint8(i uint8) *Array
func (a *Array) Uint16(i uint16) *Array
func (a *Array) Uint32(i uint32) *Array
func (a *Array) Uint64(i uint64) *Array

Example:

arr := zerolog.Arr().
    Int(1).
    Int(2).
    Int(3).
    Int64(1234567890)

logger.Info().Array("numbers", arr).Msg("integer array")
// Output: "numbers":[1,2,3,1234567890]

Float Methods

// Add floats to array
func (a *Array) Float32(f float32) *Array
func (a *Array) Float64(f float64) *Array

Example:

arr := zerolog.Arr().
    Float64(1.5).
    Float64(2.7).
    Float32(3.14)

logger.Info().Array("values", arr).Msg("float array")
// Output: "values":[1.5,2.7,3.14]

Time Methods

// Add time to array
func (a *Array) Time(t time.Time) *Array

// Add duration to array
func (a *Array) Dur(d time.Duration) *Array

Example:

now := time.Now()
later := now.Add(1 * time.Hour)

arr := zerolog.Arr().
    Time(now).
    Time(later).
    Dur(1 * time.Hour)

logger.Info().Array("times", arr).Msg("time array")

Network Methods

// Add IP address to array
func (a *Array) IPAddr(ip net.IP) *Array

// Add IP prefix/CIDR to array
func (a *Array) IPPrefix(pfx net.IPNet) *Array

// Add MAC address to array
func (a *Array) MACAddr(ha net.HardwareAddr) *Array

Example:

arr := zerolog.Arr().
    IPAddr(net.ParseIP("192.168.1.1")).
    IPAddr(net.ParseIP("192.168.1.2"))

logger.Info().Array("ips", arr).Msg("ip array")
// Output: "ips":["192.168.1.1","192.168.1.2"]

Error Methods

// Add error to array
func (a *Array) Err(err error) *Array

Example:

err1 := errors.New("error 1")
err2 := errors.New("error 2")

arr := zerolog.Arr().
    Err(err1).
    Err(err2)

logger.Info().Array("errors", arr).Msg("error array")
// Output: "errors":["error 1","error 2"]

Complex Type Methods

// Add object to array using LogObjectMarshaler
func (a *Array) Object(obj LogObjectMarshaler) *Array

// Add nested dictionary to array
func (a *Array) Dict(dict *Event) *Array

// Add interface to array using reflection
func (a *Array) Interface(i interface{}) *Array

Example:

dict1 := zerolog.Dict().Str("name", "alice").Int("age", 30)
dict2 := zerolog.Dict().Str("name", "bob").Int("age", 25)

arr := zerolog.Arr().
    Dict(dict1).
    Dict(dict2)

logger.Info().Array("users", arr).Msg("nested objects")
// Output: "users":[{"name":"alice","age":30},{"name":"bob","age":25}]

Array Marshaler Interface

// Interface for types that can marshal themselves as arrays
type LogArrayMarshaler interface {
    MarshalZerologArray(a *Array)
}

Implementation example:

type Coordinates []float64

func (c Coordinates) MarshalZerologArray(a *zerolog.Array) {
    for _, coord := range c {
        a.Float64(coord)
    }
}

// Usage
coords := Coordinates{37.7749, -122.4194}
logger.Info().Array("location", coords).Msg("coordinates")
// Output: "location":[37.7749,-122.4194]

Example with custom type:

type UserList []User

func (ul UserList) MarshalZerologArray(a *zerolog.Array) {
    for _, user := range ul {
        a.Object(user)
    }
}

type User struct {
    Name string
    Age  int
}

func (u User) MarshalZerologObject(e *zerolog.Event) {
    e.Str("name", u.Name).Int("age", u.Age)
}

// Usage
users := UserList{
    User{Name: "alice", Age: 30},
    User{Name: "bob", Age: 25},
}

logger.Info().Array("users", users).Msg("user list")
// Output: "users":[{"name":"alice","age":30},{"name":"bob","age":25}]

Dict Function

Create nested dictionaries (objects) within log events.

// Create dictionary event for nested objects
func Dict() *Event

Example:

dict := zerolog.Dict().
    Str("name", "alice").
    Int("age", 30).
    Bool("active", true)

logger.Info().
    Dict("user", dict).
    Msg("user info")
// Output: "user":{"name":"alice","age":30,"active":true}

Nested dictionaries:

address := zerolog.Dict().
    Str("street", "123 Main St").
    Str("city", "Springfield")

user := zerolog.Dict().
    Str("name", "alice").
    Dict("address", address)

logger.Info().
    Dict("user", user).
    Msg("user with address")
// Output: "user":{"name":"alice","address":{"street":"123 Main St","city":"Springfield"}}

Dict in arrays:

arr := zerolog.Arr().
    Dict(zerolog.Dict().Str("type", "info").Int("code", 100)).
    Dict(zerolog.Dict().Str("type", "warning").Int("code", 200))

logger.Info().Array("messages", arr).Msg("message array")
// Output: "messages":[{"type":"info","code":100},{"type":"warning","code":200}]

Object Marshaler Interface

The LogObjectMarshaler interface allows custom types to control how they're logged as objects.

// Interface for types that can marshal themselves as objects
type LogObjectMarshaler interface {
    MarshalZerologObject(e *Event)
}

Basic Implementation

type User struct {
    ID       int
    Username string
    Email    string
    Active   bool
}

func (u User) MarshalZerologObject(e *zerolog.Event) {
    e.Int("id", u.ID).
        Str("username", u.Username).
        Str("email", u.Email).
        Bool("active", u.Active)
}

// Usage
user := User{
    ID:       123,
    Username: "alice",
    Email:    "alice@example.com",
    Active:   true,
}

logger.Info().
    Object("user", user).
    Msg("user logged in")
// Output: "user":{"id":123,"username":"alice","email":"alice@example.com","active":true}

Using Object Method

// Add object field using LogObjectMarshaler
func (e *Event) Object(key string, obj LogObjectMarshaler) *Event
func (c Context) Object(key string, obj LogObjectMarshaler) Context

Example:

type Server struct {
    Host string
    Port int
    TLS  bool
}

func (s Server) MarshalZerologObject(e *zerolog.Event) {
    e.Str("host", s.Host).Int("port", s.Port).Bool("tls", s.TLS)
}

server := Server{Host: "localhost", Port: 8080, TLS: true}

logger.Info().Object("server", server).Msg("server config")
// Output: "server":{"host":"localhost","port":8080,"tls":true}

Embedding Objects

// Embed object fields at top level (no key)
func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event
func (c Context) EmbedObject(obj LogObjectMarshaler) Context

Example:

type RequestInfo struct {
    Method string
    Path   string
    Status int
}

func (r RequestInfo) MarshalZerologObject(e *zerolog.Event) {
    e.Str("method", r.Method).
        Str("path", r.Path).
        Int("status", r.Status)
}

req := RequestInfo{Method: "GET", Path: "/api/users", Status: 200}

logger.Info().
    EmbedObject(req).
    Msg("request completed")
// Output: "method":"GET","path":"/api/users","status":200,"message":"request completed"
// Note: No "request" key - fields are embedded at top level

Nested Objects

type Address struct {
    Street string
    City   string
    Zip    string
}

func (a Address) MarshalZerologObject(e *zerolog.Event) {
    e.Str("street", a.Street).
        Str("city", a.City).
        Str("zip", a.Zip)
}

type Person struct {
    Name    string
    Age     int
    Address Address
}

func (p Person) MarshalZerologObject(e *zerolog.Event) {
    e.Str("name", p.Name).
        Int("age", p.Age).
        Object("address", p.Address)
}

person := Person{
    Name: "alice",
    Age:  30,
    Address: Address{
        Street: "123 Main St",
        City:   "Springfield",
        Zip:    "12345",
    },
}

logger.Info().Object("person", person).Msg("person info")
// Output: "person":{"name":"alice","age":30,"address":{"street":"123 Main St","city":"Springfield","zip":"12345"}}

Common Patterns

Complex Data Structures

type Metadata struct {
    Tags     []string
    Priority int
    Flags    map[string]bool
}

func (m Metadata) MarshalZerologObject(e *zerolog.Event) {
    e.Strs("tags", m.Tags).
        Int("priority", m.Priority).
        Interface("flags", m.Flags)
}

metadata := Metadata{
    Tags:     []string{"api", "production"},
    Priority: 1,
    Flags:    map[string]bool{"debug": false, "trace": true},
}

logger.Info().Object("metadata", metadata).Msg("with metadata")

Optional Fields

type Config struct {
    Name     string
    Timeout  *time.Duration
    MaxRetry *int
}

func (c Config) MarshalZerologObject(e *zerolog.Event) {
    e.Str("name", c.Name)
    if c.Timeout != nil {
        e.Dur("timeout", *c.Timeout)
    }
    if c.MaxRetry != nil {
        e.Int("max_retry", *c.MaxRetry)
    }
}

// Only includes non-nil fields
timeout := 5 * time.Second
config := Config{
    Name:    "api",
    Timeout: &timeout,
    // MaxRetry is nil, won't be included
}

logger.Info().Object("config", config).Msg("configuration")
// Output: "config":{"name":"api","timeout":5000}

Arrays of Objects

type LogEntry struct {
    Timestamp time.Time
    Level     string
    Message   string
}

func (l LogEntry) MarshalZerologObject(e *zerolog.Event) {
    e.Time("timestamp", l.Timestamp).
        Str("level", l.Level).
        Str("message", l.Message)
}

type LogBatch []LogEntry

func (lb LogBatch) MarshalZerologArray(a *zerolog.Array) {
    for _, entry := range lb {
        a.Object(entry)
    }
}

batch := LogBatch{
    {Timestamp: time.Now(), Level: "info", Message: "msg1"},
    {Timestamp: time.Now(), Level: "warn", Message: "msg2"},
}

logger.Info().Array("batch", batch).Msg("log batch")

Contextual Objects

type ServiceInfo struct {
    Name    string
    Version string
    Region  string
}

func (s ServiceInfo) MarshalZerologObject(e *zerolog.Event) {
    e.Str("service", s.Name).
        Str("version", s.Version).
        Str("region", s.Region)
}

serviceInfo := ServiceInfo{
    Name:    "api",
    Version: "1.0.0",
    Region:  "us-west-1",
}

// Add to context for all logs
logger := logger.With().
    EmbedObject(serviceInfo).
    Logger()

logger.Info().Msg("started")
// Output includes: "service":"api","version":"1.0.0","region":"us-west-1"

Best Practices

1. Use Object Marshaler for Consistent Logging

Define MarshalZerologObject for types you log frequently:

// Good - consistent representation
type User struct {
    ID   int
    Name string
}

func (u User) MarshalZerologObject(e *zerolog.Event) {
    e.Int("id", u.ID).Str("name", u.Name)
}

logger.Info().Object("user", user).Msg("user action")

// Avoid - inconsistent manual logging
logger.Info().Int("user_id", user.ID).Str("user_name", user.Name).Msg("user action")

2. Embed Common Fields

Use EmbedObject for fields that should appear at the top level:

// Good - common fields embedded
logger := logger.With().EmbedObject(requestInfo).Logger()
logger.Info().Msg("request")
// Output: "method":"GET","path":"/api","message":"request"

// Less readable - nested
logger := logger.With().Object("request", requestInfo).Logger()
logger.Info().Msg("request")
// Output: "request":{"method":"GET","path":"/api"},"message":"request"

3. Keep Marshalers Simple

Don't perform expensive operations in marshalers:

// Good
func (u User) MarshalZerologObject(e *zerolog.Event) {
    e.Int("id", u.ID).Str("name", u.Name)
}

// Avoid - expensive operation
func (u User) MarshalZerologObject(e *zerolog.Event) {
    permissions := u.LoadPermissions()  // Database call - BAD
    e.Int("id", u.ID).Interface("permissions", permissions)
}

4. Use Arrays for Homogeneous Data

Use typed slices with marshaler interface for homogeneous arrays:

// Good - clean and efficient
type IDs []int64

func (ids IDs) MarshalZerologArray(a *zerolog.Array) {
    for _, id := range ids {
        a.Int64(id)
    }
}

logger.Info().Array("ids", IDs{1, 2, 3}).Msg("ids")

// Avoid - verbose
arr := zerolog.Arr().Int64(1).Int64(2).Int64(3)
logger.Info().Array("ids", arr).Msg("ids")

5. Reuse Array and Dict

Don't create unnecessarily complex structures:

// Good - simple when possible
logger.Info().
    Str("user", "alice").
    Int("age", 30).
    Msg("user info")

// Avoid - unnecessary nesting
dict := zerolog.Dict().Str("user", "alice").Int("age", 30)
logger.Info().Dict("info", dict).Msg("user info")

Performance Considerations

  • Arrays and Events are pooled and reused (zero allocation)
  • Object marshalers are called for every log event
  • Keep marshaler implementations fast
  • Arrays of primitives are more efficient than arrays of objects
  • Interface() uses reflection (slower than typed methods)

Thread Safety

  • Array instances are not thread-safe
  • Don't share Array instances across goroutines
  • Object marshalers should not mutate shared state
  • Create new arrays for each log event

See Also

  • Field Methods - Basic field methods including Array() and Object()
  • Core Logging - Event and Logger basics
  • Contextual Logging - Using objects in context
  • Global Configuration - Customize marshaling behavior