Skip to main content

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 call

Any other language

Post the spans yourself, with these fields in each event's data:

Field
traceId, spanIdThe trace and this span (W3C trace context ids work as they are)
parentSpanIdThe span this one runs inside. A span without one is a transaction: a request the service served
opWhat kind of work it is: http.server, db.query, http.client, …
requestIdThe 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
sampleRateThe 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's flaggrErrors() adds a W3C traceparent header to the page's own-origin fetch requests (tracePropagationTargets widens 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 the x-request-id response header. A trace whose request span carries that id as requestId shows 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.