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:

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:

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: