How to Set Up Error Monitoring & Structured Logging for Small Web Apps

error monitoring structured logging small web apps

When a small web app fails, a useful error report should tell you what the user experienced, which release was running, and where to look next. A pile of unstructured logs can contain more data without answering any of those questions.

You do not need an enterprise observability platform to get started. Give your logs a consistent shape, connect related events with a request or trace ID, collect a few user-facing health signals, and alert only when someone can take action. This guide lays out a provider-neutral workflow for an individual maintainer or small team.

Start with the failures that matter to users

List the few things your app must do reliably: sign in, load a key page, save a record, accept a payment, or send an important notification. For each, ask:

  • How would a user notice that it is broken?
  • What should you check first to diagnose it?
  • Who will receive an alert, and what can that person do?

This keeps monitoring focused on the service instead of turning every exception into an urgent notification. A failed payment callback may need immediate attention; a typo in a noncritical page may not.

Give log events a stable structure

OpenTelemetry's logs overview describes a log as a timestamped record and explains why a stable schema makes records easier to correlate and analyze. JSON is a convenient encoding, but JSON alone is not a schema: agree on field names, types, and meanings so that application code and dashboards use them consistently.

For example, a completed request might produce a record like this:

{
  "timestamp": "2026-11-07T04:01:00.000Z",
  "severity_text": "ERROR",
  "service.name": "shop-api",
  "deployment.environment.name": "production",
  "service.version": "2026.11.07.1",
  "event.name": "http.server.request",
  "http.request.method": "POST",
  "http.route": "/api/orders",
  "http.response.status_code": 503,
  "duration_ms": 842,
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "error.type": "upstream_timeout"
}

The values are illustrative, not a required schema. Keep a short shared list of fields such as:

  • When and where: timestamp, service name, environment, and deployed version.
  • What happened: event name, severity, outcome, and a concise error type.
  • Request context: method, route template, response status, and duration.
  • Correlation: a request ID or trace ID that links related records.

Use route templates such as /api/orders/{orderId} rather than logging each raw URL. A stable route helps group traffic without turning every resource identifier into a separate metric or log category. The OpenTelemetry HTTP semantic conventions provide standard names for common request attributes.

Connect the events for one request

An exception by itself may not show whether a database call, payment provider, or application change caused the failure. Add a request ID to events generated while handling a request. If your app uses distributed tracing, include the trace and span identifiers so logs and spans can be opened together. OpenTelemetry's trace guide explains how a trace ID links work across spans and services.

Create or validate the identifier at a trusted boundary. If you accept a caller-provided correlation header, treat it as untrusted input: validate its size and format, and do not let arbitrary text break your log format. Do not copy every incoming header into logs.

For a small monolith, a request ID may be enough to find all relevant events. Add full traces when you need to understand work that crosses services or external calls. You can adopt them incrementally; do not add tracing complexity just to produce more telemetry.

Use logs, metrics, and health checks for different questions

Logs explain individual events. Metrics summarize behavior over time. Traces help follow one request through its work. OpenTelemetry describes these as separate signals that can be correlated; they do not have to come from separate vendors.

Start with metrics for:

  • Traffic: how many requests or jobs are arriving.
  • Errors: how many fail, including failures that matter to your app even if the HTTP status is successful.
  • Latency: how long successful and failed requests take.
  • Saturation: whether a constrained resource such as a database connection pool or queue is close to its limit.

These are the four signals in Google's SRE monitoring guidance. The OpenTelemetry metrics overview explains how measurements are aggregated and warns that high-cardinality attributes, such as user IDs or raw URL paths, can drive memory use. Keep metric labels to bounded values such as route templates, method, and status class.

Choose alert conditions around user impact and the response you can take. A sustained rise in failed requests on a critical route or a health check that cannot complete a key operation can justify an alert. An isolated exception, expected validation error, or every 404 usually does not. HTTP status alone is not always enough: OpenTelemetry's HTTP conventions note that a server-side 4xx response is not automatically an error, since an application may expect that response in context.

Pair application metrics with a simple external check of a user-facing path. A process can be running while a database or essential route is unavailable. Keep health endpoints minimal: report whether the app can serve the intended check, not internal configuration or sensitive dependency details.

Redact sensitive data before it is collected

Logs often travel to a service outside your application host and may be retained or viewed by more people than production data. Follow the OWASP Logging Cheat Sheet: decide what each event is for, validate untrusted fields, and avoid recording secrets or sensitive personal information.

Do not log passwords, access tokens, session IDs, connection strings, encryption keys, payment details, or full request and response bodies by default. Query strings and headers can contain credentials or personal data too. Prefer an allowlist of fields over capturing an entire request object. If a user identifier is needed to investigate an issue, use the minimum necessary representation and access controls.

Test redaction with realistic fixtures before enabling production collection. Also test what happens when the log destination is unavailable or full. Logging should not unexpectedly break ordinary requests, and an unbounded retry loop should not turn a monitoring outage into an application outage. Security audit events may have different durability requirements from diagnostic logs, so decide how those events are protected and retained separately.

Match source maps to the deployed release

For a browser application, minified JavaScript stack traces can be difficult to act on. Source maps let an error-monitoring tool map generated code back to the original source. For example, Sentry's JavaScript source-map guide recommends uploading maps as part of the production build and notes that artifacts need to be uploaded before errors arrive.

Include a release or build identifier in your error events and upload the matching maps as part of deployment. Check that the maps correspond to the code actually deployed. Keep map files private unless you intend to publish them; do not expose source content through a public web directory by accident.

Keep collection and retention right-sized

For one application, structured logs written to standard output and collected by the hosting platform may be a reasonable start. OpenTelemetry says direct export can work for development and small-scale setups; a Collector becomes useful when you want a separate place to batch, filter, retry, or route telemetry to one or more backends.

Before enabling verbose logging or collecting every trace, check the storage and ingestion limits of your host or monitoring service. Set a retention period that is long enough for your operational needs but no longer than necessary. There is no universal number: security audit records, diagnostic logs, and traces can have different purposes, privacy risks, and legal requirements. Restrict access, document who can view production data, and verify that deletion and retention settings apply to exports and backups as well.

Use the same short loop during an incident

When an alert arrives:

  1. Confirm the affected user-facing route or workflow and the time it started.
  2. Compare the error, latency, and traffic signals; identify the deployed version.
  3. Follow a request ID or trace ID to the relevant logs and dependency calls.
  4. Mitigate through your normal rollback, feature-flag, or recovery process.
  5. After service recovers, record the cause, user impact, and one change that could make the next diagnosis or recovery faster.

If calls to an external API are part of the failure, review the limits and retry behavior in our guide to rate limiting an API for a small service. Retries can hide a failing dependency or amplify load, so track the outcome and duration rather than logging only the final exception.

A practical rollout for a small app

  1. Add one structured request-completion event with a stable schema and a request or trace ID.
  2. Add metrics for request volume, failed requests, latency, and the resource most likely to constrain your app.
  3. Remove secrets and unnecessary personal data before logs leave the application.
  4. Create one actionable alert and one external check for a critical user path.
  5. Attach the deployed release identifier to errors, then add matching source-map upload if you ship minified browser code.
  6. Review a real incident or a controlled test failure; delete fields and alerts that do not help you respond.

The goal is not to collect everything. It is to make the next real failure easier to detect, connect to its cause, and resolve without exposing more user data than necessary.

Official sources

Leave a Reply