Introduction to Backend Logging Patterns
In this tutorial, you will learn about Introduction to Backend Logging Patterns. We cover key concepts, practical examples, and best practices to help you master this topic.
Logging is the practice of recording events from an application for debugging, monitoring, auditing, and analysis. Effective logging provides observability into system behavior, helps diagnose production issues, and enables security auditing and compliance reporting.
flowchart LR
App[Application] --> Logger[Logger Library]
Logger -->|Stdout| Collector[Log Collector - Filebeat/Fluentd]
Logger -->|JSON| Console[Console Output]
Collector --> Aggregator[Log Aggregator - ELK/Loki]
Aggregator --> Storage[(Elasticsearch/Loki)]
Storage --> Dashboard[Grafana/Kibana]
Storage --> Alert[Alerting]
Storage --> Search[Log Search]
What You'll Learn
- Structured vs. unstructured logging
- Log levels (error, warn, info, debug, trace)
- Winston and Pino logger configuration
- Centralized log aggregation with ELK stack
Why It Matters
Without logging, debugging production issues is like operating blind. Good logging reduces mean time to resolution (MTTR) from hours to minutes. Poor logging (or too much logging) creates noise that obscures real problems.
Real-World Use
A microservice platform uses structured JSON logging with correlation IDs. When an error occurs, the operations team searches by correlation ID to see the complete request trace across all services. They identify the failing service in under 30 seconds.
Logging Implementation
Structured Logging with Winston
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 52428800,
maxFiles: 5
}),
new winston.transports.File({
filename: 'logs/combined.log',
maxsize: 52428800,
maxFiles: 5
})
]
});
Expected output:
{"level":"info","message":"Server started","service":"user-service","timestamp":"2026-06-28T10:00:00.000Z"}
Logging Middleware with Correlation ID
const { v4: uuidv4 } = require('uuid');
function loggingMiddleware(req, res, next) {
const correlationId = req.headers['x-correlation-id'] || uuidv4();
req.correlationId = correlationId;
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('request completed', {
correlationId,
method: req.method,
url: req.originalUrl,
status: res.statusCode,
duration: `${duration}ms`,
userAgent: req.headers['user-agent'],
ip: req.ip
});
});
next();
}
Expected output:
{"level":"info","message":"request completed","correlationId":"abc-123","method":"GET","url":"/api/users","status":200,"duration":"45ms"}
Pino Logger (High Performance)
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true, translateTime: true }
},
redact: ['req.headers.authorization', 'req.body.password', 'req.body.ssn'],
serializers: {
req: pino.stdSerializers.req,
res: pino.stdSerializers.res,
err: pino.stdSerializers.err
}
});
app.use(require('pino-http')({ logger }));
// Usage
app.get('/api/users', async (req, res) => {
logger.info({ userId: req.user.id }, 'Fetching user profile');
const user = await db.getUser(req.params.id);
res.json(user);
});
Expected output:
[10:00:00.000] INFO: Fetching user profile
userId: "123"
Sensitive fields in headers/body are redacted automatically.
Common Mistakes
- Logging sensitive data (passwords, tokens, PII) in plaintext — always redact or mask.
- Using unstructured logs (plain text) instead of structured (JSON) — structured logs are machine-parseable and searchable.
- Logging too much (debug in production) — too many logs create noise and increase costs.
- Logging too little (no error context) — "Something went wrong" without stack trace or context is useless.
- Not including correlation IDs — without correlation, you cannot trace a request across services.
Practice Questions
- What is the difference between structured and unstructured logging?
- What log levels should you use and when?
- Why is a correlation ID important in Distributed Systems?
- How does log redaction protect sensitive data?
- What is the difference between Winston and Pino?
Challenge
Set up structured logging for a Node.js API. Implement: (1) Winston logger with JSON format, (2) request logging middleware with correlation ID, (3) error logging with stack traces, (4) sensitive data redaction, (5) different log levels for dev and production.
FAQ
Mini Project
Build a logging system for a microservice. Implement: (1) Pino logger with JSON output, (2) HTTP request/response logging middleware, (3) correlation ID propagation, (4) sensitive data redaction, (5) error Serialization with stack traces. Send logs to stdout.
What's Next
Continue to Structured Logging for a detailed guide on structured log formats.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro