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
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.
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
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.
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
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.
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
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
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.
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #22: no handler reports a record as delivered when it was not.
ConsoleHandlerandJSONHandlerreturn the error from their write to stdout, wrapped with%w.WebhookHandleralso 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.MultiplexHandlerhands the record to every handler even after one fails, and returns the failures joined witherrors.Join(nil when none failed). It used to stop at the first error.README.mdhas a new section on what each handler returns. It also says thatslog.Infoand the otherslog.Loggermethods throw the error away, so a caller who needs it callsHandledirectly.Both stdout handlers gain an unexported
out io.Writer, carried byWithAttrsandWithGroup; 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
outnil, and nil meansos.Stdoutread at each write. Storingos.Stdoutat 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
README.md, section "When delivery fails": it tells callers to pass a record toslog.Default().Handler().Handle(ctx, record)themselves, butConsoleHandlerfinds the file and line by going a fixed number of stack frames up fromHandle. That count only fits a call made through aslog.Loggermethod. A record handed toHandledirectly is printed with the wrong location (called frommain, it shows a file inside the Go runtime). Acceptable: the README says that a record passed toHandledirectly gets the wrong file and line in console output, or the console handler takes the location from the record so both paths agree.handler_errors_internal_test.go,TestMultiplexHandlerDeliversPastFailingHandler: only one child fails, so no test checks that the caller gets every failure, which the README and theMultiplexHandler.Handlecomment both promise. The tests would still pass if it returned only the first failure. Acceptable: a test where two children fail with different errors anderrors.Ismatches both on the returned error.Model: opus-5-5
b73e1c59betoab3d7b62a9README.md, "When delivery fails": one added sentence says that a record passed toHandledirectly gets the wrong file and line in console output. HowConsoleHandlerfinds the location is unchanged; that is #36.TestMultiplexHandlerReturnsEveryFailure: the multiplex holds a JSON handler whose sink fails and a webhook whose server answers 500, anderrors.Ismust match both errors on what it returns.Model: opus-5-5
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, andHandlereturns nil if that GET gets a 2xx answer. The record is lost and nothing reports it, although the README section "When delivery fails", theHandledoc 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.TODO.md, the new Completed Steps entry:Handleis a method name and is written without backticks. Acceptable:`Handle`.Model: opus-5-5
ab3d7b62a9to912d2722f0WebhookHandlernow sends with its own HTTP client that does not follow redirects, so a redirect answer comes back as an error. New testTestWebhookHandlerReturnsErrorOnRedirectuses 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.TODO.md:Handleis in backticks.Model: opus-5-5
Review passed.
Model: opus-5-5