diff --git a/docs/src/public/llms-full.txt b/docs/src/public/llms-full.txt index f69aa0b..cf2e404 100644 --- a/docs/src/public/llms-full.txt +++ b/docs/src/public/llms-full.txt @@ -194,10 +194,28 @@ Callbacks may be invoked concurrently across goroutines if the same `*LogLayer` terminal-renderer transports (cli, pretty, console) interpret as authored "\n" boundaries. JSON sinks and wrapper transports flatten via Stringer/MarshalJSON. Each authored line is still sanitized - individually. v1 is messages-only; metadata/fields values still - collapse to one line in terminal renderers. + individually. The wrapper is honored only as a positional message + argument; values placed inside Fields or Metadata still collapse + to one line in terminal renderers. https://go.loglayer.dev/logging-api/multiline +## Log Sanitization + +- The cli, pretty, and console transports strip control bytes + (CR / LF / ANSI ESC / bidi controls / zero-width chars) from + user-controlled message strings before writing, to defeat log + forging, terminal-escape smuggling, and Trojan Source attacks. + Untrusted input cannot become multi-line by accident; the + loglayer.Multiline wrapper is the developer's explicit opt-in. +- structured and every wrapper transport (zerolog, zap, slog, + logrus, charmlog, phuslu, sentry, otellog, gcplogging, http, + datadog, testing) do NOT call sanitize.Message; their JSON + encoders escape control bytes automatically. +- Helpers: utils/sanitize.Message(string) string strips per-rune; + transport.AssembleMessage(messages, sanitize) handles per-line + sanitize with Multiline-aware joining for terminal transports. + https://go.loglayer.dev/log-sanitization + ## Fields (persistent data across all log entries) Fields ride on every emission from the logger they're set on. `WithFields` returns a new `*LogLayer`; always assign the result. @@ -1157,6 +1175,7 @@ Full module list: [`monorel.toml`](https://github.com/loglayer/loglayer-go/blob/ - [Thread Safety](https://go.loglayer.dev/logging-api/thread-safety) - [Raw Logging](https://go.loglayer.dev/logging-api/raw) - [Multi-line messages](https://go.loglayer.dev/logging-api/multiline) +- [Log Sanitization](https://go.loglayer.dev/log-sanitization) - [Mocking](https://go.loglayer.dev/logging-api/mocking) - [For TypeScript Developers](https://go.loglayer.dev/for-typescript-developers) - [Transport Overview](https://go.loglayer.dev/transports/) diff --git a/docs/src/public/llms.txt b/docs/src/public/llms.txt index ee02f37..fd01ac9 100644 --- a/docs/src/public/llms.txt +++ b/docs/src/public/llms.txt @@ -404,6 +404,7 @@ lines := lib.Lines() // []lltest.LogLine; assert on Level, Messages, Data, Meta - [Adjusting Log Levels](https://go.loglayer.dev/logging-api/adjusting-log-levels): Three-tier level control - [Raw Logging](https://go.loglayer.dev/logging-api/raw): `Raw(RawLogEntry)` bypasses the builder - [Multi-line messages](https://go.loglayer.dev/logging-api/multiline): `loglayer.Multiline` for authored multi-line output +- [Log Sanitization](https://go.loglayer.dev/log-sanitization): What gets sanitized, where, and the transport-author decision tree - [Mocking](https://go.loglayer.dev/logging-api/mocking): `loglayer.NewMock()` and `transports/testing` - [Transport Overview](https://go.loglayer.dev/transports/): All transports - [Plugins Overview](https://go.loglayer.dev/plugins/): Plugin system and hooks diff --git a/docs/src/transports/creating-transports.md b/docs/src/transports/creating-transports.md index 49fa962..87f0089 100644 --- a/docs/src/transports/creating-transports.md +++ b/docs/src/transports/creating-transports.md @@ -187,7 +187,12 @@ func (t *Transport) SendToLogger(p loglayer.TransportParams) { The core does NOT prepend `params.Prefix` into `Messages[0]`. Each transport decides how to render the prefix: -- **Fold the prefix into the message** (simplest): call `transport.JoinPrefixAndMessages(params.Prefix, params.Messages)` at the top of your `SendToLogger`. The helper returns `Messages` unchanged when `Prefix` is empty (fast path) or when `Messages[0]` isn't a string; otherwise it returns a fresh slice with `prefix + " "` prepended to `Messages[0]`. The output reads as one blob (`"[prefix] message body"`) which is what most renderer / wrapper transports want. +- **Fold the prefix into the message** (simplest): call `transport.JoinPrefixAndMessages(params.Prefix, params.Messages)` at the top of your `SendToLogger`. The helper returns `Messages` unchanged only when `Prefix` is empty (fast path); otherwise it folds the prefix in front of `Messages[0]` based on the element's type: + - `string`: prepend `prefix + " "` directly. + - `*loglayer.MultilineMessage`: prepend `prefix + " "` to the first authored line and rebuild the wrapper, so multi-line content renders with the prefix on line 1 only. + - any other value: format with `fmt.Sprintf("%v", v)` and prepend the prefix in front, so types implementing `Stringer` flow correctly. + + The output reads as one blob (`"[prefix] message body"`) which is what most renderer / wrapper transports want. - **Render the prefix separately**: read `params.Prefix` directly and render it however suits your transport. A renderer can color the prefix differently from the message body; a structured transport can emit it as its own top-level field; a wrapper transport can forward it to the underlying logger's structured-field API (`zerolog.Event.Str("prefix", p.Prefix)`, etc.). Don't call `JoinPrefixAndMessages` in this path. ```go diff --git a/docs/src/whats-new.md b/docs/src/whats-new.md index 4ed4010..324bd4a 100644 --- a/docs/src/whats-new.md +++ b/docs/src/whats-new.md @@ -7,27 +7,29 @@ description: Latest features and improvements in LogLayer for Go. - See the [main `CHANGELOG.md`](https://github.com/loglayer/loglayer-go/blob/main/CHANGELOG.md) for the auto-generated per-release log. -## May 02, 2026 - -`v2.0.0`: - -**Breaking: import paths bump to `/v2`.** The loglayer core no longer mutates `Messages[0]` to fold the `WithPrefix` value into the message text. The prefix flows through `TransportParams.Prefix` and each transport decides how to render it. Built-in transports preserve v1 user-visible output via the new `transport.JoinPrefixAndMessages` helper; the cli transport opts into smart rendering (dim-grey user prefix separate from level color). See [Migrating to v2](/migrating-to-v2) for the upgrade checklist. - -`loglayer`: +## May 03, 2026 -`Prefix` is now exposed as a separate field on `TransportParams` and on every dispatch-time plugin hook param struct (`BeforeDataOutParams`, `BeforeMessageOutParams`, `TransformLogLevelParams`, `ShouldSendParams`). Transports and plugins can render or react to the prefix independently from the message string. The legacy "prepend prefix into `Messages[0]`" auto-mutation in v1.7.x stays in place for backwards compatibility within the v1 line; v2.0.0 removes it. +`v2.1.0`: **`loglayer.Multiline(lines ...any)`** is a new value-wrapper that lets terminal transports preserve authored "\n" boundaries. The cli, pretty, and console transports collapse bare-string newlines to one line for security (log-forging, terminal-escape smuggling); the wrapper is a per-call developer-issued opt-in to that defense. Each authored line is still individually sanitized; only the boundaries between them are honored. JSON sinks and wrapper transports flatten via `Stringer` / `MarshalJSON` with no code change. See [Multi-line messages](/logging-api/multiline). The change also fixes a pre-existing bug in `transport.JoinPrefixAndMessages` where `WithPrefix` was silently dropped when `Messages[0]` was not a string. The prefix now folds in front of the `%v`-formatted first message. -`transports/cli`: +A new [Log Sanitization](/log-sanitization) reference page covers what gets sanitized where, the threat model (log forging, terminal escape smuggling, Trojan Source), and the decision tree for transport authors. -Initial release. New [CLI transport](/transports/cli) tuned for command-line app output: short level prefixes, stdout / stderr routing, TTY-detected ANSI color, no timestamps. Includes table rendering for slice-of-map metadata so the same call site emits a CLI table and a JSON array depending on the transport. +`transports/cli` `v2.2.0`: + +The cli transport now honors `loglayer.Multiline` values: authored multi-line content renders across rows on stdout / stderr while bare-string newlines are still stripped. Per-line sanitization for ANSI / CR / bidi / ZWSP is preserved within each authored line. + +`transports/cli` `v2.1.0`: New `Config.TableColumnOrder []string` knob pins the leading column order for slice-of-map metadata table rendering. Keys named here render in the listed order; the rest sort lexicographically and follow. Empty / nil keeps the previous fully-lexicographic behavior. See [Pinning column order](/transports/cli#pinning-column-order). -`transports/http`: +`transports/pretty` `v2.1.0` and `transports/console` `v2.1.0`: + +Same Multiline support as cli: authored multi-line content renders across rows; bare-string newlines still strip; per-line sanitization preserved. + +`transports/http` `v2.1.0`: New `Config.String()` redacts `Headers` values so an accidental `log.Info(cfg)` or `fmt.Sprintf("%v", cfg)` can't leak credentials passed via `Authorization` / `X-API-Key` / similar headers. Header keys stay visible for debuggability. Mirrors the redaction shape already used by `transports/datadog`. @@ -35,6 +37,24 @@ New `Config.String()` redacts `Headers` values so an accidental `log.Info(cfg)` New `Config.ShutdownTimeout` (default 5s) bounds how long `Close` waits for in-flight requests to finish during shutdown. When the timeout elapses, the worker's outbound HTTP requests are cancelled via context so `Close` can return even if the endpoint is wedged; previously a stuck endpoint could pin `Close` for up to the per-request `Client.Timeout` (30s default), and the parent `flushTransports`'s 5s timeout would leak the close goroutine. +`v2.0.1`: + +Republished every module with a clean `go.mod`. The v2.0.0 cascade shipped sub-module `go.mod` files containing dev-only `replace` directives and placeholder pseudo-version requires; downstream consumers saw `go mod tidy` 404 on the placeholders. No API changes; re-`go get` to pick up the cleaned modules. + +## May 02, 2026 + +`v2.0.0`: + +**Breaking: import paths bump to `/v2`.** The loglayer core no longer mutates `Messages[0]` to fold the `WithPrefix` value into the message text. The prefix flows through `TransportParams.Prefix` and each transport decides how to render it. Built-in transports preserve v1 user-visible output via the new `transport.JoinPrefixAndMessages` helper; the cli transport opts into smart rendering (dim-grey user prefix separate from level color). See [Migrating to v2](/migrating-to-v2) for the upgrade checklist. + +`loglayer`: + +`Prefix` is now exposed as a separate field on `TransportParams` and on every dispatch-time plugin hook param struct (`BeforeDataOutParams`, `BeforeMessageOutParams`, `TransformLogLevelParams`, `ShouldSendParams`). Transports and plugins can render or react to the prefix independently from the message string. The legacy "prepend prefix into `Messages[0]`" auto-mutation in v1.7.x stays in place for backwards compatibility within the v1 line; v2.0.0 removes it. + +`transports/cli`: + +Initial release. New [CLI transport](/transports/cli) tuned for command-line app output: short level prefixes, stdout / stderr routing, TTY-detected ANSI color, no timestamps. Includes table rendering for slice-of-map metadata so the same call site emits a CLI table and a JSON array depending on the transport. + ## Apr 30, 2026 `transports/gcplogging`: