Format the existing Markdown with prettier
check / check (push) Failing after 4s

The one-time reformat of README.md and TODO.md, produced by make fmt
with nothing else in it. REPO_POLICIES.md already matched prettier's
output and is unchanged.

Model: opus-5-5
This commit is contained in:
2026-10-06 14:04:18 +00:00
parent d827a7e65c
commit 815b57fa0d
2 changed files with 149 additions and 157 deletions
+76 -83
View File
@@ -3,35 +3,33 @@
## Summary ## Summary
simplelog is an opinionated logging package designed to facilitate easy and simplelog is an opinionated logging package designed to facilitate easy and
structured logging in Go applications with an absolute minimum of structured logging in Go applications with an absolute minimum of boilerplate.
boilerplate.
The idea is that you can add a single import line which replaces the The idea is that you can add a single import line which replaces the stdlib
stdlib `log/slog` default handler, and solve the 90% case for logging. `log/slog` default handler, and solve the 90% case for logging.
## Current Status ## Current Status
Released v1.0.0 2024-06-14. Works as intended. No known bugs. Released v1.0.0 2024-06-14. Works as intended. No known bugs.
## Features ## Features
- if output is a tty, outputs pretty color logs - if output is a tty, outputs pretty color logs
- if output is not a tty, outputs json - if output is not a tty, outputs json
- supports delivering each log message via a webhook - supports delivering each log message via a webhook
- emits every `slog` attribute: those passed to a log call, those - emits every `slog` attribute: those passed to a log call, those accumulated
accumulated with `WithAttrs`, and those qualified by `WithGroup`. with `WithAttrs`, and those qualified by `WithGroup`. `slog.Group` values
`slog.Group` values nest, and `slog.LogValuer` values are resolved. In nest, and `slog.LogValuer` values are resolved. In json output attributes are
json output attributes are object fields (groups become nested objects); object fields (groups become nested objects); in console output they are
in console output they are appended as `key=value` pairs, with grouped appended as `key=value` pairs, with grouped keys written as `group.key=value`.
keys written as `group.key=value`. See See [Attribute output](#attribute-output) for the details worth knowing
[Attribute output](#attribute-output) for the details worth knowing - reports a record that could not be delivered as an error from the handler's
- reports a record that could not be delivered as an error from the `Handle` method. See [When delivery fails](#when-delivery-fails) for how to
handler's `Handle` method. See receive it
[When delivery fails](#when-delivery-fails) for how to receive it
## Planned Features ## Planned Features
- supports delivering logs via tcp RELP (e.g. to remote rsyslog using imrelp) - supports delivering logs via tcp RELP (e.g. to remote rsyslog using imrelp)
## Installation ## Installation
@@ -49,9 +47,9 @@ go get sneak.berlin/go/simplelog
## Usage ## Usage
Below is an example of how to use SimpleLog in a Go application. This Below is an example of how to use SimpleLog in a Go application. This example is
example is provided in the form of a `main.go` file, which demonstrates provided in the form of a `main.go` file, which demonstrates logging at various
logging at various levels using structured logging syntax. levels using structured logging syntax.
```go ```go
package main package main
@@ -73,63 +71,58 @@ func main() {
## Attribute output ## Attribute output
Attributes reach every handler: the ones passed to the log call, the ones Attributes reach every handler: the ones passed to the log call, the ones
accumulated with `WithAttrs`, and the ones qualified by the groups open at accumulated with `WithAttrs`, and the ones qualified by the groups open at the
the time they were attached. `slog.Group` values nest, and time they were attached. `slog.Group` values nest, and `slog.LogValuer` values
`slog.LogValuer` values are resolved to the value they stand for. A few are resolved to the value they stand for. A few behaviours are worth knowing
behaviours are worth knowing before you rely on them. before you rely on them.
**Your values are never modified.** Whatever you log is read and rendered, **Your values are never modified.** Whatever you log is read and rendered, never
never written to. A map or a slice you pass to `slog.Any` comes back from written to. A map or a slice you pass to `slog.Any` comes back from the logger
the logger exactly as you handed it over, even when a group later uses the exactly as you handed it over, even when a group later uses the same key.
same key.
**Durations are nanoseconds in json, and readable on the console.** The **Durations are nanoseconds in json, and readable on the console.** The json and
json and webhook payloads emit a `slog.Duration` as a number of webhook payloads emit a `slog.Duration` as a number of nanoseconds, matching
nanoseconds, matching `slog.NewJSONHandler`, so a consumer can compare and `slog.NewJSONHandler`, so a consumer can compare and aggregate the field without
aggregate the field without parsing it. The console line emits the same parsing it. The console line emits the same duration as `3s`, matching
duration as `3s`, matching `slog.NewTextHandler`, because a person reads `slog.NewTextHandler`, because a person reads it.
it.
**The json payload is an object, with the consequences an object has.** **The json payload is an object, with the consequences an object has.** The
The record's own fields are named `Time`, `Level`, `Message` and `PC`, and record's own fields are named `Time`, `Level`, `Message` and `PC`, and they own
they own those names: an attribute keyed after one of them is dropped from those names: an attribute keyed after one of them is dropped from the json and
the json and webhook output. A key logged more than once keeps its last webhook output. A key logged more than once keeps its last value there for the
value there for the same reason, with one exception: when both are same reason, with one exception: when both are `slog.Group` values sharing a
`slog.Group` values sharing a key, the two groups are merged into one key, the two groups are merged into one object holding the members of both,
object holding the members of both, rather than the second replacing the rather than the second replacing the first. Neither the collision nor the merge
first. Neither the collision nor the merge applies to the console output, applies to the console output, which is a line of text: every pair appears, in
which is a line of text: every pair appears, in order. If you need a field order. If you need a field called `message`, pick a key that does not collide -
called `message`, pick a key that does not collide - the collision is the collision is silent.
silent.
**Empty things follow the `slog.Handler` contract.** An empty `Attr` is **Empty things follow the `slog.Handler` contract.** An empty `Attr` is ignored,
ignored, an empty group is elided along with its key, a group with an an empty group is elided along with its key, a group with an empty key is
empty key is inlined into its parent, and `WithGroup("")` returns the inlined into its parent, and `WithGroup("")` returns the handler unchanged.
handler unchanged.
## When delivery fails ## When delivery fails
Every handler returns an error from `Handle` when it could not deliver Every handler returns an error from `Handle` when it could not deliver the
the record: record:
- `ConsoleHandler` and `JSONHandler` return the error from their write to - `ConsoleHandler` and `JSONHandler` return the error from their write to
stdout, wrapped, so `errors.Is` still matches the original stdout, wrapped, so `errors.Is` still matches the original
- `WebhookHandler` returns an error when the request fails, and also when - `WebhookHandler` returns an error when the request fails, and also when the
the server answers with a status outside 2xx, a redirect included, server answers with a status outside 2xx, a redirect included, since it does
since it does not follow redirects. A request that has not finished not follow redirects. A request that has not finished after 5 seconds fails
after 5 seconds fails with a timeout error, so a webhook server that with a timeout error, so a webhook server that never answers holds up a log
never answers holds up a log call for 5 seconds at most call for 5 seconds at most
- `MultiplexHandler`, which simplelog installs as the default, passes the - `MultiplexHandler`, which simplelog installs as the default, passes the record
record to every handler it holds even after one of them fails, then to every handler it holds even after one of them fails, then returns all their
returns all their errors joined with `errors.Join` (nil if none failed) errors joined with `errors.Join` (nil if none failed)
`slog.Info`, `slog.Error` and the other `slog.Logger` methods throw that `slog.Info`, `slog.Error` and the other `slog.Logger` methods throw that error
error away. That is how `log/slog` works, and simplelog cannot change it. away. That is how `log/slog` works, and simplelog cannot change it. To find out
To find out whether a record was delivered, build a `slog.Record` and pass whether a record was delivered, build a `slog.Record` and pass it to the handler
it to the handler yourself, for example yourself, for example `slog.Default().Handler().Handle(ctx, record)`, then check
`slog.Default().Handler().Handle(ctx, record)`, then check the error it the error it returns. Console output takes the file and line from the record's
returns. Console output takes the file and line from the record's `PC`, `PC`, which you set with `runtime.Callers` as the `log/slog` documentation
which you set with `runtime.Callers` as the `log/slog` documentation
shows. A record whose `PC` is zero prints `???:0` instead. shows. A record whose `PC` is zero prints `???:0` instead.
## Entrypoints ## Entrypoints
@@ -154,23 +147,23 @@ alpine. We provide:
- `script/test` — run the test suite under the race detector in Docker by - `script/test` — run the test suite under the race detector in Docker by
building only the `test` stage of the `Dockerfile`, without the build cache, building only the `test` stage of the `Dockerfile`, without the build cache,
so every run tests; the image is tagged `simplelog-test` so every run tests; the image is tagged `simplelog-test`
- `script/lint` — run golangci-lint in Docker by building only the `lint` - `script/lint` — run golangci-lint in Docker by building only the `lint` stage
stage of the `Dockerfile`, without the build cache, so every run lints; the of the `Dockerfile`, without the build cache, so every run lints; the image is
image is tagged `simplelog-lint` tagged `simplelog-lint`
- `script/fmt` — format all files (writes): Go with the goimports that - `script/fmt` — format all files (writes): Go with the goimports that
`script/bootstrap` installs, then every `*.md` file with `prettier` `script/bootstrap` installs, then every `*.md` file with `prettier`
- `script/fmt-check` — check formatting (read-only); fails if `gofmt -l` - `script/fmt-check` — check formatting (read-only); fails if `gofmt -l` reports
reports files, or if `prettier` would change any `*.md` file files, or if `prettier` would change any `*.md` file
- `script/check` — run all checks: `test`, `lint`, `fmt-check` (our own - `script/check` — run all checks: `test`, `lint`, `fmt-check` (our own
extension) extension)
- `script/docker` — build the Docker image without the build cache, tagged - `script/docker` — build the Docker image without the build cache, tagged via
via `script/projectname` (byte-identical across repos); it also passes the `script/projectname` (byte-identical across repos); it also passes the output
output of `git describe` as the `VERSION` build argument, which is ignored of `git describe` as the `VERSION` build argument, which is ignored because
because this `Dockerfile` does not declare it this `Dockerfile` does not declare it
- `script/cibuild` — what CI runs: `script/bootstrap`, then `script/check`, - `script/cibuild` — what CI runs: `script/bootstrap`, then `script/check`, then
then the same image build as `script/docker` (byte-identical across repos) the same image build as `script/docker` (byte-identical across repos)
- `script/precommit` — run by the git pre-commit hook (our own extension); - `script/precommit` — run by the git pre-commit hook (our own extension); runs
runs a `go mod tidy` guard, then `script/check` a `go mod tidy` guard, then `script/check`
- `script/install-precommit` — installs the git pre-commit hook (our own - `script/install-precommit` — installs the git pre-commit hook (our own
extension); `make hooks` shims to it extension); `make hooks` shims to it
+73 -74
View File
@@ -1,90 +1,89 @@
# Workflow # Workflow
* branch (from `main`) - branch (from `main`)
* do the work in Next Step - do the work in Next Step
* move Next Step to the top of Completed Steps - move Next Step to the top of Completed Steps
* move the top item of Future Steps into Next Step - move the top item of Future Steps into Next Step
* commit (`TODO.md` changes in the same commit as the work) - commit (`TODO.md` changes in the same commit as the work)
* merge to `main` if the branch is not protected, otherwise open a PR - merge to `main` if the branch is not protected, otherwise open a PR
* push - push
# Status # Status
1.0+ 1.0+
Tagged v1.0.0 (2024-06-14) and 1.0.1 (2026-02-08). In post-1.0 Tagged v1.0.0 (2024-06-14) and 1.0.1 (2026-02-08). In post-1.0 maintenance; the
maintenance; the library is referenced by the Go styleguide. library is referenced by the Go styleguide.
# Next Step # Next Step
Restructure README.md into the standard sections: Description, Getting Restructure README.md into the standard sections: Description, Getting Started,
Started, Rationale, Design, TODO, License, Author Rationale, Design, TODO, License, Author
# Completed Steps # Completed Steps
* 2026-10-06: `make fmt` and `make fmt-check` also format Markdown, with - 2026-10-06: `make fmt` and `make fmt-check` also format Markdown, with
`prettier` at the standard settings, pinned through `package.json` and `prettier` at the standard settings, pinned through `package.json` and
`yarn.lock`; `script/bootstrap` installs node and yarn for it, and the `yarn.lock`; `script/bootstrap` installs node and yarn for it, and the
existing Markdown was formatted once existing Markdown was formatted once
* 2026-10-06: re-vendored the standard files from `sneak/prompts` at - 2026-10-06: re-vendored the standard files from `sneak/prompts` at commit
commit `dd4027b`: `REPO_POLICIES.md`, the CI workflow, `.gitignore`, `dd4027b`: `REPO_POLICIES.md`, the CI workflow, `.gitignore`, and
and `.golangci.yml` together with the lint stage's new image, plus `.golangci.yml` together with the lint stage's new image, plus the
the `.editorconfig` and `.dockerignore` this repo lacked, which `.editorconfig` and `.dockerignore` this repo lacked, which finishes the
finishes the Makefile and policy-files step. `script/cibuild` now Makefile and policy-files step. `script/cibuild` now bootstraps and runs
bootstraps and runs `script/check` before building the image, the `script/check` before building the image, the format check runs only on the
format check runs only on the host, the last `Dockerfile` stage runs host, the last `Dockerfile` stage runs `script/bootstrap`, which installs
`script/bootstrap`, which installs goimports at a pinned commit goimports at a pinned commit
* 2026-10-06: a webhook request now times out after 5 seconds, so a - 2026-10-06: a webhook request now times out after 5 seconds, so a server that
server that never answers no longer stops every log call; the webhook never answers no longer stops every log call; the webhook handler also reads
handler also reads each answer to the end so its connection is reused each answer to the end so its connection is reused
* 2026-10-06: the console handler takes the file and line it prints from - 2026-10-06: the console handler takes the file and line it prints from the
the record's `PC` instead of counting stack frames, so they name the record's `PC` instead of counting stack frames, so they name the call site
call site whether the record came through a `slog.Logger` or straight whether the record came through a `slog.Logger` or straight to `Handle`
to `Handle` - 2026-10-06: the tests run under the race detector, in Docker: `script/test`
* 2026-10-06: the tests run under the race detector, in Docker: builds the `test` stage of the `Dockerfile`, which runs `go test -race`, and a
`script/test` builds the `test` stage of the `Dockerfile`, which runs new final stage makes a plain `docker build` run both the `lint` and `test`
`go test -race`, and a new final stage makes a plain `docker build` stages
run both the `lint` and `test` stages - 2026-10-06: the linter runs only in Docker: `script/lint` builds the `lint`
* 2026-10-06: the linter runs only in Docker: `script/lint` builds the stage of the `Dockerfile`, every `docker build` in `script/` runs without the
`lint` stage of the `Dockerfile`, every `docker build` in `script/` build cache, and `script/bootstrap` no longer installs golangci-lint
runs without the build cache, and `script/bootstrap` no longer - 2026-10-06: every handler now returns a failed delivery from `Handle` instead
installs golangci-lint of discarding it: console and JSON return the stdout write error, the webhook
* 2026-10-06: every handler now returns a failed delivery from `Handle` also fails on a non-2xx answer, and the multiplex delivers to every handler
instead of discarding it: console and JSON return the stdout write and returns their errors joined
error, the webhook also fails on a non-2xx answer, and the multiplex - 2026-08-10: fixed every handler discarding slog attributes: console, JSON and
delivers to every handler and returns their errors joined webhook handlers now emit record attributes, accumulate WithAttrs without
* 2026-08-10: fixed every handler discarding slog attributes: console, mutating the receiver, and honour WithGroup; slog.Group values nest and
JSON and webhook handlers now emit record attributes, accumulate LogValuer values are resolved; console keys and values holding invalid UTF-8
WithAttrs without mutating the receiver, and honour WithGroup; are quoted
slog.Group values nest and LogValuer values are resolved; console - 2026-08-07: added canonical `.golangci.yml` (v2 schema), pinned the
keys and values holding invalid UTF-8 are quoted `Dockerfile` lint stage to golangci-lint v2.12.2 (tag+digest), and fixed all
* 2026-08-07: added canonical `.golangci.yml` (v2 schema), pinned the findings the v1→v2 jump surfaced without changing any exported signatures or
`Dockerfile` lint stage to golangci-lint v2.12.2 (tag+digest), and behavior
fixed all findings the v1→v2 jump surfaced without changing any
exported signatures or behavior * 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, Makefile
- 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, shims, README Entrypoints section
Makefile shims, README Entrypoints section
* 2026-02-08: fixed JSONHandler deadlock from recursive log.Println, - 2026-02-08: fixed JSONHandler deadlock from recursive log.Println, with
with regression test; tagged 1.0.1 regression test; tagged 1.0.1
* 2024-06-14: 1.0 prep: lint and fmt enforced in Docker build, call - 2024-06-14: 1.0 prep: lint and fmt enforced in Docker build, call stack depth
stack depth fix so log locations report correctly, example script, fix so log locations report correctly, example script, removed non-building
removed non-building RELP code; tagged v1.0.0 RELP code; tagged v1.0.0
* 2024-05-22: module path moved to sneak.berlin/go/simplelog - 2024-05-22: module path moved to sneak.berlin/go/simplelog
* 2024-05-14: initial library: slog-based console, JSON, webhook, and - 2024-05-14: initial library: slog-based console, JSON, webhook, and RELP
RELP handlers, MultiplexHandler, caller file/line info, UTC ISO handlers, MultiplexHandler, caller file/line info, UTC ISO timestamps,
timestamps, level-colored output level-colored output
# Future Steps # Future Steps
* Delete the old plain TODO file once this TODO.md lands - Delete the old plain TODO file once this TODO.md lands
* Add .aider.* to .gitignore and remove the stray aider artifacts from - Add .aider.\* to .gitignore and remove the stray aider artifacts from the
the working tree working tree
* Pick one tag scheme before the next release (v1.0.0 vs 1.0.1 are - Pick one tag scheme before the next release (v1.0.0 vs 1.0.1 are inconsistent)
inconsistent) - Tag v1.0.2, with the leading v, once the attribute fix lands, so consuming
* Tag v1.0.2, with the leading v, once the attribute fix lands, so repos can move off pseudo-version pins in one step
consuming repos can move off pseudo-version pins in one step - Fix RELP output to cache (from old TODO)
* Fix RELP output to cache (from old TODO) - Re-add RELP delivery over TCP to remote rsyslog imrelp; removed 2024-06-14
* Re-add RELP delivery over TCP to remote rsyslog imrelp; removed because it did not build (README planned feature)
2024-06-14 because it did not build (README planned feature) - Add regex filtering for webhook logs (from old TODO)
* Add regex filtering for webhook logs (from old TODO) - Better console output format (from old TODO)
* Better console output format (from old TODO)