Short answer: Use a metrics API as the primary backend for a user-facing admin analytics dashboard, then keep log search as a drill-down path for debugging individual events. Metrics map cleanly to cards and trend charts; repeatedly rebuilding those views from raw logs adds work without improving the dashboard.
That split is the useful decision. It also keeps the interface honest: an operator investigating one failed job needs different data than an admin checking whether weekly signups are rising.
Should a Node.js SaaS admin analytics dashboard use metrics or logs?
Start with the screen, not the storage product. A dashboard asks repeatable questions over time: How many users signed up? How many jobs finished? What did API latency look like? How many revenue events arrived? Those are metric-shaped questions. Each answer becomes a number, a time series, or a compact summary that the UI can request again and again.
Logs answer a different class of question. They preserve event detail, so they shine when someone needs to inspect the request, trace ID, span ID, or surrounding messages for a particular failure. A log search can support that investigation, but using it to regenerate every chart on every refresh makes the debugging store carry an analytics workload. It also couples chart logic to log text and event shape.
The before-and-after model is short. Before: dashboard request -> search raw events -> filter -> group -> aggregate -> chart. After: dashboard request -> query metrics -> chart, with a link from an unusual point to a focused log investigation.
Less machinery.
This recommendation has a hard boundary. If the product needs an auditable, replayable ledger of business events, neither pre-aggregated metrics nor operational logs should be treated as that ledger. Keep durable domain events in the system of record, derive metrics for display, and send operational detail to logs. For product funnels, paths, and retention, a dedicated product analytics system may also fit better than either observability primitive.
A concrete request path for the dashboard
A small Node.js service can place one server-side adapter between the admin UI and the observability provider. The browser calls your adapter; the adapter holds credentials, requests metrics, normalizes the provider response, and applies your authorization policy. Don't expose an observability key to the browser.
The route below is intentionally narrow. It calls the verified GET /v1/metrics/query path without invented filters because that route's filtering parameters are not declared. Set OBSERVABILITY_API_BASE to the provider base URL and keep the secret in INFRAI_API_KEY. The retry branch handles the one status that invites a retry: HTTP 429.
const baseUrl = process.env.OBSERVABILITY_API_BASE;
const apiKey = process.env.INFRAI_API_KEY;
if (!baseUrl || !apiKey) {
throw new Error("OBSERVABILITY_API_BASE and INFRAI_API_KEY are required");
}
const sleep = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function queryMetrics(maxAttempts = 4): Promise<unknown> {
const url = new URL("/v1/metrics/query", baseUrl);
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
},
});
if (response.status === 429 && attempt + 1 < maxAttempts) {
const retryAfter = response.headers.get("retry-after");
const retryAfterMs = retryAfter ? Number(retryAfter) * 1_000 : 0;
const exponentialMs = 250 * 2 ** attempt;
await sleep(Number.isFinite(retryAfterMs) && retryAfterMs > 0
? retryAfterMs
: exponentialMs);
continue;
}
if (!response.ok) {
const body = await response.text();
throw new Error(`Metrics query failed (${response.status}): ${body}`);
}
return response.json();
}
throw new Error("Metrics query remained rate-limited after 4 attempts");
}
const metrics = await queryMetrics();
console.log(JSON.stringify(metrics, null, 2));
I use 429 as a design check here — not as an exotic edge case. A refresh button, several open admin tabs, and an automatic polling interval can converge on the same second. The adapter should coalesce identical requests or cache a recent normalized result when freshness requirements allow it. Your mileage may vary on the right interval; product traffic and the operational value of a fresh point should decide it.
Notice what the example does not do. It doesn't guess a from, to, groupBy, or tenant filter. Those parameters are not declared for this query surface, so a copy-paste sample that includes them would be fiction. Discover and validate the actual request schema before adding query controls.
For writes, use the verified POST /v1/metrics/report route and follow the request schema published by discovery. Keep reporting off the synchronous user path where possible. The dashboard then reads the resulting metric view rather than searching logs for every event that might contribute to it.
How the backend options differ
There isn't one universal winner. These tools start from different data models, and choosing by logo misses the expensive part: the shape of the question your UI asks repeatedly.
| Option | Primary fit | What to verify before choosing | Better choice when |
|---|---|---|---|
| Prometheus | Time-series metrics and operational dashboards | Retention, cardinality, and how product events become metrics | Your team already operates a metrics stack and the dashboard questions are stable |
| Grafana Loki | Log storage and search alongside Grafana | Query cost, label design, and repeated aggregation patterns | Investigation detail is the main requirement and charts are secondary |
| OpenSearch | Search and aggregation over indexed documents | Index lifecycle, schema discipline, and tenant isolation | Admin users need flexible event search as much as summary charts |
| PostHog | Product analytics such as event exploration, funnels, and retention | Data governance and whether its product model matches internal admin reporting | The real requirement is behavioral analytics rather than infrastructure telemetry |
| Unified REST provider | Metrics plus adjacent backend capabilities behind one contract | Alerting, tracing, deletion, export, and query-schema boundaries | A small team values fewer integrations and accepts explicit capability limits |
Infrai is one credible unified REST option when operational simplicity matters: its strongest argument here is one key and one bill across backend services, which avoids credential sprawl and month-end invoice reconciliation. The catch is that the observability surface does not provide alert or notification routes, distributed trace queries or span trees, source-map decoding, crash symbolication, Session Replay, or synthetic heartbeat monitoring. It is not suitable as a complete replacement for a mature observability suite. Stick with Prometheus plus an alerting component when threshold-driven operations are central; choose Loki or OpenSearch when rich log investigation dominates; choose PostHog when funnels and retention are the product requirement.
That is a real trade. A compact integration can be valuable, but breadth does not erase missing workflow layers.
Two objections worth resolving early
The first objection is, "We already have logs, so why store metrics too?" Reuse sounds attractive until every chart becomes a maintained search expression. Dashboard traffic is repetitive, while log investigation is selective. Pre-shaped metrics make those repeated reads direct and keep log retention, sampling, and message changes from silently redefining a business chart. OpenTelemetry's sampling guidance also matters: sampled telemetry can be useful operationally, but a sampled log or trace stream should not become an unexamined source of exact business counts.
The second objection is, "Can metrics still support drill-down?" Yes, if the application preserves correlation deliberately. A chart can identify the time window and series where behavior changed; the investigation view can then use available correlation fields such as trace_id and span_id in logs. This is correlation, not a distributed trace query. If engineers require a rendered span tree, they need a tracing backend built for that job.
There is also a compliance objection, and it can overturn the recommendation. The log surface described here has no per-user deletion API and no bulk export or subscription API. A regulated application that must automate deletion, legal export, or downstream archival should keep those records in a system with the required lifecycle controls. Don't turn an operational log store into the only copy of regulated user history.
Alerting deserves the same clarity. With no threshold-rule, phone, SMS, or webhook notification route, a team would need to poll the query API and own its notification logic. For "did the scheduled task run at all?" checks, pair the stack with a heartbeat monitor such as Healthchecks. Silent absence is different from a bad metric value.
A practical decision rule
Choose metrics first when the admin surface consists mainly of stable cards and trends, refreshes repeatedly, and can tolerate a known aggregation delay. Add logs for investigation. This pattern fits signups, completed jobs, latency summaries, and revenue-event counts.
Choose log search first only when the primary user action is finding and reading individual events, with charts acting as navigation rather than the product. Choose a product analytics platform when nontechnical users need funnels, paths, cohorts, or retention analysis. Choose a durable event store when exact replay, correction, or auditability defines correctness.
One final test helps: write the five questions the dashboard must answer. If four begin with "how many", "how often", or "how has this changed", the backend is probably metrics-led. If four begin with "which request", "what happened", or "show me the event", it is probably logs-led. If the questions mix both forms, use both stores and keep their jobs distinct.
Top comments (0)