Error Logging: Capturing and Contextualizing Application Errors
In this tutorial, you will learn about Error Logging: Capturing and Contextualizing Application Errors. We cover key concepts, practical examples, and best practices to help you master this topic.
Error logging captures details about application failures: the error type, message, stack trace, request context, and system state at the time of failure. Well-structured error logs enable rapid diagnosis and reduce mean time to resolution (MTTR).
flowchart TB
Error[Error Occurs] --> Catch[Error Handler]
Catch --> Format{Format Error}
Format --> Context[Add Request Context]
Context --> Stack[Capture Stack Trace]
Stack --> Tags[Add Tags: service, version, env]
Tags --> Severity[Set Severity Level]
Severity --> Log[Write to Error Log]
Log --> Alert{Auto-Alert?}
Alert -->|Critical| Pager[PagerDuty]
Alert -->|Error| Dashboard[Error Dashboard]
Log --> Sentry[Sentry/Rollbar]
What You'll Learn
- Structured error logging with context
- Express error handling middleware
- Error categorization and severity
- Error aggregation with Sentry
Why It Matters
"Something went wrong" is the least useful error message in history. A good error log tells you exactly what failed, where, when, for which user, and with what request context. This turns hours of debugging into minutes.
Real-World Use
A SaaS platform uses Sentry for error tracking. Each error includes the user ID, request parameters, service version, and environment. When a new error appears, Sentry alerts the team. They see the exact request that caused the error and the surrounding log context.
Error Logging Implementation
Structured Error Handler
class AppError extends Error {
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR', details = {}) {
super(message);
this.name = 'AppError';
this.statusCode = statusCode;
this.code = code;
this.details = details;
this.timestamp = new Date().toISOString();
Error.captureStackTrace(this, this.constructor);
}
}
// Express error handling middleware
function errorHandler(err, req, res, next) {
const statusCode = err.statusCode || 500;
const isOperational = err instanceof AppError;
const errorLog = {
error: {
name: err.name,
message: err.message,
code: err.code || 'UNKNOWN',
statusCode,
stack: err.stack,
isOperational
},
request: {
correlationId: req.correlationId,
method: req.method,
url: req.originalUrl,
ip: req.ip,
userAgent: req.headers['user-agent'],
userId: req.user?.id || 'anonymous'
},
service: {
name: process.env.SERVICE_NAME,
version: process.env.APP_VERSION,
environment: process.env.NODE_ENV,
host: require('os').hostname()
}
};
// Log the full error
logger.error(errorLog, err.message);
// Sentry capture (if configured)
if (process.env.SENTRY_DSN) {
Sentry.captureException(err, {
user: { id: req.user?.id },
tags: { service: process.env.SERVICE_NAME },
extra: { url: req.originalUrl, method: req.method }
});
}
// Send safe response to client
res.status(statusCode).json({
error: isOperational ? err.message : 'An unexpected error occurred',
code: isOperational ? err.code : 'INTERNAL_ERROR',
correlationId: req.correlationId
});
}
app.use(errorHandler);
Expected output:
Error log: {"error":{"name":"AppError","message":"User not found","code":"USER_NOT_FOUND","stack":"..."},"request":{"correlationId":"abc","method":"GET","url":"/api/users/123"}}
Response: {"error":"User not found","code":"USER_NOT_FOUND","correlationId":"abc"}
Error Categorization
const ERROR_CATEGORIES = {
VALIDATION: {
severity: 'warn',
httpStatus: 400,
alert: false
},
AUTHENTICATION: {
severity: 'warn',
httpStatus: 401,
alert: false
},
AUTHORIZATION: {
severity: 'warn',
httpStatus: 403,
alert: true
},
NOT_FOUND: {
severity: 'info',
httpStatus: 404,
alert: false
},
RATE_LIMIT: {
severity: 'warn',
httpStatus: 429,
alert: false
},
INTEGRATION: {
severity: 'error',
httpStatus: 502,
alert: true
},
DATABASE: {
severity: 'error',
httpStatus: 500,
alert: true
},
INTERNAL: {
severity: 'error',
httpStatus: 500,
alert: true
}
};
function categorizeError(err) {
if (err.code && ERROR_CATEGORIES[err.code]) {
return ERROR_CATEGORIES[err.code];
}
if (err.name === 'ValidationError') return ERROR_CATEGORIES.VALIDATION;
if (err.statusCode === 401) return ERROR_CATEGORIES.AUTHENTICATION;
if (err.statusCode === 403) return ERROR_CATEGORIES.AUTHORIZATION;
if (err.statusCode === 404) return ERROR_CATEGORIES.NOT_FOUND;
if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') return ERROR_CATEGORIES.INTEGRATION;
return ERROR_CATEGORIES.INTERNAL;
}
Expected output:
Validation errors: WARN, no alert. Database errors: ERROR, triggers PagerDuty. Rate limit errors: WARN, no alert.
Error Aggregation and Deduplication
class ErrorAggregator {
constructor() {
this.errors = new Map();
this.windowMs = 3600000; // 1 hour window
}
addError(error) {
const key = `${error.name}:${error.code || 'UNKNOWN'}:${error.statusCode}`;
const now = Date.now();
if (!this.errors.has(key)) {
this.errors.set(key, {
count: 0,
firstSeen: now,
lastSeen: now,
error
});
}
const entry = this.errors.get(key);
entry.count++;
entry.lastSeen = now;
// Clean old entries
for (const [k, v] of this.errors) {
if (now - v.lastSeen > this.windowMs) {
this.errors.delete(k);
}
}
return entry;
}
getSummary() {
return Array.from(this.errors.entries())
.map(([key, entry]) => ({
key,
count: entry.count,
firstSeen: new Date(entry.firstSeen).toISOString(),
lastSeen: new Date(entry.lastSeen).toISOString(),
message: entry.error.message
}))
.sort((a, b) => b.count - a.count);
}
}
Expected output:
Error summary: [
{ key: "AppError:USER_NOT_FOUND:404", count: 150, message: "User not found" },
{ key: "AppError:INSUFFICIENT_FUNDS:400", count: 45, message: "Insufficient funds" }
]
Common Mistakes
- Logging errors without stack traces — the stack trace is essential for identifying the exact failure point.
- Swallowing errors (catching and not logging) — errors that are caught and ignored hide real problems.
- Exposing internal error details to the client — never expose stack traces, SQL queries, or internal paths in API responses.
- Not categorizing errors — all errors going to the same severity makes prioritization impossible.
- Logging the same error multiple times — use error aggregation to group duplicate errors.
Practice Questions
- What information should an error log include?
- Why should you not expose stack traces to clients?
- What is the difference between operational and programmer errors?
- How does error aggregation improve error monitoring?
- When should an error trigger an automated alert?
Challenge
Build an error handling system for a payment API. Implement: (1) custom AppError class with code and status, (2) error categorization (validation, payment, integration, internal), (3) error logging with full context, (4) error aggregation dashboard, (5) Sentry integration for critical errors.
FAQ
Mini Project
Build a comprehensive error logging system. Implement: (1) AppError class with categorization, (2) Express error handler with structured logging, (3) error aggregation with deduplication, (4) Sentry integration, (5) Process-level handlers for uncaught exceptions and unhandled rejections.
What's Next
Continue to Request Logging for HTTP request/response logging patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro