mirror of
https://github.com/openziti/ziti.git
synced 2026-09-11 13:29:03 +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
104 lines
3.0 KiB
Go
104 lines
3.0 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
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"slices"
|
|
)
|
|
|
|
// boundHandler carries a set of attrs that get prepended to every record
|
|
// flowing through it, then delegates to its parent. WithAttrs never mutates
|
|
// the receiver; it returns a new boundHandler whose attrs are the receiver's
|
|
// combined with the additional ones and whose parent is the receiver's parent
|
|
// (not the receiver itself). The effect on a chain of slog.Logger.With calls
|
|
// is that they produce sibling boundHandlers at the same chain depth rather
|
|
// than stacking wrapper-on-wrapper, which would grow the chain on every call.
|
|
type boundHandler struct {
|
|
parent slog.Handler
|
|
attrs []slog.Attr
|
|
}
|
|
|
|
func (h *boundHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
|
return h.parent.Enabled(ctx, level)
|
|
}
|
|
|
|
func (h *boundHandler) Handle(ctx context.Context, r slog.Record) error {
|
|
r2 := slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
|
|
r2.AddAttrs(h.attrs...)
|
|
r.Attrs(func(a slog.Attr) bool {
|
|
r2.AddAttrs(a)
|
|
return true
|
|
})
|
|
return h.parent.Handle(ctx, r2)
|
|
}
|
|
|
|
func (h *boundHandler) WithAttrs(more []slog.Attr) slog.Handler {
|
|
if len(more) == 0 {
|
|
return h
|
|
}
|
|
combined := make([]slog.Attr, 0, len(h.attrs)+len(more))
|
|
combined = append(combined, h.attrs...)
|
|
combined = append(combined, more...)
|
|
return &boundHandler{parent: h.parent, attrs: combined}
|
|
}
|
|
|
|
func (h *boundHandler) WithGroup(name string) slog.Handler {
|
|
if name == "" {
|
|
return h
|
|
}
|
|
return &groupedHandler{parent: h, name: name}
|
|
}
|
|
|
|
// groupedHandler wraps every record's attrs in slog.Group(name, ...) before
|
|
// delegating to its parent. A subsequent WithAttrs creates a boundHandler
|
|
// whose parent is this groupedHandler, so the attrs land inside the group.
|
|
type groupedHandler struct {
|
|
parent slog.Handler
|
|
name string
|
|
}
|
|
|
|
func (h *groupedHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
|
return h.parent.Enabled(ctx, level)
|
|
}
|
|
|
|
func (h *groupedHandler) Handle(ctx context.Context, r slog.Record) error {
|
|
var items []any
|
|
r.Attrs(func(a slog.Attr) bool {
|
|
items = append(items, a)
|
|
return true
|
|
})
|
|
r2 := slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
|
|
r2.AddAttrs(slog.Group(h.name, items...))
|
|
return h.parent.Handle(ctx, r2)
|
|
}
|
|
|
|
func (h *groupedHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
if len(attrs) == 0 {
|
|
return h
|
|
}
|
|
return &boundHandler{parent: h, attrs: slices.Clone(attrs)}
|
|
}
|
|
|
|
func (h *groupedHandler) WithGroup(name string) slog.Handler {
|
|
if name == "" {
|
|
return h
|
|
}
|
|
return &groupedHandler{parent: h, name: name}
|
|
}
|