mirror of
https://github.com/openziti/ziti.git
synced 2026-09-10 08:45:41 +00:00
ff8ae7cd45
- 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
86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
/*
|
|
Copyright NetFoundry Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// Package logging is the async slog sink that ziti's logging refactor lands on.
|
|
// It owns the seven canonical log levels (trace through panic), the bounded
|
|
// AsyncHandler, and the SyncEmit path used for fatal/panic durability.
|
|
package logging
|
|
|
|
import (
|
|
"log/slog"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Custom slog.Level values extending slog's four standard levels (Debug=-4,
|
|
// Info=0, Warn=4, Error=8) with Trace below Debug and Fatal and Panic above
|
|
// Error. Together these cover the seven canonical level names ziti carries on
|
|
// the agent IPC wire.
|
|
const (
|
|
LevelTrace slog.Level = -8
|
|
LevelFatal slog.Level = 12
|
|
LevelPanic slog.Level = 16
|
|
)
|
|
|
|
// LevelName returns the canonical lowercase name for a slog.Level on the wire.
|
|
// All seven canonical levels (trace, debug, info, warn, error, fatal, panic)
|
|
// map to their canonical names. Non-canonical values (e.g. slog.LevelDebug+1)
|
|
// fall back to a lowercased slog.Level.String(); slog renders those as
|
|
// "DEBUG+1" / "ERROR+4", so the wire never carries silent garbage.
|
|
func LevelName(l slog.Level) string {
|
|
switch l {
|
|
case LevelTrace:
|
|
return "trace"
|
|
case slog.LevelDebug:
|
|
return "debug"
|
|
case slog.LevelInfo:
|
|
return "info"
|
|
case slog.LevelWarn:
|
|
return "warn"
|
|
case slog.LevelError:
|
|
return "error"
|
|
case LevelFatal:
|
|
return "fatal"
|
|
case LevelPanic:
|
|
return "panic"
|
|
}
|
|
return strings.ToLower(l.String())
|
|
}
|
|
|
|
// ParseLevel converts a canonical level name (case-insensitive) into a
|
|
// slog.Level, returning an error for unrecognized names. Both "warn" and
|
|
// "warning" are accepted.
|
|
func ParseLevel(name string) (slog.Level, error) {
|
|
switch strings.ToLower(strings.TrimSpace(name)) {
|
|
case "trace":
|
|
return LevelTrace, nil
|
|
case "debug":
|
|
return slog.LevelDebug, nil
|
|
case "info":
|
|
return slog.LevelInfo, nil
|
|
case "warn", "warning":
|
|
return slog.LevelWarn, nil
|
|
case "error":
|
|
return slog.LevelError, nil
|
|
case "fatal":
|
|
return LevelFatal, nil
|
|
case "panic":
|
|
return LevelPanic, nil
|
|
}
|
|
return 0, errors.Errorf("invalid log level %q", name)
|
|
}
|