Skip to content

feat(loglayer)!: v2 — remove prefix mutation, bump every module to /v2#61

Merged
theogravity merged 8 commits into
mainfrom
feat/v2-remove-prefix-mutation
May 2, 2026
Merged

feat(loglayer)!: v2 — remove prefix mutation, bump every module to /v2#61
theogravity merged 8 commits into
mainfrom
feat/v2-remove-prefix-mutation

Conversation

@theogravity
Copy link
Copy Markdown
Contributor

Summary

loglayer-go v2. The 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.

This is a single-mechanism breaking change with a fan-out across the entire monorepo: every module's import path bumps to /v2.

What changed

Core (go.loglayer.dev/v2):

  • Remove applyPrefix from loglayer.go and the three call sites in dispatch.go, builder.go, log.go.
  • Add transport.JoinPrefixAndMessages(prefix, messages) []any helper. Fast-path early returns when the prefix is empty / messages is empty / messages[0] isn't a string. Per-call cost on a no-prefix logger is one string compare.

Built-in transports (16 of 19, preserving v1 user-visible output):

  • console, pretty, structured, testing, zerolog, zap, slog, logrus, phuslu, charmlog, http, sentry, otellog, gcplogging: each calls the helper at the top of SendToLogger. central and datadog inherit via embedded *http.Transport. lumberjack inherits via delegation to structured.
  • testing.LogLine gained a Prefix string field for fixture assertions.
  • blank intentionally left as raw passthrough (advanced users see v2 params unmangled).

Smart rendering opt-in (1 of 19):

  • cli reads params.Prefix directly and renders it in dim grey, separate from the level color. The level prefix and message body keep the level color (yellow / red); the user prefix gets a third visual layer.

Module path bumps (every published package):

  • Main module: go.loglayer.devgo.loglayer.dev/v2.
  • 19 transport sub-modules: transports/<name>/v2.
  • 6 plugin sub-modules: plugins/<name>/v2.
  • 2 integration sub-modules: integrations/<name>/v2.
  • 6 example sub-modules and plugins/datadogtrace/livetest also bumped.
  • All Go imports + replace / require directives updated.

Docs:

  • New docs/src/migrating-to-v2.md walking the upgrade.
  • whats-new.md v2.0.0 entry at the top of May 02, 2026.
  • creating-transports.md and creating-plugins.md updated to drop the legacy auto-prepend caveats and document the v2 contract.
  • cheatsheet.md, llms.txt, llms-full.txt updated.

Changeset:

  • One multi-package :major changeset bumping every monorel-managed module to v2.

Test plan

  • go test -count=1 ./... (main module) passes.
  • bash scripts/foreach-module.sh test passes across all 33 modules.
  • bun run docs:build clean.
  • Live demo: log.WithPrefix("[auth]").Warn("retrying") through the cli transport produces yellow warning: + dim grey [auth] + yellow retrying.
  • Live demo: same call through the structured transport produces a JSON line with msg: "[auth] retrying" (legacy behavior preserved via the helper).
  • CI per-module test job passes.
  • Reviewer follow-up.

Migration cost on consumers

One-time import-path bump. Most users:

go get go.loglayer.dev/v2 \
       go.loglayer.dev/transports/<your-transports>/v2 \
       go.loglayer.dev/plugins/<your-plugins>/v2
-import "go.loglayer.dev"
+import "go.loglayer.dev/v2"

Custom transport authors who consumed Messages[0] and relied on the prefix being baked in: either call transport.JoinPrefixAndMessages to keep v1 behavior, or read params.Prefix directly and render smartly. Documented in Migrating to v2.

🤖 Generated with Claude Code

Phase 1 of the v2 cascade.

Core changes:
- Remove applyPrefix from loglayer.go and the three call sites in
  builder.go, dispatch.go, log.go. The core no longer mutates
  Messages[0]; the prefix flows through TransportParams.Prefix and
  each transport renders it however it likes.
- Add transport.JoinPrefixAndMessages helper for transports that
  want the v1 'prefix folded into Messages[0]' rendering. Early-
  return on empty prefix or non-string Messages[0]; only allocates
  when there's an actual prefix to merge.

Transport updates (preserving v1 user-visible behavior):
- console, pretty, structured, testing, zerolog, zap, slog, logrus,
  phuslu, charmlog, http, sentry, otellog, gcplogging: each calls
  the helper at the top of SendToLogger before its existing
  rendering.
- testing.LogLine gained a Prefix field for tests that want the
  unmangled signal.
- blank intentionally left as-is (it's a raw-passthrough transport
  for advanced users).
- lumberjack inherits behavior from structured via delegation.

Test fixture updates:
- example_test.go's exampleTransport calls the helper.
- TestPrefix and TestPrefixSurfacedOnTransportParams updated for
  the new contract (prefix on params.Prefix, not in Messages[0]).

Phase 2 (smart rendering opt-ins for cli/structured/pretty),
Phase 3 (module path bumps to /v2), and Phase 4 (changesets +
docs) follow.
Phase 3 of the v2 cascade.

Module path changes:
- Main module: go.loglayer.dev -> go.loglayer.dev/v2.
- Every transport sub-module: go.loglayer.dev/transports/<name> ->
  go.loglayer.dev/transports/<name>/v2.
- Every plugin sub-module: same pattern (/plugins/<name>/v2).
- Every integration sub-module: same (/integrations/<name>/v2).
- All 6 example sub-modules: same.
- plugins/datadogtrace/livetest: nested module also bumped.

Import path updates (mechanical):
- All Go file imports now resolve via the /v2 path.
- Each sub-module's go.mod has its replace/require lines pointed at
  the /v2 path.
- The placeholder version on require lines was bumped from
  v0.0.0-00010101000000-000000000000 to
  v2.0.0-00010101000000-000000000000 because Go module rules require
  the require version to match the major (v2.x.y for /v2 modules).

This is a BREAKING CHANGE for every consumer: import paths must
be updated to /v2 across the board. Phase 4 (changesets, per-module
docs, migration guide, whats-new entry) follows.
…geset

Phase 4 of the v2 cascade: docs + changesets.

- New docs/src/migrating-to-v2.md walking the import-path bump,
  the prefix flow change, and the two transport strategies
  (preserve via JoinPrefixAndMessages or smart render).
- Sidebar entry under Introduction.
- whats-new.md gets a v2.0.0 entry at the top of May 02, 2026.
- Multi-package changeset bumps every monorel-managed module
  to :major (27 packages: main + 19 transports + 6 plugins + 2
  integrations).
- creating-transports.md, creating-plugins.md, cheatsheet.md,
  llms.txt, llms-full.txt updated to remove the 'legacy auto-
  prepend' caveats and document the v2 contract.
Lefthook's pre-push tidy reordered the require blocks on some
sub-modules (lumberjack, others) so all go.loglayer.dev-prefixed
requires are alphabetically sorted.
Critical:
- Update TransportParams.Prefix GoDoc (loglayer.go) to describe v2
  contract; remove the stale 'auto-prepend will be removed in a
  future major version' caveat that referred to v2 itself.
- Update Config.Prefix GoDoc (types.go) to describe v2 contract.
- Update internal LogLayer.prefix field comment to describe v2
  propagation model.
- Update whats-new.md v1.7.0 entry to note the auto-prepend was
  removed in v2.0.0 (resolves the chronological contradiction).
- Rewrite docs/src/transports/cli.md 'Using WithPrefix' section
  for v2: the cli transport reads params.Prefix and renders the
  prefix in dim grey, separate from the level color.
- Delete .changeset/prefix-on-params.md (revived from a stale
  branch off main; was already consumed by v1.7.0 release).

Important:
- Sanitize the user prefix in cli's smart-rendering path
  (sanitize.Message on params.Prefix) so an env- or config-loaded
  prefix can't smuggle ANSI / CRLF through the new layer.
- Add 4 new cli tests: WithPrefix renders inline, WithPrefix gets
  dim-grey ANSI separate from level color, WithPrefix sanitizes
  ANSI smuggling, info-level WithPrefix only colors the prefix
  (body unstyled).
- Add direct unit tests for transport.JoinPrefixAndMessages
  covering all five branches (empty prefix, nil messages, empty
  messages, non-string first arg, normal case with fresh-slice
  guarantee).
- Sweep em-dashes from new content (migrating-to-v2.md, cli.md,
  changeset body).
- Rename ':::tip Pre-v1.7.0 readers' callout to 'Pre-v2 readers'.
- Add 'Do I have to migrate?' and 'Why this change' lede sections
  to migrating-to-v2.md, with the existing buried '## Why' tail
  consolidated into the lede.

Skipped (deferred to follow-up):
- C1 (37 doc pages with v1 import paths): mechanical sweep that
  fits a separate PR; build still passes since the import paths
  in the docs are illustrative, not load-bearing.
- AGENTS.md / CONTRIBUTING.md template updates for new
  sub-modules: same follow-up.
- I2 (contract test for prefix in transport/transporttest): worth
  but not blocking.
- I4 (cli WithPrefix Example with // Output): would need to omit
  // Output for color cases; the new tests cover the same ground.
- I5 (no benchmark for prefix path): non-blocking; claim about
  'one string compare' is verifiable via the helper's source.
- I6 (lltest 'byte-for-byte equivalent' comment): pre-existing
  drift; unrelated to v2.
- I7 (doc.go list of helpers): minor; doesn't affect users.
@theogravity
Copy link
Copy Markdown
Contributor Author

Round-1 review fixes pushed in 87daae8. Both reviewers' Critical and most Important findings addressed; the rest are deferred with a stated reason.

Critical (all fixed)

  • C4 / Doc#1: TransportParams.Prefix GoDoc rewritten for v2 — removed the stale "auto-prepend will be removed in a future major version" caveat (v2 IS that future major).
  • C5: Config.Prefix GoDoc updated for v2.
  • C6: internal LogLayer.prefix field comment updated for the v2 propagation model.
  • C7: whats-new.md v1.7.0 entry now notes the auto-prepend was removed in v2.0.0 (resolves chronological contradiction).
  • C2 / Doc#2: cli.md "Using WithPrefix" section rewritten to describe the dim-grey rendering and remove the false "core mutates the message" framing.
  • Doc#3: deleted .changeset/prefix-on-params.md — confirmed it was consumed by v1.7.0's release on main but my branch revived it via the same stale-branch pattern monorel's doctor was built to catch.

Important (most fixed)

  • I1: cli now sanitizes params.Prefix via sanitize.Message in the smart-rendering path. Closes the ANSI / CRLF smuggling regression introduced when the prefix moved off the message path.
  • C3 / I4: 4 new cli tests covering WithPrefix rendering, dim-grey ANSI layering, sanitization, and info-level (no level color) behavior.
  • I3: direct unit tests for transport.JoinPrefixAndMessages covering all 5 branches (empty prefix, nil messages, empty slice, non-string messages[0], normal case with fresh-slice guarantee).
  • Doc#4: em-dash sweep across the new content (migrating-to-v2.md, cli.md, changeset body).
  • Doc#5: "Do I have to migrate?" and "Why this change" sections added at the top of migrating-to-v2.md; the buried tail ## Why consolidated into the lede.
  • Doc#8: :::tip Pre-v1.7.0 readers renamed to Pre-v2 readers.

Deferred to follow-up (with reasons)

  • C1: 37 doc pages still reference v1 import paths in their code samples. Mechanical sweep, fits a separate PR cleanly. The migration guide and changeset use correct /v2 paths; the deferred sweep is for instructional samples on transport / plugin / integration pages and the README.
  • C8: AGENTS.md and CONTRIBUTING.md templates for adding new sub-modules need updating for the v2 path. Same follow-up.
  • I2: contract test for prefix in transport/transporttest — worth adding so third-party transports get a green-CI signal, not blocking.
  • I5: prefix-path benchmark — claim about "one string compare" is verifiable via the helper source; benchmark would substantiate but isn't load-bearing.
  • I6: internal/lltest "byte-for-byte equivalent" comment — pre-existing drift, unrelated to v2.
  • I7: doc.go helper list — minor; doesn't affect users.
  • S1-S5: nice-to-haves only.

Tests still green; per-module suite passes; docs build clean.

Per direction: loglayer-go is too new to make v1-vs-v2 framing
visible to readers who land on regular docs. Sweep all 'as of v2'
/ 'preserve v1 behavior' / 'pre-v2 readers' / 'in v2' references
out of regular docs and replace with plain present-tense
descriptions of what the code does today.

Files swept:
- loglayer.go: TransportParams.Prefix GoDoc and internal prefix
  field comment now describe the contract without versioning.
- types.go: Config.Prefix GoDoc same.
- transport/helpers.go: JoinPrefixAndMessages doc reframed as
  'fold prefix into the first message' rather than 'preserve v1
  behavior'.
- transports/*/*.go: comment cleanup at every SendToLogger call
  site that called the helper (the line was 'Preserve the v1
  prefix folded into Messages[0] rendering; the core no longer
  mutates messages, transports own it now' — now 'Fold the prefix
  into Messages[0] for the rendered output; transports own this
  rendering choice').
- docs/src/transports/creating-transports.md: drop 'As of v2' and
  'Pre-v2 readers' callout; describe current contract plainly.
- docs/src/transports/cli.md: drop 'behavior change vs. v1'
  paragraph; keep just the 'set ColorNever for monochrome' tip.
- docs/src/plugins/creating-plugins.md: drop 'In v2' qualifier on
  the read-only contract.
- docs/src/cheatsheet.md: drop 'as of v2' framing; replace stale
  'prefix prepended' inline comment with 'Prefix value'.
- docs/src/public/llms.txt and llms-full.txt: drop 'as of v2' /
  'in v2' framing; update 'Out of Scope (v1)' heading to
  'Currently out of scope'.
- docs/src/whats-new.md: tighten the v1.7.0 line so the
  parenthetical doesn't self-contradict.
- docs/src/migrating-to-v2.md: em-dash sweep on the new lede.

Kept intact (per direction):
- whats-new.md v2.0.0 entry — still names v1.7.0 / v2.0.0 because
  whats-new is the version-history page.
- migrating-to-v2.md — still names v1 / v2 because it's the
  migration page.
- .changeset/v2-prefix-passthrough.md — the changeset body is
  the canonical v2 release note.
Round-3 reviewer found three sites where the sed sweep didn't
match because their wording differed slightly from the canonical
'Preserve the v1 ... rendering' pattern:

- dispatch.go formatLog GoDoc: 'preserve the v1 prepended-into-
  messages shape' -> 'fold it into the first message string'.
- builder.go dispatch inline comment: same rewrite.
- transports/testing/testing.go SendToLogger inline comment:
  reframed as 'Fold the prefix into Messages[0] so test fixtures
  see one rendered message string'.

Plus three test/example files the regex missed:
- example_test.go exampleTransport: comment reframed as 'Fold
  the prefix into the message so the examples render as one
  blob'.
- loglayer_test.go TestPrefixSurfacedOnTransportParams: drop
  v2/v1 framing.
- transports/cli/cli_test.go TestWithPrefixRendersInline: drop
  'v2 contract' framing.
…en-wrap

Round-4 reviewer's only finding was cosmetic: the prior comment
hyphen-wrapped 'transport.JoinPrefix-\nAndMessages' across two
lines, which is unusual in Go comments. Reflow the comment so the
full identifier sits on one line.
@theogravity theogravity merged commit 9343bdb into main May 2, 2026
13 checks passed
@theogravity theogravity deleted the feat/v2-remove-prefix-mutation branch May 2, 2026 22:51
theogravity added a commit that referenced this pull request May 2, 2026
Follow-up to the v2.0.0 release (#61, #63) and user-facing import sweep
(#65) addressing issues surfaced by a four-reviewer documentation pass.

Critical:
- creating-transports.md: drop stale "already prefix-applied" comment on
  TransportParams.Messages; the core no longer mutates Messages[0].
- llms.txt / llms-full.txt: add transports/cli to the Renderers catalog
  and install snippet (shipped at v2.0.0 but was missing from both).
- introduction.md: show the lltest "go.loglayer.dev/transports/testing/v2"
  import alias in the test-capture snippet.

Important:
- GoDoc cross-references bumped to /v2 in transports/cli/cli.go,
  transports/otellog/otellog.go, doc.go, examples/pretty-modes/main.go.
- migrating-to-v2.md: reframe lede as "two breaking changes" (the
  import-path bump is its own breaking change); add paragraph on the
  core no longer mutating caller-owned input; rewrite Step 1 shell
  snippet so the trailing-backslash + comment paste-trap is gone; add
  callout for the new testing.LogLine.Prefix field.
- configuration.md / llms.txt: reframe the Prefix inline comment so it
  no longer claims the core prepends.

Style:
- Em-dash sweep across llms.txt and llms-full.txt (~50 occurrences) per
  the project's no-em-dashes-anywhere rule.
- llms-full.txt: typo "rolled-our-own" -> "roll-our-own"; reframe the
  "Testing Transport (lltest)" and "Capture entries with lltest"
  headings since lltest is an alias convention, not the package name.

transports/central left undocumented intentionally: its package GoDoc
is already correct for v2 and the module is not in monorel.toml or the
v2.0.0 release.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant