Skip to content

Velocity v2.2.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 11:46
· 54 commits to main since this release
v2.2.0
01174b6

Logging hardening: reliable shutdown, secure redaction, stable live output, and honest benchmarks.

Four independently reviewed fix-and-verification rounds went into this release. Every fix ships with a permanent regression test; the full finding-to-test mapping and measurement artifacts live in docs/specs/ and docs/benchmarks/ in the development tree.

Behaviour changes

  • Shared family lifetime. Once any Close completes, the whole parent/child family is closed for good: later log calls are dropped, AddWriter can no longer revive output, and concurrent Close calls all wait for the same drain and return the same recorded result. Close drains calls that were already admitted - console and JSON writes register as in-flight for the whole formatting cycle, and the render helpers (Render, RenderRaw, Newline, BannerLines, KeyValues, Bullet) register before running - so an admitted call completes rather than writing after Close returned. Admitted family output (including Notify) finishes before the final destination flush, so notifications shared with console logging on one buffered sink are always emitted.
  • Reliable fatal delivery. Logger.Fatal is exempt from the sampler in every dispatch path and waits for preceding accepted entries, its own write, and a flush before invoking FatalHandler/exiting. Delivery is acknowledged per queue item (a FIFO barrier sentinel closed by the worker that dequeues it), so the wait cannot be satisfied early by racing aggregate counters. A custom handler that returns leaves the logger reusable. LogEntry and slog records at LevelFatal are logged but never exit the process.
  • Content-driven <secure> tag handling. The maybe-secure flag derives from message content whenever scanning is enabled, independent of the writer mix, so adding a writer after a scan can no longer leak plaintext. On all-trusted topologies markers are stripped (plaintext shown); untrusted writers still see redaction. Group item text and continuation lines follow the same policy as headers on console and JSON.
  • Colour permission fixed at construction. WithColour(false) survives every theme swap; a mono-to-coloured swap restores colour only where permission allows. FORCE_COLOR can style non-terminals but never grants trust or cursor control. Logger.Status now respects the resolved colour permission on terminals, with styling and trust propagated separately - a trusted terminal with styling disabled still shows Secure field plaintext, without ANSI.
  • Honest close errors. MultiWriter.Close (and Logger.Close through it) returns worker close errors joined with errors.Join.
  • Deprecated ConsoleWriterRB: the ring-full direct-write fallback is removed - a full queue drops the record, counts it in DroppedCount, and Write returns nil; a second Close waits for the same drain and returns nil. Scheduled for removal in v3; use ConsoleWriter.
  • Stable live displays. Widgets finalise exactly once (concurrent Stop/Complete all wait for the one finalisation, no output after), cursor-control capability depends on the real destination rather than FORCE_COLOR, and multi-row displays no longer walk down the terminal on each repaint: repeated repaints, grow/shrink, widget removal and interleaved log lines hold a stable vertical position.
  • Terminal cell widths. Table, box, banner, component-column and truncation widths are measured in terminal cells (uniseg grapheme widths) with an allocation-free printable ASCII fast path. Absent table cells are padded to the declared geometry; negative Bullet nesting is clamped.

New APIs

  • live.NewOutput(io.Writer) *live.Output - opt-in shared terminal coordinator. Pass the same *Output to WithConsoleOutput and the widget constructors so log records and live displays serialise on one destination.
  • StyledRenderable - optional extension to Renderable for types that need resolved styling and trust propagated separately at render time (RenderStyled(w, styled, trusted)). Logger.Render/RenderRaw dispatch to it first; StatusItem implements it. Legacy Renderable and TTYRenderable implementations are unaffected.
  • velocity.Uint64(key string, val uint64) Field - lossless unsigned integer field on every output path, stored as bits without a float64 round trip.

Internal

  • ringbuffer.go rewritten as a mutex-guarded bounded byte queue with owned byte storage and a single drainer goroutine; the speculative CAS/skip reclamation protocol is gone.
  • The unused per-Logger buffer pool was removed; WithBufferSize and WithFieldPoolSize remain deprecated compatibility options.
  • PutFieldSlice clears the pooled slice through its capacity, so a later, smaller use cannot observe stale field pointers.
  • New direct dependency: github.com/rivo/uniseg v0.4.7 (terminal cell widths). golang.org/x/term remains.

Performance

Benchmarks now measure real serialisation (the previous fixtures routed enabled paths to io.Discard, which Velocity maps to a no-output fast path - the old numbers measured almost nothing). Corrected, interleaved measurements with a stable control benchmark are published in the README alongside delivery/drop ratios for the async figures. Disabled-level logging remains 0 B/op, 0 allocs/op, asserted by test.