Analyzing Webserver Errors
An error count shows that something broke. Structured context—route, status code, release, and request ID—helps you identify what broke and correlate it with a deployment.
The most useful error event preserves the dimensions you will need during an incident.
In modern web development, monitoring and logging are critical aspects of maintaining robust and reliable applications. Telemetry allows developers to track various events and system metrics, which can be crucial for debugging and improving user experience. In this post, we’ll explore how to use the Telemetry API to catch and log all errors in an Express.js web server. Although this guide only covers Express, the ideas are applicable to all web servers, from Rails to Elixir, etc.
Set up Telemetry
Before we can log any data, we need to set up Telemetry. For this example, we'll use the Telemetry JavaScript SDK.
First, install the Telemetry SDK in your project:
npm install telemetry-sh
Then, initialize the Telemetry client in your application with your API key:
import telemetry from "telemetry-sh";
telemetry.init("YOUR_API_KEY");
Create an Express application
Next, let's set up a basic Express application. If you don't already have Express installed, you can add it to your project with the following command:
npm install express
Now, create a simple Express server:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
Add structured error middleware
The error handler belongs after all routes. Capture safe categories and operational context rather than raw bodies, credentials, stack traces, or exception messages that may contain private values.
const express = require("express");
const telemetry = require("telemetry-sh");
telemetry.init(process.env.TELEMETRY_API_KEY);
const app = express();
app.get("/api/projects/:id", async (req, res) => {
throw Object.assign(new Error("Synthetic failure"), {
code: "PROJECT_LOOKUP_FAILED",
});
});
app.use(async (err, req, res, next) => {
const statusCode = Number(err.statusCode) || 500;
await telemetry.log("api_request_failed", {
route_template: req.route?.path ?? "unmatched_route",
method: req.method,
status_code: statusCode,
status: "error",
error_type: err.constructor?.name ?? "Error",
error_code: err.code ?? "UNCLASSIFIED_ERROR",
request_id: req.get("x-request-id") ?? "missing",
release: process.env.APP_RELEASE ?? "unknown",
});
res.status(statusCode).json({ error: "Request failed" });
});
app.listen(3000);
Use route templates such as /api/projects/:id, not raw paths containing customer identifiers. Keep the event schema the same for handled 4xx and 5xx responses when those failures matter to the workflow.
Query and visualize errors
Start with failure rate by route and release, then keep a recent-events table for investigation:
SELECT
route_template,
release,
status_code,
error_code,
COUNT(*) AS failures,
MAX(timestamp_utc) AS last_seen_at
FROM api_request_failed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY route_template, release, status_code, error_code
ORDER BY failures DESC, last_seen_at DESC;
For a true error rate, log successful and failed requests to the same table with a shared status or status_code field. A failure-only table can rank errors, but it cannot supply the denominator.
Next steps
Use the API error rate by route recipe for a denominator-safe query, example visualization, dashboard layout, and alert guidance. Pair it with API latency percentiles so an incident view covers both correctness and speed.