Sentrinel โ what each page does
A tour of every screen: what it answers, where its data comes from, and what you need to have configured for it to show anything. Written against the shipped build โ everything here works today.
Two data paths feed the whole product:
- Aggregated metrics โ counters the plugin computes in-process and flushes every 30s, stored in Postgres. Exact regardless of sampling, cheap to query over long windows. These drive Traffic, Performance, Consumers, Errors.
- Raw events โ individual request logs, application logs and spans, stored in ClickHouse in production. Detailed but sampled. These drive Request logs, the Trace waterfall, and Apdex.
When a page looks empty, it is almost always because one of those two paths isn't enabled โ the tables below say which.
Apps
Answers: which services report to me, and is anything on fire right now?
Cards for each registered app with requests, error rate, p95, and consumer count for the selected window, plus a sparkline. This is the app switcher: the app selected here scopes every other page.
Apps are created two ways โ explicitly (Create app) or automatically the first time a plugin authenticates with a key for a name it hasn't seen.
Traffic
Answers: how much am I serving, and which endpoints carry it?
A time series of requests/errors/success plus a per-endpoint table sorted by volume, with error rate, average latency and bytes transferred. Drag on any chart to zoom into a window; every page respects that selection.
Click an endpoint for its own view: requests and latency over time, a response time histogram, status-code breakdown, and โ if request logging is on โ the most recent requests, slowest requests, and errors for that endpoint specifically.
Those three lists come from raw logs. With only metrics ingest enabled the charts fill in and the lists stay empty; that is expected, not a bug.
Source: aggregated metrics; endpoint lists from raw logs.
Errors
Answers: what is failing, on which endpoints, for whom?
Client (4xx) vs server (5xx) errors over time, grouped by status code and endpoint, with occurrence counts and affected-consumer counts. Good for "we're returning a lot of 422s" โ for "why", go to Issues.
Source: error ingest.
Issues
Answers: what are the distinct bugs, and are they getting better?
Every error occurrence is fingerprinted from its type and normalized stack, so a crash firing 50,000 times is one row, not 50,000. Each issue carries a title, culprit (the top frame in your code), occurrence count, first/last seen, and affected consumers.
Issues have a lifecycle: resolve one, and if it recurs it comes back automatically marked as a regression rather than quietly reopening. Bulk resolve/ignore from the list.
Each issue can be assigned to a team member, and the list filters by assignee โ including Unassigned, which is usually the one you are hunting. A list of bugs becomes a list of work once each has a name on it.
Open an issue for its occurrence timeline, affected endpoints and consumers, and recent raw occurrences with full stack traces. Every occurrence carries Open the request โ and its trace id, so a bug reaches the exact request that produced it rather than dead-ending at a stack trace.
Source: error ingest with stack traces. Thrown exceptions are captured with their real stack โ the plugin hooks the error path before the framework turns them into a response, which is what makes grouping work.
Performance
Answers: is it fast, and where is the time going?
p50/p95/p99 over time, a latency histogram, slowest endpoints, and Apdex โ a single 0โ1 satisfaction score. Apdex counts each request as satisfied (โค your threshold), tolerating (โค 4ร), or frustrated, and weights them.
Set the threshold per app, or override it per endpoint โ a report generator and a login endpoint should not be judged the same way.
Source: metrics for the percentiles; raw logs for Apdex, which needs per-request timings. No request logging means no Apdex.
Consumers
Answers: who is calling me, how much, and who is having a bad time?
Per-client request volume, error rate, and average latency. A consumer is
whatever consumerIdentifier returns โ an API key name, tenant id, or app name.
Open one for its traffic over time, its most recent requests, and its errors tab: exactly what failed for that client, with status, endpoint and message. This is the "customer says the API is broken" screen.
Source: consumer metrics; per-consumer lists from raw logs.
Request logs
Answers: show me this exact request.
A searchable, filterable feed of individual requests: method, path, status, latency, consumer, timestamp. Filter by status class, method, path, or log level. Go live streams new requests as they arrive (SSE).
Open one for the full picture:
| Tab | Contents |
|---|---|
| Details | Timing, sizes, consumer, environment, plus any business context the app attached with addRequestContext() |
| Timeline | Logs, spans and the outcome interleaved in one trail โ "the fraud check ran, the bureau call took 848 ms, then we declined" |
| Logs | Everything logged while handling this request, with categories and attributes |
| Payloads | Request and response bodies, masked as configured |
| Headers | Request headers, masked as configured |
| Trace | The span waterfall for this request |
| Related | The same client's requests immediately before and after |
Related is the one people underuse: it reconstructs what a client was doing around a failure, which is usually where the cause is.
Requires requestLogging.enabled. Bodies and headers additionally require
logRequestBody / logResponseBody / logRequestHeaders.
Logs vs Request logs
Two different things with confusingly similar names, so plainly:
| Request logs | Logs | |
|---|---|---|
| One row per | HTTP request your app served | logger call your code made |
| Written by | the plugin, automatically | your code, deliberately |
| Answers | "show me this exact request" | "what did the code say, and about whom" |
They are two views of the same moment. A request lists exactly what it logged (its Logs tab); a log record opens the request it was written during. Neither is a subset of the other โ a request that logged nothing still has a row, and a background job's logs belong to no request at all.
Logs
Answers: what did the code actually say, and about whom?
A log explorer built around fields rather than text. The message is the stable,
groupable part ("Checkout rejected"); the attributes are what you filter on
(reason=insufficient_funds, tier=enterprise, consumer=mobile_app).
The level tiles double as filters โ click WARN for warnings and above. The
category dropdown filters a whole subsystem (payment selects payment.checkout
and payment.fraud). Clicking any attribute value in a row adds it as a filter,
which is how you get from "something is wrong" to "it is wrong for this one
customer on this one endpoint" without writing a query. Go live tails
matching records as they arrive.
Fields opens the vocabulary of your own telemetry: every attribute key in
play with its commonest values, each clickable as a filter. You should not have
to already know that checkout attaches tier and fraud attaches riskBand.
Click a record to open it in full โ three tabs:
- Record โ the message, every attribute, and whether this message is a one-off or a pattern
- Same request โ everything else that request logged, in order, with this record highlighted
- Connections โ the request being handled (with the business context the handler attached), the trace and span, and the issue if that request failed
From there, "every log in this trace" pulls the records from every service that took part, and "open the waterfall" jumps to the spans.
The reverse direction works too โ every request has a Logs tab listing exactly what it logged.
Requires logging: { } in the plugin (or logCapture for plain console
output). See PLUGIN.md.
Traces
Answers: where did the time actually go inside this request?
A list of traces, and a proper waterfall for each: spans nested by parent, all on one timeline, coloured by kind (SERVER / CLIENT / DB / INTERNAL).
Click any span for its duration, self time, status, and full attributes. Self time is the number to look at โ a 271 ms span that spent 245 ms waiting on a child isn't the problem, its child is. The panel calls out the slowest span by self time so you can start there.
Root spans link through to the request log entry that produced them, and
traceparent propagation means a call into another instrumented service
continues the same trace rather than starting a new one.
Requires traceSpan()/traced() in your code, or an OTLP exporter. Without
them you get the root HTTP span only.
Resources
Answers: is the host healthy?
CPU and memory (RSS, heap total, heap used) over time, reported by the plugin on every metrics flush. Useful for separating "the code got slower" from "the box ran out of memory".
Source: automatic โ no configuration.
Uptime
Answers: is it up from the outside?
HTTP checks on an interval, with uptime percentage, response time, and an incident timeline. Complements the plugin: the plugin can only report while your process is alive, so a check from outside is what catches a hard down.
Alerts
Answers: tell me before a customer does.
Threshold rules on error rate, p95 latency, or request count over a window, with Slack / Discord / generic webhook delivery. Alerts resolve themselves and send a recovery notice.
Each alert keeps its firing history, so you can see whether it is a real signal or noise that needs its threshold moved.
Set up channels first; an alert with no channel only changes colour in the UI.
SLOs
Answers: how much error budget is left this month?
Availability and latency objectives over a rolling window (default 30 days), with attainment, remaining error budget, and burn-down.
The difference from an alert: an alert fires on a moment, an SLO tracks a promise. "99.9% availability" means about 43 minutes of downtime a month โ the budget tells you how much you have spent.
Cron monitors
Answers: did my nightly job actually run?
The thing uptime checks cannot tell you. Each monitor gets a check-in URL; the job pings it on completion. If a ping doesn't arrive within its interval plus grace, the monitor is marked missed and notifies your channels.
Long jobs can report ?state=start, a ?duration=, or an explicit
?state=fail, giving you run duration history and distinguishing "failed" from
"never ran".
LLM cost
Answers: what is the AI spend, and who is spending it?
Cost, call count, token usage and latency broken down by model, by client, and by endpoint, priced from a built-in table.
Populated from GenAI spans โ gen_ai.request.model plus gen_ai.usage.*_tokens
โ so any OTel-instrumented LLM call is priced automatically.
Dashboards
Answers: the handful of numbers I check first thing.
Saved arrangements of views the product already computes โ requests, error rate, latency percentiles, top endpoints and clients, CPU, memory, open issues, LLM spend. Compose one in Settings โ Custom dashboards, then open it here.
A board saved without picking an app is org-wide and shows under every app; a board saved with one selected belongs to that app. Only the widgets a board actually uses are fetched, so a two-chart board makes two calls.
Status pages
Answers: telling customers before they ask.
A public page at /public/status/<slug> showing component health from your
uptime checks, plus incidents you post. No authentication โ deliberately: a
status page behind a login is useless during the outage it exists for.
SQL console
Answers: the question no dashboard anticipated.
Read-only SQL over the telemetry tables, with guardrails: a single SELECT or
WITH, allowlisted tables only, no system catalogs, a hard row cap, and no
access to users or sessions โ so it can never read a password hash or steal
a session token.
On a multi-tenant instance results are scoped to your own applications, which is
why the built-in examples all project app_id: the filter needs that column.
Settings
Account, password, team members and roles (owner / admin / member / viewer), per-app Apdex thresholds, custom dashboard configs, deploy history, and your organization's quota usage for the month.
What is not here
Being straight about the edges:
- Billing. Plans and per-org quotas exist and are enforced (
429over quota), but no payment provider is wired up. - Email alerts. Slack, Discord and webhooks work; email is not implemented.
- Session replay, RUM, product analytics, feature flags. Out of scope โ they need a browser SDK, which is a different product.
- On-call scheduling. Point the webhook channel at PagerDuty or Better Stack rather than rebuilding it.
- The SQL console reads Postgres, so at ClickHouse scale it queries the rollups rather than raw logs.