- renames the FormatPretty --log-formatter value from "pfxlog" to
"pretty"; the old name described its historical origin rather than its
behavior, and pfxlog is being removed
- accepts "pfxlog" as a deprecated, unadvertised alias for "pretty" via
FormatPrettyAlias so existing configs and scripts keep working
- advertises "pretty" in the --log-formatter help text across the run,
tunnel, controller, router, and demo commands
- adds the "pretty" case alongside "pfxlog" to the legacy pfxlog
formatter switches so --log-formatter accepts the same values on every
command
- tests that the "pfxlog" alias still resolves to pretty output
- points slog.Default() at the async slog sink in Install, gated at the
live global level, so un-named slog output and libraries that log
through slog.Default() land in ziti's configured handler instead of
slog's built-in stderr text handler
- wires it through the registry's HandlerFor under the name "default",
which binds no name attr so records already carrying a channel attr
are not double-tagged
- adds DefaultLoggerName so the gating name is discoverable and
operator-targetable via set-channel-log-level
- tests that after Install, slog.Default() reaches the sink and gates
Debug below the global level
- adds logging.ResolveFormat, which picks the effective --log-formatter
when the flag is unset: ZITI_LOG_NO_JSON (or the deprecated
PFXLOG_NO_JSON) forces pfxlog output, else a terminal stderr gets
pfxlog and a redirected stderr gets json, matching pfxlog's old
GlobalInit behavior
- keeps an explicit --log-formatter value authoritative over the env and
tty defaults
- wires ResolveFormat into the ziti run and ziti tunnel logging setup and
documents the default in the --log-formatter help text
- tests the precedence rules, both env var names, and the pfxlog-style
boolean parsing
- adds BuildPrettyHandler and BuildHandlerForFormat in common/logging:
pretty output wraps the hand-rolled logging.PrettyHandler (a direct
port of pfxlog's; df/dl was dropped after review found level-label
gaps, so there is no github.com/michaelquigley/df dependency) in the
AsyncHandler chain; the format-aware builder picks pretty / json /
text by --log-formatter so default look matches pre-slog
- adds BuildTextHandler so --log-formatter=text emits logrus-TextFormatter-
style key=value output (level=info msg=...) via a slog TextHandler rather
than the colored pretty handler, restoring the pre-slog meaning of text
- adds logging.Fatal: a slog-world fatal (slog provides none) that emits
at LevelFatal durably via SyncEmit, then exits, so hard-exit paths do
not lose the record to the async queue; converts the controller and
router startup hard-exit sites from Error+os.Exit / Error+panic to it,
dropping the router's startup panic
- rewires agentlog.DefaultLogLevelCallbacks onto common/logging:
SetLogLevel drives logging.SetGlobalLevel (lockstep slog + logrus),
SetChannelLogLevel and ClearChannelLogLevel drive SetNamedLevel /
ClearNamedLevel; per-channel overrides become slog-only per design
- adds agentToSlog mapping across the seven canonical levels with an
Info fallback for forward-compat
- ziti/run Options.PreRun and ziti/tunnel rootPreRun build the slog
handler chain via logging.BuildHandlerForFormat and call Install;
--verbose seeds the initial level instead of mutating logrus
directly; AsyncOptions flags exposed via logging.AddFlags on each
persistent flag set
- hardens the run command's logging flags: PreRun reads --verbose /
--log-formatter across the command chain so they are honored at either
the alias-parent (ziti controller run) or child position, and the
ziti controller / ziti router alias parents skip their legacy
pfxlog/logrus PersistentPreRun setup for the run subcommand (which
installs the slog chain itself), keeping it for sibling subcommands
- adds Phase 7 acceptance tests in common/agentlog: TestInstallInvariant
covers Out=io.Discard, noop formatter, ReportCaller, and the
lockstep level mirror after Install; TestEndToEnd_AgentSetLogLevel
walks the agent set-log-level path end to end across bridged-logrus
and direct-slog routes; TestPerChannelOverride_AppliesToSlogOnly_NotPfxlog
confirms the design's slog-only channel semantics
- adds Fatal/Panic durability subprocess tests in common/logging that
fork the test binary, Install the production handler chain, then
call logrus.Fatal / logrus.Panic and assert the records reach stderr
before exit/panic; proves the bridge's SyncEmit path flushes before
os.Exit
- adds doc/logging.md developer note covering how to write a slog
line, channel-naming convention, the no-Warn/Error-in-hot-paths
rule, the operator surface, the migration checklist, AsyncOptions
tunables, and what's deliberately deferred
- adds doc/design/slog-conversion-plan.md with the code-grounded
per-package channel inventory, the sdk-golang embedder-injection
pattern, conversion order with deep analysis for the first four
chunks, and cross-repo coordination notes
- adds Registry (exported, for tests) tracking the global *slog.LevelVar, a
per-name override map of *slog.LevelVar, a logger cache, and the root
handler; For(name) returns a cached *slog.Logger whose handler chain binds
channel: name as the first attr (panics on empty); SetGlobalLevel /
SetNamedLevel / ClearNamedLevel mutate the registry under a write lock;
ClearNamedLevel deliberately tracks the live global rather than
snapshotting, so a later global change still reaches the previously-
overridden name
- adds namedHandler, the chain node that gates Enabled on the registry-
resolved level (override or live global) and forwards Handle to the
registry's root; WithAttrs / WithGroup compose with the existing
boundHandler / groupedHandler so For(name).WithGroup(...).With(...) works
- adds a package-level default Registry behind an atomic.Pointer; Configure
swaps the default and clears the logger cache (the new Registry starts
empty); the package-level For / SetGlobalLevel / etc. panic if Configure
has not been called
- adds slogBridge, the logrus.Hook that copies each logrus.Entry into a
slog.Record and dispatches via RootHandler.Handle; Fatal and Panic levels
route through SyncEmit instead so they're durable before logrus exits the
process
- adds Install (and the testable InstallTo) which Configure's the default
Registry, sets the global slog level, redirects logrus output to
io.Discard, replaces its formatter with noopFormatter, sets its level to
the mapped equivalent, and registers slogBridge as a hook
- adds bidirectional level mappings between logrus.Level and slog.Level for
the seven canonical names; non-canonical slog levels bucket into the
canonical level whose value they most recently exceeded
- adds SyncEmit, the package-level entry point that type-asserts the root to
*AsyncHandler and routes through its SyncEmit when possible, falling back
to Handle (already synchronous for non-async handlers)
- adds ReplaceAttr, the slog.HandlerOptions.ReplaceAttr callback that
coerces JSON output into the pfxlog shape: lowercase level via LevelName,
nested source attr suppressed at the top level
- adds sourceFlattener, the chain wrapper that decodes the record's PC into
flat file and func attrs; bridged records arrive with PC == 0 and the
bridge derives their file/func attrs from logrus's already-resolved
Entry.Caller (its symbolized PC does not re-decode reliably, so the bridge
does not forward it), and sourceFlattener passes those records through
- adds BuildHandler, which constructs the production chain (AsyncHandler ->
sourceFlattener -> JSONHandler) over a caller-supplied io.Writer
- exposes Registry.Root so the package-level RootHandler / SyncEmit can
reach the underlying handler without exporting the field
- covers everything under -race: For panics on empty, caches loggers,
GlobalLevel gates Debug below Info, per-name override lets Debug through
one logger while a peer still filters, clear reverts to the live global
(and reflects a later global change), level changes affect previously-
created loggers, the composed
For("router.link").WithGroup("g").With("k","v").Info(...) produces
{msg, channel:"router.link", g:{k:"v", x:1}} through the async queue,
Configure replaces the default with a fresh cache, concurrent For +
SetNamedLevel + ClearNamedLevel + SetGlobalLevel don't race, level
round-trip and non-canonical bucketing, bridge async-for-non-fatal vs
synchronous-for-fatal-and-panic, InstallTo invariants and pre-filter for
below-level records, SyncEmit fallback for a non-async root, ReplaceAttr
level rename + source suppression + group isolation, sourceFlattener PC=0
pass-through, BuildHandler end-to-end pfxlog-shape JSON, and custom-level
rendering
- adds a new common/logging package with custom slog.Level constants for
Trace (-8), Fatal (12), and Panic (16) extending slog's four standard
levels, plus LevelName and ParseLevel as the single source of truth for
canonical lowercase wire names (warn and warning both accepted)
- adds AsyncOptions (QueueSize, BlockThreshold, SummaryInterval) with
Validate, defaults of 4096 / Warn / 5s, and AddFlags / OptionsFromFlags
bindings so the package can wire into cobra via spf13/pflag alone
- adds AsyncHandler, a bounded async slog.Handler that hands records to a
single drain goroutine and onto a downstream handler under a shared
mutex; records at or above the block threshold block (with a closeNotify
escape so shutdown cannot deadlock), records below it drop when the
queue is full and bump a per-level atomic counter
- the drain emits a drop-summary record on each SummaryInterval tick when
any per-level counter is non-zero, and also counts downstream errors in
a drain_errors counter that appears in the same summary line; downstream
errors are also logged once to os.Stderr to avoid slog recursion
- Close signals shutdown and returns immediately; the drain final-flushes
records that beat the close, emits a final summary if drops occurred,
and closes drainDone for tests
- SyncEmit bypasses the queue and writes through the downstream handler
synchronously under the same downstreamMu the drain uses, so fatal/panic
records are durable before the process exits
- adds boundHandler, which prepends bound attrs to every record flowing
through it before delegating to its parent; WithAttrs returns a new
boundHandler whose parent is the receiver's parent (not the receiver
itself), so a chain of slog.Logger.With calls produces sibling
boundHandlers at the same chain depth rather than stacking
wrapper-on-wrapper
- adds groupedHandler, which wraps record attrs in slog.Group(name, ...)
before delegating; a subsequent WithAttrs creates a boundHandler whose
parent is the groupedHandler, so the attrs land inside the group
- AsyncHandler.WithAttrs and WithGroup are the real chain entry points;
empty attrs and empty group names return the receiver so no-op
slog.Logger.With() and WithGroup("") allocate nothing
- covers the lot with -race tests: level round-trip and offset fallback,
defaults validity and bad-value rejection, flag round-trip, async normal
flow, drop-on-full with summary attrs, block at the threshold, Close
idempotent + non-blocking + unblocks Handle, Handle racing Close never
panics, SyncEmit synchronous and serialized with the drain, drain-error
counting, the four worked examples from the design doc for the chain
(with-then-group, group-then-with, nested groups, basic with-attrs),
no-nesting on repeated WithAttrs, sibling-loggers-do-not-leak-attrs,
WithGroup("") and WithAttrs(nil) as no-ops on all three handler types,
and Enabled delegation through the chain