Return sink write errors from every handler (closes #22) #35

Merged
clawbot merged 1 commits from issue-22-handler-write-errors into next 2026-10-06 08:45:18 +02:00
Collaborator

Fixes #22: no handler reports a record as delivered when it was not.

  • ConsoleHandler and JSONHandler return the error from their write to stdout, wrapped with %w.
  • WebhookHandler also returns an error when the server answers with a status outside 2xx. It does not follow redirects, which can resend the request without the record, so a redirect is returned as an error.
  • MultiplexHandler hands the record to every handler even after one fails, and returns the failures joined with errors.Join (nil when none failed). It used to stop at the first error.
  • README.md has a new section on what each handler returns. It also says that slog.Info and the other slog.Logger methods throw the error away, so a caller who needs it calls Handle directly.

Both stdout handlers gain an unexported out io.Writer, carried by WithAttrs and WithGroup; the constructors keep their signatures. The new tests live in an internal test file so they can set it to a writer that always fails.

Deviation from the plan (#22 (comment)): the constructors leave out nil, and nil means os.Stdout read at each write. Storing os.Stdout at construction would pin the default handler, built at import, to the original stdout, and would break two existing tests that redirect stdout after building a handler.

Judgement call: the webhook status error wraps an unexported sentinel, so callers get no new exported name to match on.

Judgement call: a webhook URL that redirects now fails every record, including a 307 or 308 that used to resend it intact.

Model: opus-5-5

Fixes https://git.eeqj.de/sneak/simplelog/issues/22: no handler reports a record as delivered when it was not. - `ConsoleHandler` and `JSONHandler` return the error from their write to stdout, wrapped with `%w`. - `WebhookHandler` also returns an error when the server answers with a status outside 2xx. It does not follow redirects, which can resend the request without the record, so a redirect is returned as an error. - `MultiplexHandler` hands the record to every handler even after one fails, and returns the failures joined with `errors.Join` (nil when none failed). It used to stop at the first error. - `README.md` has a new section on what each handler returns. It also says that `slog.Info` and the other `slog.Logger` methods throw the error away, so a caller who needs it calls `Handle` directly. Both stdout handlers gain an unexported `out io.Writer`, carried by `WithAttrs` and `WithGroup`; the constructors keep their signatures. The new tests live in an internal test file so they can set it to a writer that always fails. Deviation from the plan (https://git.eeqj.de/sneak/simplelog/issues/22#issuecomment-126814): the constructors leave `out` nil, and nil means `os.Stdout` read at each write. Storing `os.Stdout` at construction would pin the default handler, built at import, to the original stdout, and would break two existing tests that redirect stdout after building a handler. Judgement call: the webhook status error wraps an unexported sentinel, so callers get no new exported name to match on. Judgement call: a webhook URL that redirects now fails every record, including a 307 or 308 that used to resend it intact. Model: opus-5-5
clawbot added the needs-review label 2026-10-06 02:34:09 +02:00
clawbot self-assigned this 2026-10-06 02:34:09 +02:00
Author
Collaborator
  1. README.md, section "When delivery fails": it tells callers to pass a record to slog.Default().Handler().Handle(ctx, record) themselves, but ConsoleHandler finds the file and line by going a fixed number of stack frames up from Handle. That count only fits a call made through a slog.Logger method. A record handed to Handle directly is printed with the wrong location (called from main, it shows a file inside the Go runtime). Acceptable: the README says that a record passed to Handle directly gets the wrong file and line in console output, or the console handler takes the location from the record so both paths agree.

  2. handler_errors_internal_test.go, TestMultiplexHandlerDeliversPastFailingHandler: only one child fails, so no test checks that the caller gets every failure, which the README and the MultiplexHandler.Handle comment both promise. The tests would still pass if it returned only the first failure. Acceptable: a test where two children fail with different errors and errors.Is matches both on the returned error.

Model: opus-5-5

1. `README.md`, section "When delivery fails": it tells callers to pass a record to `slog.Default().Handler().Handle(ctx, record)` themselves, but `ConsoleHandler` finds the file and line by going a fixed number of stack frames up from `Handle`. That count only fits a call made through a `slog.Logger` method. A record handed to `Handle` directly is printed with the wrong location (called from `main`, it shows a file inside the Go runtime). Acceptable: the README says that a record passed to `Handle` directly gets the wrong file and line in console output, or the console handler takes the location from the record so both paths agree. 2. `handler_errors_internal_test.go`, `TestMultiplexHandlerDeliversPastFailingHandler`: only one child fails, so no test checks that the caller gets every failure, which the README and the `MultiplexHandler.Handle` comment both promise. The tests would still pass if it returned only the first failure. Acceptable: a test where two children fail with different errors and `errors.Is` matches both on the returned error. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-06 04:51:59 +02:00
clawbot force-pushed issue-22-handler-write-errors from b73e1c59be to ab3d7b62a9 2026-10-06 06:13:37 +02:00 Compare
Author
Collaborator
  1. README.md, "When delivery fails": one added sentence says that a record passed to Handle directly gets the wrong file and line in console output. How ConsoleHandler finds the location is unchanged; that is #36.
  2. New test TestMultiplexHandlerReturnsEveryFailure: the multiplex holds a JSON handler whose sink fails and a webhook whose server answers 500, and errors.Is must match both errors on what it returns.

Model: opus-5-5

1. `README.md`, "When delivery fails": one added sentence says that a record passed to `Handle` directly gets the wrong file and line in console output. How `ConsoleHandler` finds the location is unchanged; that is https://git.eeqj.de/sneak/simplelog/issues/36. 2. New test `TestMultiplexHandlerReturnsEveryFailure`: the multiplex holds a JSON handler whose sink fails and a webhook whose server answers 500, and `errors.Is` must match both errors on what it returns. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-06 06:13:43 +02:00
Author
Collaborator
  1. webhook_handler.go, WebhookHandler.Handle: when the webhook answers the POST with a 301, 302 or 303 redirect, the HTTP client follows it with a GET that carries no record, and Handle returns nil if that GET gets a 2xx answer. The record is lost and nothing reports it, although the README section "When delivery fails", the Handle doc comment and the PR body all say a status outside 2xx is returned as an error. Acceptable: a redirect answer is returned as an error like any other status outside 2xx (for example, the handler uses its own HTTP client that does not follow redirects), with a test against a server that answers with a redirect.

  2. TODO.md, the new Completed Steps entry: Handle is a method name and is written without backticks. Acceptable: `Handle`.

Model: opus-5-5

1. `webhook_handler.go`, `WebhookHandler.Handle`: when the webhook answers the POST with a 301, 302 or 303 redirect, the HTTP client follows it with a GET that carries no record, and `Handle` returns nil if that GET gets a 2xx answer. The record is lost and nothing reports it, although the README section "When delivery fails", the `Handle` doc comment and the PR body all say a status outside 2xx is returned as an error. Acceptable: a redirect answer is returned as an error like any other status outside 2xx (for example, the handler uses its own HTTP client that does not follow redirects), with a test against a server that answers with a redirect. 2. `TODO.md`, the new Completed Steps entry: `Handle` is a method name and is written without backticks. Acceptable: `` `Handle` ``. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-06 08:02:49 +02:00
clawbot added 1 commit 2026-10-06 08:31:01 +02:00
Return sink write errors from every handler (closes #22)
check / check (push) Successful in 43s
check / check (pull_request) Successful in 51s
912d2722f0
The console and JSON handlers threw away the error from their write to
stdout, so a lost log line looked delivered. They now return it,
wrapped. The webhook handler also returns an error for a status outside
2xx, and no longer follows redirects, which could resend the request
without the record. The multiplex handler passes the record to every
handler and returns their errors joined with errors.Join instead of
stopping at the first.

Both stdout handlers gain an unexported writer, nil meaning os.Stdout at
write time, so tests can supply a sink that fails. The README says what
each handler returns and that the slog.Logger methods discard a
handler's error.

Model: opus-5-5
clawbot force-pushed issue-22-handler-write-errors from ab3d7b62a9 to 912d2722f0 2026-10-06 08:31:01 +02:00 Compare
Author
Collaborator
  1. WebhookHandler now sends with its own HTTP client that does not follow redirects, so a redirect answer comes back as an error. New test TestWebhookHandlerReturnsErrorOnRedirect uses a server that redirects the POST to a path answering 200. Judgement call: the README's webhook line now also says redirects are not followed.
  2. TODO.md: Handle is in backticks.

Model: opus-5-5

1. `WebhookHandler` now sends with its own HTTP client that does not follow redirects, so a redirect answer comes back as an error. New test `TestWebhookHandlerReturnsErrorOnRedirect` uses a server that redirects the POST to a path answering 200. Judgement call: the README's webhook line now also says redirects are not followed. 2. `TODO.md`: `Handle` is in backticks. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-06 08:38:41 +02:00
Author
Collaborator

Review passed.

Model: opus-5-5

Review passed. Model: opus-5-5
clawbot merged commit 151dd42b4b into next 2026-10-06 08:45:18 +02:00
clawbot deleted branch issue-22-handler-write-errors 2026-10-06 08:45:19 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/simplelog#35