Skip to content

Thread Safety

Every method on *loglayer.LogLayer is safe to call from any goroutine, including concurrently with emission. There is no setup-only category. You don't need to clone or lock before passing a logger to a goroutine.

The contract is verified by concurrency_test.go under -race, including a runtime-level-toggle test and a transport hot-swap test.

Per-method classification

ClassMethodsHow safety is achieved
EmissionInfo, Warn, Error, Debug, Fatal, WithMetadata, WithError, WithContext (on builder), Raw, MetadataOnly, ErrorOnlyRead-only on logger state.
Returns-newWithFields, WithoutFields, Child, WithPrefix, WithGroup (on *LogLayer), WithContext (on *LogLayer)Build a new logger; receiver untouched.
Read-onlyGetFields, GetLoggerInstance, IsLevelEnabledNo state change.
Level mutatorsSetLevel, EnableLevel, DisableLevel, EnableLogging, DisableLoggingBacked by an atomic.Uint32 bitmap. Mirrors zap.AtomicLevel. Designed for live runtime toggling (SIGUSR1, admin endpoints flipping debug on, etc.).
Transport mutatorsAddTransport, RemoveTransport, SetTransportsPublish a new immutable transport set via atomic.Pointer. Concurrent mutators on the same logger serialize via an internal mutex (slow path); the dispatch hot path only loads the pointer.
Plugin mutatorsAddPlugin, RemovePluginSame atomic-pointer pattern as transports, serialized by pluginMu.
Group mutatorsAddGroup, RemoveGroup, EnableGroup, DisableGroup, SetGroupLevel, SetActiveGroups, ClearActiveGroupsSame atomic-pointer pattern, serialized by groupMu.
Mute togglesMuteFields, UnmuteFields, MuteMetadata, UnmuteMetadataBacked by atomic.Bool. Concurrent reads from the dispatch path are safe, but flipping mid-emission can interleave (some entries see pre-toggle, others post). Treat them as setup-time admin toggles.

What this means in practice

  • Pass loggers to goroutines freely. No clone needed. The receiver is unchanged by every emission method.
  • Use WithFields to derive per-request loggers in HTTP handlers (or the loghttp middleware, which does this for you). The new logger is goroutine-local; the parent is untouched.
  • Child() is the no-additions form of WithFields: it returns an independent clone with the parent's fields shallow-copied, useful when you want isolation without adding fields.
  • Mute toggles aren't races, but they're racy in intent. Set them at startup or via an admin endpoint; don't expect a clean cutover mid-traffic.

Caller-owned input

Maps you pass to WithFields and WithMetadata are not deep-copied. Treat them as read-only after handing them off, or build a fresh one per call. See the inline warnings on the Fields and Metadata pages.