# 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.

Source: https://docs.alphorn.dev/use-cases/error-tracking/

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

```javascript
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

```python
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}")
    raise
```

## Frontend 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:

```javascript title="browser"
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}`,
    }),
  });
});
```

```javascript title="server (Express)"
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 |
| Email | Tags has any of `error` | Full error archive |
