Skip to content

Debug events

A Debug event is delivered only while the producer’s debug mode is on. While it is off, SendEvent drops the event and returns nil, before the capture time is stamped and before anything reaches the channel.

That is the point of the type. Debug calls can stay in the code on the path you need them on, and the cost of leaving them there is one atomic load plus whatever you spent building the event.

package main
import (
"context"
"log"
"github.com/kataras/patrol"
)
func main() {
ctx := context.Background()
producer := patrol.NewProducer(patrol.ProducerOptions{ProjectName: "checkout-api"})
defer producer.Close(ctx)
// Dropped: debug mode is off. Returns nil.
if err := producer.SendEvent(ctx, patrol.NewEvent(patrol.Debug).WithMessage("cache miss")); err != nil {
log.Println(err)
}
producer.Debug(true)
// Delivered.
if err := producer.SendEvent(ctx, patrol.NewEvent(patrol.Debug).WithMessage("cache miss")); err != nil {
log.Println(err)
}
}

patrol.NewEvent(patrol.Debug) sets the type and nothing else. event.WithDebug("message") sets the type, the message and a fresh stacktrace in one call, which is what you want when the interesting part is where the code was.

producer.Debug(true) turns debug mode on for the whole producer until something turns it off. producer.Debug(false) turns it off, except while a Debugging session is running: a session has asked for debug mode and keeps it until it ends.

Debugging runs a function with debug mode forced on and sends the event that function returns:

package main
import (
"context"
"fmt"
"log"
"github.com/kataras/patrol"
)
func main() {
ctx := context.Background()
producer := patrol.NewProducer(patrol.ProducerOptions{ProjectName: "checkout-api"})
defer producer.Close(ctx)
err := producer.Debugging(ctx, func(ctx context.Context) (*patrol.Event, error) {
total, err := recalculate(ctx)
if err != nil {
return nil, err
}
return patrol.NewEvent(patrol.Debug).
WithMessage("recalculated the basket").
WithField("Total", total), nil
})
if err != nil {
log.Println("debugging:", err)
}
}
func recalculate(context.Context) (int, error) {
return 1299, fmt.Errorf("basket 84213: no such row")
}

The rules it follows:

  • The event you return is sent as a Debug event. Its type is overwritten, so returning an Info event does not sneak past the switch.
  • Returning nil, nil sends nothing and returns nil. That is the normal path when the block found nothing worth reporting.
  • Returning an error wraps it as patrol: debugging transaction failed: ..., sends it as an Error event, and returns it. So a failed debugging block reports itself even though debug delivery is what you were testing.
  • Sessions may overlap. Debug mode turns off when the last one ends, not when the first one does.
  • Close waits for every active session before it drains the buffer, so a session in flight cannot lose its event.
  • After Close, Debugging returns patrol.ErrClosed without running the function.

A Debug event is an ordinary event in the batch once it is buffered: same sort, same merge, same fan-out. Slack colours its attachment yellow, #aabb15. The SQL table stores it with type = 3. Nothing filters it out downstream, which is worth remembering before turning debug mode on in a service that pages someone.

Next: shutdown.