A five-person product team does not need a wall of dashboards. It does need a way to answer a user report without opening six tabs and guessing which deploy changed behavior. The useful question is smaller than “do we have observability?” It is: can we connect a failed user action to a request, a deploy, and the system that actually broke?
Here is the setup I would choose for a small content or SaaS product before buying anything elaborate. It is intentionally plain: a health check, deploy markers, structured logs, a short list of critical flows, and a weekly review of the misses.
Pick four flows, not forty metrics
Start with the actions that make the product real. For a content site, that might be:
- Build and deploy the public site.
- Publish or edit an article.
- Generate search/RSS/sitemap output.
- Deliver a contact or correction message.
For a SaaS product, the first set might be signup, login, billing, data import, and one background job. The point is to name flows in user language, not infrastructure language. “Postgres CPU” matters later. “User cannot publish an article” matters first.
For each flow, track one success counter, one failure counter, and one latency number. A small JSON log is enough:
{
"event": "content_publish_finished",
"status": "success",
"article_id": "rsc-boundary-notes",
"duration_ms": 1840,
"deploy_sha": "4ccb94d",
"request_id": "req_8v2n"
}
If the team cannot name the event, it usually cannot debug the failure either.
Put request IDs where support can see them
The most expensive support thread starts with “it did not work” and no identifier. Add a request ID to every API response and error page. For user-facing severe errors, show a small reference line such as Reference: req_8v2n. It does not need to be pretty; it needs to be searchable.
The same ID has to appear in the API log, the worker log, and any downstream call you control. If a publish action queues a background job, include both IDs:
{
"request_id": "req_8v2n",
"job_id": "job_publish_742",
"account_id": "acct_19",
"event": "search_index_enqueue"
}
Without that link, the team ends up matching timestamps by hand.
Add deploy markers before the first incident
A surprising number of production questions are really deploy questions. Did the error rate begin after the last release? Did only one route change? Did the queue retry count rise when a feature flag moved from 10 percent to 100 percent?
Every log line does not need the full Git history. It does need the current commit SHA or deployment version. Dashboards also need deploy markers. If the graph changes immediately after 4ccb94d, responders have a starting point.
This is also where release notes help. A note that says “changed sitemap generation and excluded search pages” is useful during a crawlability issue. A note that says “misc cleanup” is not.
Alert on symptoms someone will act on
Small teams cannot afford noisy alerts. I would start with five:
| Alert | First check |
|---|---|
| Site unavailable for 3 minutes | hosting status and latest deploy |
| Public build failed twice | build log and content file changed |
| Contact form failures above threshold | email provider and spam filter |
| Background job retries rising | downstream API and queue delay |
| Billing or signup failures rising | provider dashboard and recent release |
Do not alert on every warning log. Put lower urgency signals on a dashboard or weekly review. Alerts are for waking someone up or interrupting work; they need a clear first action.
Review the misses every Friday
Once a week, pick one support thread or bug report and ask what would have made it easier to debug. Maybe the logs missed the account ID. Maybe the frontend swallowed the API error code. Maybe the dashboard showed total errors but not the route. Add one signal, not a whole new platform.
The stack can stay boring for a long time if the signals are connected. A request ID, deploy SHA, flow-level event name, and a handful of alerts will answer more real questions than a dozen charts nobody trusts.