7 Commits

Author SHA1 Message Date
Paul Lorenz 41e1bb2e91 Merge pull request #4205 from openziti/honor-ziti-log-no-json
Honor ZITI_LOG_NO_JSON and restore terminal-aware log-format default
2026-08-12 10:49:43 -04:00
Paul Lorenz ba68a528f5 Rename the pretty log formatter value from pfxlog to pretty
- 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
2026-08-12 01:15:47 -04:00
Paul Lorenz 3abca69cb1 Route stdlib slog.Default() into the sink on Install. Fixes #4232
- 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
2026-08-06 19:20:37 -04:00
Paul Lorenz c9af14fb87 Honor ZITI_LOG_NO_JSON and restore terminal-aware log default. Fixes #4204
- 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
2026-07-30 15:24:33 -04:00
Paul Lorenz 0aefe599c9 Install slog and route agent log-level callbacks through common/logging. Fixes #3910
- 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
2026-06-15 15:34:31 -04:00
Paul Lorenz 53a7dea7f3 Add named-logger registry, logrus bridge, and pfxlog-shape JSON. Fixes #3906
- 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
2026-06-13 00:05:27 -04:00
Paul Lorenz ff8ae7cd45 Add common/logging package: AsyncHandler, level helpers, and chain composition. Fixes #3904
- 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
2026-06-12 19:34:17 -04:00