See your server-side services' throughput, latency percentiles and failures per route, open any request's trace as a waterfall, and follow a request from the browser through your services
Last updated October 18, 2026
Performance Monitoring and Tracing
The console's Performance page (/console/<project>/performance) shows the requests your server-side services handle, grouped into transactions. A transaction is a method and route such as GET /orders/{id}. For each one you see:
- how many requests it served, and how many a minute,
- p50, p75, p95 and p99 latency,
- the share that failed.
Open a transaction for its latency over time, its duration distribution, its slowest requests and its recent failures. Each request opens its trace: a waterfall of every span in the request, the errors reported in it, and the browser page load that made it.
Sending spans
Spans arrive through the same ingest as server errors, POST /api/ingest/server-events, with kind: "span".
Go
The Go SDK's errorreport package does it for you. Set TracesSampleRate and wrap your mux with its middleware:
reporter := errorreport.New(errorreport.Options{
Token: os.Getenv("FLAGGR_ERRORS_TOKEN"),
Release: "checkout-api@2.9.0",
TracesSampleRate: 0.1,
})
http.ListenAndServe(":8080", reporter.Middleware(mux)) // spans named by the mux pattern, "GET /orders/{id}"
span, ctx := reporter.StartSpan(r.Context(), "load order", "db.query") // a child span
defer span.End()
client := &http.Client{Transport: reporter.Transport(nil)} // traceparent to the services you callAny other language
Post the spans yourself, with these fields in each event's data:
| Field | |
|---|---|
traceId, spanId | The trace and this span (W3C trace context ids work as they are) |
parentSpanId | The span this one runs inside. A span without one is a transaction: a request the service served |
op | What kind of work it is: http.server, db.query, http.client, … |
requestId | The request's x-request-id. It joins the trace to the browser page load that made the request |
error, or status: "error" | Marks the span failed. A statusCode of 500 or more does too |
sampleRate | The share of traces your service keeps (0 to 1), so Flaggr can estimate the real request count |
The event itself carries name, method, route, statusCode, durationMs and timestamp (when the span started). A request with one database query, as two spans:
{
"events": [
{
"kind": "span", "name": "GET /orders/{id}", "method": "GET", "route": "/orders/{id}",
"statusCode": 200, "durationMs": 184, "timestamp": "2026-10-18T05:15:06.120Z",
"serviceId": "<service id>", "environment": "production", "release": "orders@1.9.0",
"data": { "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", "spanId": "00f067aa0ba902b7", "op": "http.server", "sampleRate": 0.1 }
},
{
"kind": "span", "name": "SELECT orders", "durationMs": 41, "timestamp": "2026-10-18T05:15:06.150Z",
"data": { "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", "spanId": "b7ad6b7169203331", "parentSpanId": "00f067aa0ba902b7", "op": "db.query" }
}
]
}A route should be the route's pattern, such as /orders/{id}, not the path a request used. One transaction per path would split a route's requests across thousands of rows.
Sampling and estimates
Flaggr keeps 10% of traces by default (SERVER_TELEMETRY_SPAN_SAMPLE_RATE), always whole traces, and always keeps failed spans. Each kept span records the rate it survived. Request counts, throughput and failure rates are estimates: each stored span counts as 1 / its rate. Percentiles are computed over the stored spans. Spans are kept for 7 days.
From the browser to your services
Two links tie a browser to the backend:
- Errors.
@flaggr/sdk'sflaggrErrors()adds a W3Ctraceparentheader to the page's own-originfetchrequests (tracePropagationTargetswidens or turns this off). It sends the header unsampled, so your service decides whether to trace the request, and the trace keeps the browser's id. The fetch breadcrumbs on a browser error record that trace id and link to the trace. Server errors captured in the request carry it too. - Page loads. The analytics script times each same-origin
/api/request a page makes, with thex-request-idresponse header. A trace whose request span carries that id asrequestIdshows the page load: which page, which browser, and how long the request took end to end, network included.
An issue whose event carries a traceId has a View trace link.
API
| Endpoint | |
|---|---|
GET /api/projects/{id}/performance?range=1h|24h|7d&serviceId= | Transactions, busiest first, with the window's totals and trend |
GET /api/projects/{id}/performance/transaction?name=GET%20/orders/{id}&range= | One transaction: stats, trend, duration histogram, slowest requests and recent failures |
GET /api/projects/{id}/traces/{traceId} | One trace: spans in tree order with their offsets, its errors and its browser page loads |
They need read access. From an AI assistant, the MCP tools get_performance and get_server_trace read the same data, and the flaggr-errors agent skill teaches it the setup. Callers who reach a project only through a public project get the timings without span attributes, error messages, page URLs or session ids.