Skip to content

Your first event

This is the program from the library’s README. It builds one flusher, hands it to a Producer, checks the connection and sends a single exception.

// source: README.md#L31-L71
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/kataras/patrol"
)
func main() {
ctx := context.Background()
slack, err := patrol.NewSlack(patrol.SlackOptions{
Token: "xoxb-...",
Channels: []string{"C0123456789"},
})
if err != nil {
log.Fatal(err)
}
producer := patrol.NewProducer(patrol.ProducerOptions{
ProjectName: "My Project",
BufferInterval: 5 * time.Second,
OnError: func(err error) { log.Println("patrol:", err) },
}, slack)
defer producer.Close(ctx)
// Optional: verify every integration before sending anything.
if err := producer.Test(ctx); err != nil {
log.Println(err)
}
e := patrol.NewException(fmt.Errorf("payment failed")).
WithField("UserEmail", "user@example.com").
WithMentions("U0123456789")
if err := producer.SendEvent(ctx, e); err != nil {
log.Fatal(err)
}
}

patrol.NewSlack validates Token and Channels and returns an error when either is missing. Every integration constructor works this way: it checks the required fields before it builds a client, so a typo in a config file fails at startup instead of at the first incident.

patrol.NewProducer takes the options and the flushers. It returns a *Producer and no error, because it has no required field, and it starts a background goroutine that owns the buffer. BufferInterval: 5 * time.Second is also the default; the batch flushes at that interval or when it reaches MaxBatchSize, which defaults to 100.

producer.Test(ctx) calls the Test method of every flusher that has one, at the same time, and returns their errors joined and prefixed with the flusher name. Slack’s Test calls auth.test and checks that a bot id came back. It is optional and it costs one round trip per flusher, so most services call it once at startup and log the result rather than exiting on it.

patrol.NewException(err) creates an event of type Error, keeps the error, captures a stacktrace and uses the error’s text as the message. WithField and WithMentions are two of the builders on the Events page; every one of them returns the event, so they chain.

producer.SendEvent(ctx, e) stamps the capture time, fills in the project name when the event has none, removes duplicate fields and puts the event on a buffered channel. It does not touch the network, so the goroutine that hit the error is not waiting on Slack. It returns an error only when the buffer is full and ctx ends first, or when the producer is already closed.

defer producer.Close(ctx) is what makes the last batch arrive. Without it the program can exit with events still in the buffer. Shutdown covers what Close waits for and what happens when its context runs out.

One Slack message per channel and project, carrying one attachment per event: the fields, the message, the stacktrace with a vscode:// link on each frame, any uploaded files, and the mentions. The attachment is coloured by event type, red for Error, blue for Info, yellow for Debug.

SendEvent returning nil means the event is buffered, not that it was delivered. Delivery errors arrive later, on the OnError callback, which is why the example sets one. Leave it unset and the default writes them through log/slog at error level.

The three usual causes, in the order they come up:

  • The batch has not flushed yet. Wait out BufferInterval, or call Close.
  • The event is a Debug event and debug mode is off, in which case SendEvent drops it and returns nil. See Debug events.
  • The flusher failed and OnError is not looking. The joined error names the flusher: slack: invalid_auth.

Next: what the Producer does with a batch.