Stacktraces
Three calls capture a stacktrace: patrol.NewException(err), event.WithError(err) and event.WithDebug(message). All three use patrol.NewStacktrace(), which you can also call yourself and attach with event.WithStacktrace(st). Passing nil to WithStacktrace removes one.
What is in it
Section titled “What is in it”A Stacktrace is one field, Frames []Frame, outermost first. The frame you are reading is at the top of the list, main at the bottom, which is the opposite of a Go panic dump and the same order most issue trackers use.
| Field | JSON | What it holds |
|---|---|---|
Function |
function |
The function name without its package path |
Module |
module |
The import path of the package that holds it |
Filename |
filename |
The file path as the runtime reported it, when that path is relative |
AbsPath |
abs_path |
The absolute file path, when the runtime reported one |
Lineno |
lineno |
The line in that file |
InApp |
in_app |
Whether the frame looks like your code rather than a library’s |
Exactly one of Filename and AbsPath is set per frame. A build with trimmed paths reports relative ones, and then Filename carries them. Both fields are omitempty, and so is every other one, so a frame in the JSON is as short as the facts allow.
At most 64 frames are captured. NewStacktrace returns nil when the runtime gives it none.
What is dropped
Section titled “What is dropped”Noise, before you ever see it:
- Every frame whose module is
runtimeortesting. - Every frame inside
github.com/kataras/patrolitself, unless the file ends in_test.go. The call that captured the trace is not interesting; the code that hit the error is. - Every frame whose module starts with a prefix listed in
patrol.SkipModuleFrames.
InApp is a guess, not a filter. It is false for an empty module and for any path containing vendor or third_party, true for main, and otherwise true when the first element of the import path contains a dot, which is how a module path is spelled and a standard library path is not. A console can grey out the rest; nothing in the library acts on it.
Hiding your own layers
Section titled “Hiding your own layers”A framework or a middleware package between the error and the handler shows up in every trace. AddSkipModuleFrames takes prefixes, matched against a frame’s module:
package main
import ( "context" "errors" "log"
"github.com/kataras/patrol")
func init() { // Before any stacktrace is captured: this slice is read without a lock. patrol.AddSkipModuleFrames( "github.com/kataras/iris/v14", "github.com/example/internal/middleware", )}
func main() { ctx := context.Background() producer := patrol.NewProducer(patrol.ProducerOptions{ProjectName: "checkout-api"}) defer producer.Close(ctx)
if err := producer.CaptureException(ctx, errors.New("charge declined")); err != nil { log.Fatal(err) }}SkipModuleFrames is a plain exported slice and AddSkipModuleFrames appends to it without a lock. Call it from init or from the top of main, before the first event. Adding to it while another goroutine is capturing a trace is a data race, and the race detector will say so.
Editor deep links
Section titled “Editor deep links”A frame with an AbsPath renders as a link. Slack and the email template both build vscode://file/<path>:<line> and show <path>:<line> as the text, so a frame in a Slack thread opens in the editor on the machine reading it. A frame with only a relative Filename renders as plain text, because a relative path means nothing on someone else’s disk.
That is also why a service built with -trimpath produces traces you can read but cannot click.
Reading one in the JSON
Section titled “Reading one in the JSON”{ "stacktrace": { "frames": [ { "function": "main", "module": "main", "abs_path": "/srv/checkout/main.go", "lineno": 41, "in_app": true }, { "function": "Handler.Charge", "module": "github.com/example/checkout/http", "abs_path": "/srv/checkout/http/handler.go", "lineno": 118, "in_app": true }, { "function": "chargeCard", "module": "github.com/example/checkout/payments", "abs_path": "/srv/checkout/payments/stripe.go", "lineno": 64, "in_app": true } ] }}Outermost first: main called the handler, the handler called chargeCard, and chargeCard is where the error was captured.
Next: pick a destination.