Skip to content

Shutdown

producer.Close(ctx) is what makes the last batch arrive. A process that exits without it loses whatever was buffered, which on the defaults is up to five seconds of events.

package main
import (
"context"
"errors"
"log"
"time"
"github.com/kataras/patrol"
)
func main() {
producer := patrol.NewProducer(patrol.ProducerOptions{ProjectName: "checkout-api"})
serve(producer)
// Give delivery ten seconds, then leave.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := producer.Close(ctx); err != nil {
log.Println("patrol: shutdown did not finish in time:", err)
}
if err := producer.SendEvent(ctx, patrol.NewEvent(patrol.Info).WithMessage("too late")); !errors.Is(err, patrol.ErrClosed) {
log.Println("expected ErrClosed, got:", err)
}
}
func serve(*patrol.Producer) {}
  1. Stops new Debugging sessions. One already running is allowed to finish.
  2. Waits for every active session.
  3. Closes the event channel. SendEvent returns patrol.ErrClosed from this point on.
  4. The background loop drains what is left, flushes it once through every flusher, and returns.
  5. Every flusher that implements Closer gets its Close(ctx). An error there goes to OnError as sql: close: ....

Close returns nil once that sequence finishes. Delivery errors are not its return value: they go to OnError, like every other flush error, because one of six flushers failing is not a reason for a shutdown to report failure.

Close returns ctx.Err() and cancels the in-flight flush. The shutdown itself keeps going in the background: the buffer still drains and the Closers still run, with a cancelled context. Your process is free to exit at that point, and a flusher that respects context cancellation will stop early.

Pass a context with a deadline in a service that has to exit on time, and context.Background() in a short program where correctness beats the clock.

Close is safe to call more than once and from more than one goroutine. The second call waits on the same shutdown as the first.

patrol.ErrClosed is returned by SendEvent after the channel is closed and by Debugging once Close has started. Compare it with errors.Is. It means the producer is gone, not that the event was bad: nothing you can retry on the same producer will work.

There is no reopen. A producer is created by NewProducer and ends at Close.

producer.Test(ctx) is the other half of the lifecycle, at the start of it. It calls Test(ctx) on every flusher that implements Tester, concurrently, and returns their errors joined and prefixed with the flusher names. A flusher with no Test is skipped without complaint.

Every built-in Test wraps patrol.ErrTest, so errors.Is(err, patrol.ErrTest) is true for a failed connectivity check and false for anything else that went wrong on the way:

Flusher What Test does
Slack auth.test, and checks a bot id came back
Discord Reads every configured channel with the bot token
Twilio Lists one active account
Email Opens an SMTP session, upgrades to TLS, authenticates, quits
SQL Pings the database
HTTP client GET / against the server, which also checks the credentials

Each of those is a real round trip to someone else’s service. Call it at startup and log the result; a service that exits because Slack is having a bad morning is worse off than one that logs it and carries on.

Next: stacktraces.