The handlers took attributes and dropped them on the floor. Handle never
read record.Attrs, so the inline slog.Info("casting", "device", d) form
lost its fields; WithAttrs returned the receiver unchanged, so anything
attached to a derived logger vanished; and WithGroup did the same, so
grouping silently did nothing. The JSON and webhook handlers marshaled
the slog.Record value directly, which cannot work: a record keeps its
attributes in unexported fields, so encoding/json only ever saw Time,
Message, Level and PC.
The consequence was perverse. Converting log.Printf("[%s] Casting %s",
device, file) into structured attributes, as the Go styleguide asks,
left the output with strictly less information than before, and the
calling code reviewed as correct because it was correct.
A small shared attribute layer now holds the accumulated attributes and
open groups. It is copy-on-write, so two loggers derived from one parent
cannot leak attributes into each other, and it qualifies attributes by
the groups open at the time they were attached, per the slog.Handler
contract. Rendering follows each handler's format: the JSON and webhook
handlers emit attributes as object fields with groups as nested objects,
merging a group named twice rather than duplicating its key; the console
handler appends key=value pairs, groups flattened to dotted keys, quoted
only where a bare value would be ambiguous.
Values are resolved through slog.Value.Resolve, so LogValuer values are
reported as the value they stand for instead of as a struct, and errors
are reported as their message rather than as the empty object
encoding/json makes of them. Anything encoding/json cannot marshal falls
back to its slog string form, so an attribute is never silently emptied.
The record's own fields keep the names they have always had - Time,
Level, Message, PC - and win a collision with an attribute key, so
existing consumers of the json output see no change beyond the added
fields.
All three handlers are covered, including WebhookHandler, which had the
same defect and is reached through the same MultiplexHandler.
Two ignored error returns are also checked explicitly, because current
golangci-lint no longer excludes them by default and would otherwise
fail make check on a developer's toolchain.
2.5 KiB
2.5 KiB
Workflow
- branch (from
main) - do the work in Next Step
- move Next Step to the top of Completed Steps
- move the top item of Future Steps into Next Step
- commit (
TODO.mdchanges in the same commit as the work) - merge to
mainif the branch is not protected, otherwise open a PR - push
Status
1.0+
Tagged v1.0.0 (2024-06-14) and 1.0.1 (2026-02-08). In post-1.0 maintenance; the library is referenced by the Go styleguide.
Next Step
Bring the Makefile up to policy in one commit: add fmt-check, check, and hooks targets (test/lint/fmt/docker exist) and add the missing policy files it depends on: .golangci.yml, REPO_POLICIES.md, .editorconfig, .dockerignore, and .gitea/workflows/check.yml running make check.
Completed Steps
- 2026-08-10: fixed every handler discarding slog attributes: console, JSON and webhook handlers now emit record attributes, accumulate WithAttrs without mutating the receiver, and honour WithGroup; slog.Group values nest and LogValuer values are resolved
- 2026-02-08: fixed JSONHandler deadlock from recursive log.Println, with regression test; tagged 1.0.1
- 2024-06-14: 1.0 prep: lint and fmt enforced in Docker build, call stack depth fix so log locations report correctly, example script, removed non-building RELP code; tagged v1.0.0
- 2024-05-22: module path moved to sneak.berlin/go/simplelog
- 2024-05-14: initial library: slog-based console, JSON, webhook, and RELP handlers, MultiplexHandler, caller file/line info, UTC ISO timestamps, level-colored output
Future Steps
- Rewrite the Dockerfile to run make check with sha256-pinned base images (currently golangci/golangci-lint:latest and golang:1.22, unpinned, duplicating Makefile logic instead of calling it)
- Restructure README.md into the standard sections: Description, Getting Started, Rationale, Design, TODO, License, Author
- Delete the old plain TODO file once this TODO.md lands
- Add .aider.* to .gitignore and remove the stray aider artifacts from the working tree
- Pick one tag scheme before the next release (v1.0.0 vs 1.0.1 are inconsistent)
- Tag v1.0.2, with the leading v, once the attribute fix lands, so consuming repos can move off pseudo-version pins in one step
- Fix RELP output to cache (from old TODO)
- Re-add RELP delivery over TCP to remote rsyslog imrelp; removed 2024-06-14 because it did not build (README planned feature)
- Add regex filtering for webhook logs (from old TODO)
- Better console output format (from old TODO)