Skip to content

Child Loggers ​

Child() returns a new *loglayer.LogLayer that inherits the parent's configuration, transports, fields, and level state, and can be mutated independently.

go
child := log.Child()

What Gets Inherited ​

AspectInherited?Mutations propagate?
TransportsYes (same instances)No (transport list copy)
ConfigYes (value copy)No
Fields mapYes (shallow copy)No
Level stateYes (cloned)No
PrefixYes (via config)No

The child holds a reference to the same transport instances, they're shared. Everything else is copied so the child can be modified without touching the parent.

Use Cases ​

Per-request loggers ​

WithFields already returns a new logger, so the per-request pattern is just one assignment:

go
func handler(w http.ResponseWriter, r *http.Request) {
    reqLog := serverLog.WithFields(loglayer.Fields{
        "requestId": r.Header.Get("X-Request-ID"),
        "method":    r.Method,
        "path":      r.URL.Path,
    })

    reqLog.Info("handling request")
    // ... pass reqLog into business logic ...
    reqLog.Info("handled")
}

The parent serverLog is untouched, the next request gets a fresh derived logger. Child() is the no-additions form if you want isolation without adding fields.

Per-subsystem loggers via WithPrefix ​

WithPrefix is Child() plus a prefix override:

go
authLog := log.WithPrefix("[auth]")
dbLog   := log.WithPrefix("[db]")

Each subsystem can mutate its own fields, prefix, or level state without affecting the others.

Tightening levels in one branch ​

go
debugLog := log.Child()
debugLog.SetLevel(loglayer.LogLevelDebug)

The original log keeps its threshold; the child runs at debug for one code path.

Fields Isolation ​

Each derived logger has its own fields map (shallow copy from the parent at derive time). Modifications never bleed.

go
log = log.WithFields(loglayer.Fields{"shared": "value"})
child := log.WithFields(loglayer.Fields{"child_only": "x"})

log.Info("parent")  // {"msg": "parent", "shared": "value"}
child.Info("child") // {"msg": "child", "shared": "value", "child_only": "x"}

Note that the copy is shallow: if a field value is a map or slice, the parent and child point at the same underlying data. Don't mutate field values in place; replace the whole entry by deriving a new logger.

Level Isolation ​

go
log.SetLevel(loglayer.LogLevelInfo)
child := log.Child()

child.DisableLevel(loglayer.LogLevelInfo)

log.Info("emitted")    // parent: info enabled
child.Info("dropped")  // child: info disabled

When Not to Use Child ​

If you want the same logger everywhere with no per-call divergence, just pass the original. Child() is for the moments you genuinely need an isolated copy.