When a Node.js application is small, debugging is relatively simple.
You see an error in the terminal, identify the problematic file, fix the code, and restart the application.
But as your application grows, things become much more complicated.
You may have:
- Multiple servers
- Background workers
- Scheduled jobs
- REST APIs
- Database operations
- Third-party APIs
- Authentication services
- Payment integrations
- Production deployments
At that point, checking terminal logs manually is no longer enough.
This is where automated logging and error monitoring become essential.
A good monitoring system should help you answer questions such as:
- What went wrong?
- When did it happen?
- Which API caused it?
- Which user or request was affected?
- How frequently is the error occurring?
- Is the problem happening on one server or all servers?
- Did the latest deployment introduce the issue?
In this article, we’ll explore how to build a reliable logging and error-monitoring system for Node.js applications.
Why Logging Matters
Logs provide visibility into what your application is doing.
For example:
POST /api/login
User authenticated
Database query completed
Response sent
When something fails:
POST /api/orders
Database connection failed
Request failed
Without logs, debugging production issues becomes guesswork.
Instead of asking:
“Why is this API failing?”
you can inspect the logs and determine:
Request
↓
Controller
↓
Service
↓
Database
↓
Error
Good logs turn debugging from guessing into investigation.
Console.log Is Not Enough
Many Node.js developers start with:
console.log("User created");
console.log(user);
And:
console.log("Error:", error);
This is fine during development.
But production applications need structured logging.
Instead of:
User created
you want something like:
{
"level": "info",
"event": "user_created",
"userId": "12345",
"timestamp": "2026-09-15T10:30:00Z"
}
Structured logs are much easier to search, filter, analyze, and send to monitoring systems.
What Is Structured Logging?
Structured logging means storing log information in a predictable format, usually JSON.
For example:
logger.info({
event: "order_created",
orderId: order._id,
userId: user._id
});
The output can look like:
{
"level": "info",
"event": "order_created",
"orderId": "68a123",
"userId": "67b456",
"timestamp": "2026-09-15T10:30:12.123Z"
}
Now your monitoring system can easily search:
event = "order_created"
or:
userId = "67b456"
This is much more useful than searching through thousands of plain-text console.log() statements.
Popular Logging Libraries for Node.js
Instead of building your own logger, you can use an established logging library.
Popular options include:
- Pino
- Winston
- Bunyan
For high-performance Node.js applications, Pino is a popular choice.
Let’s see how it can be used.
Installing Pino
Install Pino:
npm install pino
Create a logger:
import pino from "pino";
export const logger = pino({
level: "info"
});
Now you can write:
logger.info("Server started");
Or structured information:
logger.info({
userId: user._id,
event: "user_login"
});
Log Levels
A good logging system should have different log levels.
Common levels include:
trace
debug
info
warn
error
fatal
For example:
Debug
Useful during development:
logger.debug({
query,
event: "database_query"
});
Info
Normal application activity:
logger.info({
event: "server_started",
port: 3000
});
Warn
Something unexpected happened but the application can continue:
logger.warn({
event: "slow_database_query",
duration: 2500
});
Error
An operation failed:
logger.error({
event: "payment_failed",
error
});
Fatal
A critical error prevents the application from continuing.
Centralized Error Handling
One of the most important patterns in Express applications is centralized error handling.
Instead of handling every error differently:
app.use((err, req, res, next) => {
logger.error(err);
res.status(500).json({
status: 0,
message: "Internal server error"
});
});
Now unexpected errors can be captured in one place.
A more useful implementation can include request information:
app.use((err, req, res, next) => {
logger.error({
event: "request_error",
method: req.method,
url: req.originalUrl,
error: err.message,
stack: err.stack
});
res.status(500).json({
status: 0,
message: "Internal server error"
});
});
This immediately tells you:
- HTTP method
- Endpoint
- Error message
- Stack trace
- When the error happened
Request Logging Middleware
You should also automatically log incoming requests.
For example:
app.use((req, res, next) => {
const start = Date.now();
res.on("finish", () => {
const duration = Date.now() - start;
logger.info({
event: "http_request",
method: req.method,
url: req.originalUrl,
statusCode: res.statusCode,
duration
});
});
next();
});
Now every request can produce a log like:
{
"event": "http_request",
"method": "GET",
"url": "/api/users",
"statusCode": 200,
"duration": 142
}
This becomes extremely useful when investigating slow APIs.
Add a Request ID
One of the best improvements you can make is adding a unique request ID.
Consider a request:
Client
↓
API
↓
Service
↓
Database
↓
External API
If something fails, you want to connect all logs belonging to that request.
For example:
requestId = 8f92ab31
Then:
{
"requestId": "8f92ab31",
"event": "request_received"
}
{
"requestId": "8f92ab31",
"event": "database_query"
}
{
"requestId": "8f92ab31",
"event": "external_api_failed"
}
Now you can search for:
requestId = 8f92ab31
and see the complete journey of the request.
Why Error Monitoring Is Different From Logging
Logging and error monitoring are related, but they aren’t exactly the same.
Logging
Answers:
What is my application doing?
Error monitoring
Answers:
What is failing, how often, and how serious is it?
For example, your logs might contain:
Database timeout
Database timeout
Database timeout
Database timeout
An error-monitoring platform can aggregate those events and show:
DatabaseTimeoutError
Occurrences: 1,248
Affected users: 317
First seen: 10:12 AM
Last seen: 10:45 AM
This makes production issues much easier to prioritize.
Automated Error Monitoring
You can connect your Node.js application to an error-monitoring service.
Popular platforms include:
- Sentry
- Datadog
- New Relic
- Elastic Observability
- Grafana
- OpenTelemetry-compatible systems
The exact choice depends on your infrastructure and requirements.
The important concept is:
Application
↓
Error detected
↓
Monitoring SDK
↓
Monitoring platform
↓
Alert / Dashboard
Capturing Exceptions
Consider this code:
try {
await processPayment();
} catch (error) {
logger.error(error);
throw error;
}
With an error-monitoring SDK, the error can also be reported:
try {
await processPayment();
} catch (error) {
logger.error({
event: "payment_processing_failed",
error
});
monitoring.captureException(error);
throw error;
}
Now developers can inspect the error from a centralized dashboard.
Monitoring Unhandled Errors
Node.js applications can also encounter errors that weren’t explicitly handled.
For example:
process.on("uncaughtException", (error) => {
logger.fatal({
event: "uncaught_exception",
error
});
});
And:
process.on("unhandledRejection", (reason) => {
logger.fatal({
event: "unhandled_rejection",
reason
});
});
However, these handlers should not be treated as a way to safely continue running after every fatal error.
For serious process-level failures, a safer production strategy is often:
Fatal Error
↓
Log error
↓
Report error
↓
Gracefully shut down
↓
Process manager/container restarts application
Tools such as PM2, Docker, Kubernetes, or cloud orchestration platforms can help restart failed processes.
Monitoring API Performance
Error monitoring isn’t only about exceptions.
You should also monitor performance.
For every request, track:
Request
Response status
Response time
Database time
External API time
For example:
{
"endpoint": "/api/orders",
"method": "POST",
"statusCode": 201,
"duration": 850,
"databaseDuration": 420,
"externalApiDuration": 300
}
Now you can identify bottlenecks.
If the endpoint takes 850ms:
Database 420ms
External API 300ms
Node.js 130ms
You immediately know where to investigate.
Slow Query Detection
Database performance is often one of the biggest sources of backend problems.
You can automatically log slow queries.
For example:
if (duration > 1000) {
logger.warn({
event: "slow_database_query",
duration,
collection: "orders"
});
}
This creates a useful signal:
Slow query detected
Collection: orders
Duration: 2,431ms
You can then investigate:
- Missing indexes
- Large collections
- Expensive aggregations
- Poor query patterns
- Excessive database calls
Background Job Monitoring
Modern Node.js applications often use background jobs.
For example:
API
↓
Queue
↓
Worker
↓
AI Processing
↓
Database
The worker should have its own logging.
logger.info({
event: "job_started",
jobId: job.id,
type: job.name
});
On success:
logger.info({
event: "job_completed",
jobId: job.id,
duration
});
On failure:
logger.error({
event: "job_failed",
jobId: job.id,
error
});
This makes background systems much easier to debug.
Don’t Log Sensitive Information
One of the most important rules of production logging is:
Never log sensitive data unnecessarily.
Avoid logging:
Passwords
Authentication tokens
API keys
Credit card numbers
Private user data
Session secrets
For example, don’t do:
logger.info({
user
});
if user contains sensitive information.
Instead:
logger.info({
userId: user._id,
event: "user_login"
});
Log only what you actually need.
Log Rotation
If you store logs locally, log files can grow quickly.
For example:
app.log
app.log
app.log
app.log
...
Eventually the server disk may become full.
Log rotation solves this problem.
A typical strategy might be:
app-2026-09-15.log
app-2026-09-14.log
app-2026-09-13.log
You can also configure:
- Maximum file size
- Maximum retention period
- Compression
- Automatic deletion
In larger systems, logs are commonly shipped to centralized storage instead of relying on local files.
Centralized Logging
When you have multiple servers:
Server 1
Server 2
Server 3
Server 4
Searching logs individually becomes difficult.
Instead:
Server 1 ─┐
Server 2 ─┤
Server 3 ─┼──→ Central Logging System
Server 4 ─┘
Common technologies include:
- Elasticsearch
- OpenSearch
- Grafana Loki
- Cloud logging services
- Datadog
- Splunk
Now developers have one place to search application logs.
Metrics, Logs, and Traces
Modern observability usually consists of three major signals.
Logs
Tell you:
What happened?
Example:
Payment API failed
Metrics
Tell you:
How much / how often?
Example:
HTTP 500 errors: 3.2%
Average response time: 420ms
CPU: 72%
Traces
Tell you:
Where did the request spend its time?
Example:
API
↓ 20ms
Node.js
↓ 80ms
MongoDB
↓ 300ms
Payment API
Together:
Observability
/ | \
Logs Metrics Traces
gives you a much clearer picture of your application.
OpenTelemetry
For applications that need vendor-neutral observability, OpenTelemetry is an important technology to understand.
It provides standardized instrumentation for collecting:
- Traces
- Metrics
- Logs
The general architecture is:
Node.js Application
↓
OpenTelemetry
↓
Telemetry Collector
↓
Monitoring Backend
The backend could be a variety of observability platforms.
This approach reduces dependency on a single monitoring vendor.
Automated Alerts
Monitoring becomes truly useful when it can notify your team automatically.
For example:
Error rate > 5%
↓
Trigger Alert
↓
Slack / Email / Pager
↓
Developer investigates
Other useful alert conditions include:
CPU > 90%
Memory > 85%
API latency > 2 seconds
Database failures increasing
Queue backlog increasing
HTTP 500 rate increasing
Worker repeatedly failing
The goal isn’t to create hundreds of alerts.
The goal is to create actionable alerts.
Avoid Alert Fatigue
If your team receives:
100 alerts/day
developers may start ignoring them.
Instead, focus on alerts that require action.
Bad alert:
One request took 1.2 seconds
Better alert:
95th percentile API latency exceeded 2 seconds
for 10 consecutive minutes.
Good monitoring should reduce noise rather than create more noise.
A Practical Node.js Architecture
A production-ready architecture might look like:
Client
│
▼
┌─────────────┐
│ Node.js API │
└──────┬──────┘
│
┌─────────┴─────────┐
▼ ▼
Application Database
Logs Logs
│
▼
Logging Library
│
▼
Observability Layer
│
┌─────┼──────┐
▼ ▼ ▼
Logs Metrics Traces
│ │ │
└─────┼──────┘
▼
Monitoring Platform
│
▼
Alerts / Dashboard
Recommended Project Structure
For a TypeScript Node.js backend:
src/
│
├── controllers/
├── routes/
├── services/
├── models/
├── middleware/
│ ├── requestLogger.ts
│ └── errorHandler.ts
│
├── logger/
│ ├── logger.ts
│ └── serializers.ts
│
├── monitoring/
│ ├── metrics.ts
│ ├── tracing.ts
│ └── errors.ts
│
├── queues/
│
├── utils/
│
└── app.ts
This keeps monitoring concerns separate from business logic.
Production Logging Checklist
Before deploying your Node.js application, make sure you have:
Logging
- Structured JSON logs
- Log levels
- Request IDs
- Request/response logging
- Error logging
- Database error logging
- Background job logging
Error Monitoring
- Unhandled exception monitoring
- Unhandled rejection monitoring
- Error aggregation
- Stack traces
- Deployment tracking
Performance
- API response times
- Database query times
- External API latency
- Queue processing time
- CPU and memory monitoring
Security
- No passwords in logs
- No API keys in logs
- No authentication tokens
- No unnecessary personal data
- Secure log storage
Alerts
- High error rate
- High latency
- Database failures
- Worker failures
- Infrastructure problems
From Logs to Automated Monitoring
The progression usually looks like this:
Level 1
console.log()
↓
Level 2
Structured Logging
↓
Level 3
Centralized Logs
↓
Level 4
Error Monitoring
↓
Level 5
Metrics + Tracing
↓
Level 6
Automated Alerts
↓
Level 7
Automated Recovery
You don’t need to implement everything on day one.
Start with structured logging and centralized error handling, then gradually add metrics, tracing, alerts, and automated recovery.
Final Thoughts
A production Node.js application isn’t complete simply because its APIs work.
You also need to know what happens after deployment.
When users report:
“The application is not working.”
Your monitoring system should help you answer:
What happened?
↓
Which endpoint?
↓
Which request?
↓
Which service?
↓
Which database query?
↓
Which error?
↓
How many users affected?
↓
Did it start after a deployment?
That’s the real purpose of observability.
The goal isn’t to collect thousands of logs.
The goal is to build a system where important application events, errors, performance problems, and failures can be detected automatically and investigated quickly.
For Node.js developers, a strong production monitoring stack can be built incrementally:
Node.js
+
Structured Logging
+
Error Monitoring
+
Metrics
+
Tracing
+
Alerts
Once these pieces are in place, debugging production applications becomes significantly faster, safer, and more predictable.




