Back to the blog

Logging and Monitoring: Sentry, Metrics and Alerts

Setting up application monitoring: Sentry for errors, structured logs, metrics, alerts and tracing.

January 5, 2026
10 min read
820 views
MOLOTILO

MOLOTILO DIGITAL

Logging and Monitoring: Sentry, Metrics and Alerts

Why you need monitoring

Without monitoring, you learn about problems from your users. With monitoring — before them. Logging, error tracking and metrics are the foundation of reliable applications.

Sentry — error tracking

npm install @sentry/nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  integrations: [
    new Sentry.Replay({
      maskAllText: false,
      blockAllMedia: false
    })
  ]
});

// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0
});

// Manual error capture
try {
  await riskyOperation();
} catch (error) {
  Sentry.captureException(error, {
    tags: { feature: 'checkout' },
    extra: { orderId: '123' }
  });
}

// Adding user context
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.name
});

// Breadcrumbs for debugging
Sentry.addBreadcrumb({
  category: 'auth',
  message: 'User logged in',
  level: 'info'
});

Structured logs

import pino from 'pino';

// Creating a logger
const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: process.env.NODE_ENV !== 'production'
    }
  },
  base: {
    env: process.env.NODE_ENV,
    version: process.env.APP_VERSION
  }
});

// Usage
logger.info({ userId: '123', action: 'login' }, 'User logged in');
logger.error({ err, orderId: '456' }, 'Payment failed');
logger.warn({ threshold: 0.9, current: 0.95 }, 'Memory usage high');

// Child logger with context
const requestLogger = logger.child({
  requestId: crypto.randomUUID(),
  path: req.path
});

requestLogger.info('Request started');
// ... processing
requestLogger.info({ duration: 150 }, 'Request completed');

// Express middleware
import pinoHttp from 'pino-http';

app.use(pinoHttp({
  logger,
  customLogLevel: (req, res, err) => {
    if (res.statusCode >= 500) return 'error';
    if (res.statusCode >= 400) return 'warn';
    return 'info';
  },
  customSuccessMessage: (req, res) => {
    return `${req.method} ${req.url} - ${res.statusCode}`;
  }
}));

Metrics

import { Counter, Histogram, Registry } from 'prom-client';

const register = new Registry();

// Request counter
const httpRequestsTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'path', 'status'],
  registers: [register]
});

// Response time histogram
const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration in seconds',
  labelNames: ['method', 'path'],
  buckets: [0.1, 0.5, 1, 2, 5],
  registers: [register]
});

// Middleware for collecting metrics
app.use((req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;

    httpRequestsTotal.inc({
      method: req.method,
      path: req.route?.path || req.path,
      status: res.statusCode
    });

    httpRequestDuration.observe(
      { method: req.method, path: req.route?.path || req.path },
      duration
    );
  });

  next();
});

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

Alerts

// Sending an alert to Slack
async function sendSlackAlert(message: string, severity: 'info' | 'warning' | 'error') {
  const colors = {
    info: '#36a64f',
    warning: '#ff9800',
    error: '#f44336'
  };

  await fetch(process.env.SLACK_WEBHOOK_URL!, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      attachments: [{
        color: colors[severity],
        title: `[${severity.toUpperCase()}] Alert`,
        text: message,
        ts: Math.floor(Date.now() / 1000)
      }]
    })
  });
}

// Alert on a critical error
process.on('uncaughtException', async (error) => {
  logger.fatal({ err: error }, 'Uncaught exception');
  Sentry.captureException(error);
  await sendSlackAlert(`Uncaught exception: ${error.message}`, 'error');
  process.exit(1);
});

// Alert on high load
setInterval(async () => {
  const memUsage = process.memoryUsage();
  const heapUsedPercent = memUsage.heapUsed / memUsage.heapTotal;

  if (heapUsedPercent > 0.9) {
    await sendSlackAlert(
      `High memory usage: ${(heapUsedPercent * 100).toFixed(1)}%`,
      'warning'
    );
  }
}, 60000);

Health Checks

// app/api/health/route.ts
export async function GET() {
  const checks = {
    database: await checkDatabase(),
    redis: await checkRedis(),
    memory: checkMemory()
  };

  const healthy = Object.values(checks).every(c => c.status === 'ok');

  return Response.json(
    { status: healthy ? 'healthy' : 'unhealthy', checks },
    { status: healthy ? 200 : 503 }
  );
}

async function checkDatabase() {
  try {
    await prisma.$queryRaw`SELECT 1`;
    return { status: 'ok' };
  } catch (error) {
    return { status: 'error', message: error.message };
  }
}

async function checkRedis() {
  try {
    await redis.ping();
    return { status: 'ok' };
  } catch (error) {
    return { status: 'error', message: error.message };
  }
}

function checkMemory() {
  const usage = process.memoryUsage();
  const heapPercent = usage.heapUsed / usage.heapTotal;

  return {
    status: heapPercent < 0.9 ? 'ok' : 'warning',
    heapUsed: Math.round(usage.heapUsed / 1024 / 1024) + 'MB'
  };
}

Conclusion

Monitoring is an investment in stability. Sentry catches errors, structured logs help debugging, metrics show trends, alerts warn about problems. Set it all up before production.

If you don’t monitor — you don’t know what’s happening. And if you don’t know — you can’t fix it.

Enjoyed the article?

Subscribe to our blog so you don’t miss new posts