Course:Internet & Tools/
Lesson

Imagine you ship a feature on Friday. By Monday morning, users are reporting something is broken. You were not watching when it happened, and the user's description is "it just stopped working." Without logs, you are solving a mystery with no clues.

Logs are your application's flight recorder. Every important event gets written down with a timestamp, so you can travel back in time and see exactly what happened, in what order, and why. The better your logs, the faster you debug, and the calmer your Friday deploys become.

Why logs matter in practice

Logs serve four distinct purposes, and each one will save you time at some point in your career.

Use caseWhat logs tell you
DebuggingWhich line of code failed, and what the inputs were
MonitoringWhether the server is behaving normally right now
AuditingWho did what and exactly when they did it
SecurityWhether someone is probing for vulnerabilities

The key insight is that logs are written during normal operation, before anything goes wrong. By the time you need them, your chance to add more has already passed. Write logs now so that future-you has the evidence needed to fix things fast.

02

Reading logs to debug AI-generated backend code

AI-generated backend code has a specific problem: it almost never includes logging. When Copilot or ChatGPT generates an Express route or a database query, the code handles the happy path but silently swallows errors or fails without leaving any trace of what happened.

AI pitfall
AI-generated server code rarely adds logging, almost never uses appropriate log levels, and tends to catch errors with empty catch blocks. When the code breaks in production, you have zero visibility into what went wrong. Always add logging to AI-generated backend code before deploying it.

Here is a typical AI-generated route with no observability:

// AI-generated - no logging at all
app.get('/api/users/:id', async (req, res) => {
  try {
    const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
    res.json(user);
  } catch (e) {
    res.status(500).json({ error: 'Something went wrong' });
  }
});

When this breaks, you get nothing, just a 500 error and silence. Here is what it should look like:

// With proper logging
app.get('/api/users/:id', async (req, res) => {
  logger.info('Fetching user', { userId: req.params.id, requestId: req.id });
  try {
    const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
    if (!user) {
      logger.warn('User not found', { userId: req.params.id });
      return res.status(404).json({ error: 'User not found' });
    }
    logger.debug('User fetched successfully', { userId: user.id });
    res.json(user);
  } catch (error) {
    logger.error('Failed to fetch user', {
      userId: req.params.id,
      error: error.message,
      stack: error.stack
    });
    res.status(500).json({ error: 'Internal server error' });
  }
});

The difference is night and day when debugging production issues.

03

The five log levels

Not every event deserves the same amount of attention. Log levels let you filter by importance so you can focus on what matters without drowning in noise. Think of them like a hospital triage system, from routine check-in to full emergency.

LevelWhen to useVolumeProduction default
DEBUGStep-by-step tracing during developmentVery highOff
INFONormal, significant eventsMediumOn
WARNUnusual but handled situationsLowOn
ERROROperations that failedLowOn
FATALSystem cannot continueVery rareOn (with alerts)

DEBUG: Highly detailed tracing

[DEBUG] Entering getUser() with id=42
[DEBUG] SQL query: SELECT * FROM users WHERE id = 42
[DEBUG] Query returned 1 row in 3ms

DEBUG logs capture step-by-step details that are only useful when you are hunting a specific bug. They create a lot of volume, so you typically disable them in production and enable them temporarily when you need to trace a problem.

INFO: Normal operation highlights

[INFO] Server started on port 3000
[INFO] User alice@example.com logged in
[INFO] Order #12345 processed successfully

INFO marks events that are expected and significant. Reading INFO logs should give you a clear narrative of what the application did: started, accepted requests, processed data, finished cleanly.

WARN: Something unusual happened

[WARN] API response took 3.5s (threshold: 2s)
[WARN] Retrying failed request (attempt 2 of 3)
[WARN] Database connection pool at 80% capacity

Warnings are not failures, but they are early signals that something might go wrong soon. A slow response, a retry, a resource running low, these are yellow flags. Pay attention to warnings; they often precede errors by minutes or hours.

ERROR: Something failed

[ERROR] Database connection timeout after 30s
[ERROR] Payment processing failed: card declined
[ERROR] Failed to send email: SMTP server unreachable

An error means a specific operation failed. The application caught it and handled it gracefully, maybe it returned a 500 response to the user, but the underlying problem needs investigation.

FATAL: The application cannot continue

[FATAL] Out of memory - shutting down
[FATAL] Cannot connect to required database on startup
[FATAL] Critical configuration missing: JWT_SECRET not set

FATAL means the process is about to exit or is completely broken. This is your "wake someone up at 3 AM" level.

04

Anatomy of a log entry

A well-structured log line packs several pieces of information into a single record:

2024-01-24 14:32:15 [ERROR] src/api/users.js:42 - User lookup failed
  Error: User not found in database
    at getUser (src/api/users.js:42)
    at handleRequest (src/server.js:18)
    at processRequest (src/middleware.js:7)
PartExampleWhy it matters
Timestamp2024-01-24 14:32:15Tells you exactly when the event occurred
Level[ERROR]Lets you filter by severity
Locationsrc/api/users.js:42Takes you straight to the relevant code
MessageUser lookup failedHuman-readable summary of what happened
Stack traceThe indented linesShows the path the code took to reach the error

The stack traceWhat is stack trace?A list of function calls recorded at the moment an error occurs, showing exactly which functions were active and in what order. deserves special attention. Read it from top to bottom: the top line is where the error was thrown, and each line below shows the calling function. The bug is almost always at the top.

05

Plain text vs structured JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. logs

You have two main choices for how to structure your log output.

Plain text

2024-01-24 14:32:15 INFO  Server started on port 3000
2024-01-24 14:32:16 WARN  Response time: 3.2s for GET /api/users
2024-01-24 14:32:17 ERROR Connection to database failed

Plain text is easy to read in a terminalWhat is terminal?A text-based interface where you type commands to interact with your computer. Also called the command line or shell. with tail -f. It is great for local development. The downside is that querying it programmatically is awkward, you need regexWhat is regex?A compact pattern language for matching, searching, and replacing text, built into nearly every programming language and code editor. or custom parsing.

JSON format (structured loggingWhat is structured logging?Writing log entries as machine-readable JSON objects with consistent fields instead of plain text, making them searchable by log analysis tools.)

json
{
  "timestamp": "2024-01-24T14:32:17Z",
  "level": "ERROR",
  "message": "Connection to database failed",
  "file": "src/db.js",
  "line": 42,
  "userId": 12345,
  "requestId": "abc-123-def",
  "durationMs": 30000
}

JSON logs are machine-friendly. Log aggregation tools can indexWhat is index?A data structure the database maintains alongside a table so it can find rows by specific columns quickly instead of scanning everything. any field, which means you can search for all errors from a specific user, or all requests that took over 2 seconds, with a single query. In production environments, JSON is the professional standard.

Use plain text locally for readability, and JSON in production for queryability. Many logging libraries support both formats and switch based on an environment variable.
06

What to log and what to skip

Good logging is not about logging everything. It is about logging the right things.

Log thisSkip this
User IDs and request IDsPasswords and tokens
Error messages and stack tracesCredit card numbers
Request method and URLPersonal data you do not need
Response status and durationEvery iteration of a tight loop
Retry attempts and their outcomesRedundant "entering function X" noise

The privacy rule is absolute: if a piece of data could harm a user if exposed in a breach, it has no business in a log file.

07

Quick reference

LevelWhen to useExample message
DEBUGDetailed internal tracing"SQL query: SELECT * FROM users WHERE id=42"
INFONormal, significant events"User logged in", "Server started"
WARNUnusual but handled"Slow response 3.2s", "Retry attempt 2/3"
ERROROperation failed"Database timeout", "Payment declined"
FATALSystem cannot continue"Out of memory", "Required config missing"
javascript
// Logging in Node.js with Winston

const winston = require('winston');

// Configure logger
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// Add console logging in development
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

// Usage
logger.info('Server started', { port: 3000 });

logger.warn('API response slow', {
  endpoint: '/api/users',
  duration: 3500,
  threshold: 2000
});

logger.error('Database query failed', {
  query: 'SELECT * FROM users',
  error: err.message,
  stack: err.stack
});

// In Express middleware
app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    logger.info('HTTP Request', {
      method: req.method,
      url: req.url,
      status: res.statusCode,
      duration: Date.now(), start,
      userId: req.user?.id
    });
  });

  next();
});

// Error handling
app.use((err, req, res, next) => {
  logger.error('Unhandled error', {
    error: err.message,
    stack: err.stack,
    url: req.url,
    method: req.method,
    userId: req.user?.id
  });

  res.status(500).json({ error: 'Internal server error' });
});