Skip to content

Logging in Express Applications

Good logging is what makes debugging production issues possible after the fact, when you can't attach a live debugger. This lesson covers request logging with morgan and structured application logging with winston.

Two Kinds of Logs You Need

Request logs (via morgan) capture every incoming HTTP request, method, path, status, response time, giving you a timeline of traffic. Application logs (via winston or a similar logger) capture meaningful events and errors from your own business logic, with structured detail console.log() alone doesn't provide.

Both matter: request logs tell you *what* happened at the HTTP level, application logs tell you *why*, from inside your code.

const morgan = require('morgan');
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

app.use(morgan('combined'));

logger.info('User created', { userId: user.id });
logger.error('Payment failed', { orderId, error: err.message });

winston.format.json() produces structured, machine-parseable log lines, far more useful than plain strings once you're searching logs at scale.

Logging Levels

logger.error(message, meta);  // failures needing attention
logger.warn(message, meta);   // unexpected but non-fatal situations
logger.info(message, meta);   // normal, notable application events
logger.debug(message, meta);  // verbose detail, usually off in production
  • Use error sparingly, for things that genuinely need investigation, not routine validation failures.
  • info is a good default level for meaningful business events (user signed up, order placed).
  • Configure log level per environment, verbose in development, quieter in production.
  • Never log secrets, passwords, tokens, or full credit card numbers.

Logging Cheatsheet

The tools and patterns covered in this lesson.

Tool Purpose
morgan Automatic HTTP request logging
winston Structured, leveled application logging
pino Alternative high-performance structured logger
Log aggregation service Centralizing logs across multiple instances (e.g. Datadog, Loggly)

Structured Logging With Context

Rather than logging plain strings, attach structured metadata, request IDs, user IDs, relevant identifiers, so logs can be filtered and correlated later, especially useful when diagnosing an issue reported by a specific user.

app.use((req, res, next) => {
  req.requestId = crypto.randomUUID();
  next();
});

app.use((req, res, next) => {
  logger.info('Incoming request', {
    requestId: req.requestId,
    method: req.method,
    path: req.path,
  });
  next();
});

Logging Errors in Centralized Error-Handling Middleware

The centralized error handler (from the Error Middleware lesson) is the natural place to log unexpected errors with full context, before sending a clean, generic response to the client.

app.use((err, req, res, next) => {
  logger.error('Unhandled error', {
    requestId: req.requestId,
    message: err.message,
    stack: err.stack,
  });
  res.status(err.status || 500).json({ error: 'Internal server error' });
});

Common Mistakes

  • Relying solely on console.log() in production, with no levels, structure, or aggregation.
  • Logging sensitive data like passwords, tokens, or full payment details.
  • Logging every single request at error level, drowning real problems in noise.
  • Not attaching a request ID (or similar correlation identifier) to make related log lines easy to trace together.

Key Takeaways

  • Request logs (morgan) and application logs (winston) serve different, complementary purposes.
  • Structured, leveled logging is far more useful at scale than plain console.log() strings.
  • Never log secrets or sensitive personal data.
  • Attaching a request ID to every log line makes tracing a single request's full journey much easier.

Pro Tip

Generate a unique request ID at the very top of your middleware stack and include it in every log line for that request, this single habit makes tracing a specific user's bug report through your logs dramatically easier.