DEV Community

Timevolt
Timevolt

Posted on

Monitoring and Logging: The Force Awakens Your Alerts

The Quest Begins (The "Why")

Look, the reality is that I once got paged at 3 a.m. because a user complained that the checkout page felt “sluggish.” I rolled out of bed, opened the dashboard, and saw… nothing. No spikes in CPU, no error rates, just a flat line that told me nothing useful. The logs? A tangled mess of console.log statements scattered across services, each with its own timestamp format and zero context. I felt like I was trying to find a needle in a haystack while blindfolded. That night I realized we were flying blind—users were experiencing problems before we even had a clue something was wrong. The dragon I needed to slay wasn’t a bug in the code; it was our lack of visibility.

The Revelation (The Insight)

Here’s the thing: monitoring and logging aren’t just optional extras; they’re the radar and black box of your application. When you combine metrics (the “what’s happening right now”) with structured logs (the “why it happened”) and a trace ID that follows a request across services, you gain the ability to spot anomalies before they bubble up to the user. It’s like giving yourself a early‑warning system that whispers, “Hey, something’s off,” instead of shouting after the damage is done. The insight hit me when I started correlating a rise in latency metrics with a specific log pattern—suddenly I could see that a slow third‑party API was the culprit, not my own code. That moment felt like discovering a hidden Easter egg in a classic 8‑bit game: a small detail that unlocked a whole new level of understanding.

Wielding the Power (Code & Examples)

Let’s get concrete. Imagine a simple Express endpoint that processes a payment. Here’s the before version—what many of us start with:

// before.js – minimal logging, no correlation
const express = require('express');
const app = express();

app.post('/pay', (req, res) => {
  console.log('Payment request received'); // <-- useless without context
  // ... business logic …
  const result = processPayment(req.body);
  if (result.error) {
    console.error('Payment failed'); // <-- no request ID, no timing
    return res.status(500).send('Failed');
  }
  res.send('OK');
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

When something goes wrong, you’re left guessing which request caused the error, how long it took, and whether it was an isolated spike or a trend.

Now, the after version—structured logging, a request ID, and a latency metric:

// after.js – powered by pino + prom-client + opentelemetry
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const pino = require('pino')();
const client = require('prom-client');
const { trace, context, propagation } = require('@opentelemetry/api');

// metric: histogram for request duration
const requestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.3, 0.5, 0.7, 1, 2, 5],
});

// middleware to inject request ID and start a span
app.use((req, res, next) => {
  const reqId = uuidv4();
  req.id = reqId;
  pino.info({ reqId, method: req.method, url: req.originalUrl }, 'incoming request');

  // start a span for tracing
  const span = trace.getTracer('payment-service').startSpan('HTTP request');
  context.with(context.setValue(context.active(), span), () => {
    res.on('finish', () => {
      span.setAttribute('http.status_code', res.statusCode);
      span.end();
    });
    next();
  });
});

// actual handler
app.post('/pay', (req, res) => {
  const end = requestDuration.startTimer({ method: req.method, route: req.path });

  pino.info({ reqId: req.id }, 'processing payment');
  try {
    const result = processPayment(req.body);
    if (result.error) {
      pino.error({ reqId: req.id, error: result.error }, 'payment failed');
      end({ status_code: 500 });
      return res.status(500).send('Failed');
    }
    pino.info({ reqId: req.id }, 'payment succeeded');
    end({ status_code: 200 });
    res.send('OK');
  } catch (err) {
    pino.error({ reqId: req.id, err }, 'unexpected error');
    end({ status_code: 500 });
    res.status(500).send('Error');
  }
});

// expose metrics endpoint
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', client.register.contentType);
  res.end(await client.register.metrics());
});

app.listen(3000, () => pino.info('Service listening on :3000'));
Enter fullscreen mode Exit fullscreen mode

What changed?

  1. Request ID (uuidv4) lets you grep every log line for a single user journey.
  2. Structured logging (pino) emits JSON—easy to ship to ELK, Loki, or Datadog.
  3. Histogram metric tracks latency per endpoint; you can alert on the 95th‑percentile creeping up.
  4. OpenTelemetry span gives you a trace ID that appears in both logs and your tracing backend (Jaeger, Tempo, etc.).
  5. Metrics endpoint (/metrics) is scraped by Prometheus, enabling alerts and dashboards.

Traps to Avoid (the “boss fights” on the quest)

  • Logging everything at info level – you’ll drown in noise. Reserve error for real problems, warn for unexpected but non‑fatal situations, and debug for dev‑only details.
  • Forgetting to propagate the request ID – if you spawn a background job or call another service, make sure you pass the ID (or trace context) along; otherwise the trail goes cold.
  • Over‑instrumenting – adding a histogram for every tiny function can blow up cardinality. Start with high‑level request metrics, then drill down where you actually see issues.

Why This New Power Matters

When you start treating observability as a first‑class feature, you shift from reactive firefighting to proactive hunting. You can set SLO‑based alerts that fire when error budgets are burning, not when users start complaining. You can spot a creeping latency trend caused by a slow database query before it turns into a timeout cascade. And when an incident does happen, you have a neat breadcrumb trail: request ID → logs → trace → metric. That means MTTR drops from hours to minutes, and your on‑call rotations feel less like a nightmare and more like a well‑rehearsed drill.

Pretty cool, right? You’ve just leveled up your service from a clunky torch‑bearer to a night‑vision‑equipped scout.


Your turn: Pick one service you maintain, add a request ID, ship a simple latency histogram, and push a structured log line with that ID. Then, fire a synthetic error (maybe a malformed payload) and watch how quickly you can pinpoint it in your logs and metrics. What’s the first thing you notice? Share your win in the comments—I’m excited to hear how your newfound vision changes the game!

Top comments (0)