Application Error Tracking
Route application errors and exceptions to the right team in real time, with examples for Express.js, Python, and frontend JavaScript errors.
Application errors need immediate attention - but not all errors are equal. Route critical errors to PagerDuty while sending warnings to Slack, without configuring each service individually.
Express.js error handler
app.use((err, req, res, next) => {
const status = err.status ?? 500;
const priority = status >= 500 ? 5 : 3;
fetch("https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.ALPHORN_API_KEY}`,
},
body: JSON.stringify({
title: `${err.name}: ${err.message}`,
message: `${req.method} ${req.path}\n\nStack:\n${err.stack?.slice(0, 500)}`,
priority,
tags: ["error", `http-${status}`, "backend"],
}),
}).catch(() => {}); // Don't block the error response
// 4xx errors are the client's fault, so tell them what went wrong; hide 5xx details
res.status(status).json({ error: status < 500 ? err.message : "Internal server error" });
});Python exception handler
import os
import traceback
import requests
def notify_error(exc, context=""):
try:
requests.post(
"https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT",
headers={"Authorization": "Bearer " + os.environ["ALPHORN_API_KEY"]},
json={
"title": f"{type(exc).__name__}: {str(exc)[:100]}",
# the end of the traceback is the most useful part
"message": f"{context}\n\n{traceback.format_exc()[-1500:]}",
"priority": 5,
"tags": ["error", "exception", "backend"],
},
timeout=5,
).raise_for_status()
except requests.RequestException:
pass # never let a failed notification hide the original error
# Usage
try:
process_payment(order)
except Exception as e:
notify_error(e, context=f"Processing order {order.id}")
raiseFrontend errors
Alphorn doesn't send CORS headers, so a browser on your site can't POST to it directly. Send frontend errors to your own backend instead, and let the backend forward them to Alphorn. That also keeps the API key on the server:
window.addEventListener("error", (event) => {
fetch("/client-errors", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: event.message,
source: `${event.filename}:${event.lineno}`,
}),
});
});app.post("/client-errors", express.json(), (req, res) => {
const { message, source } = req.body;
fetch("https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.ALPHORN_API_KEY}`,
},
body: JSON.stringify({
title: `Frontend error: ${message}`,
message: `${source}\n\nUser-Agent: ${req.get("user-agent")}`,
priority: 3,
tags: ["error", "frontend"],
}),
}).catch(() => {});
res.sendStatus(204);
});This endpoint is public, so rate-limit it before you route its messages anywhere noisy.
Routing examples
| Channel | Filter | Purpose |
|---|---|---|
| Slack (#errors) | Tags has any of error | All errors visible to the team |
| PagerDuty | Priority greater than 4 and tags has any of error | Page on-call for 500s |
| Slack (#frontend) | Tags has any of frontend | Frontend-specific errors |
Tags has any of error | Full error archive |
Deployment Notifications
Send deployment notifications from Docker Compose, Kubernetes, Vercel, and Netlify when deploys start, succeed, or fail, routed to the right team.
Backup Monitoring
Get notified when backups succeed or fail. Works with pg_dump, mysqldump, restic, borgbackup, and any other backup tool.