Files
dofus-manager/docs/architecture/19-monitoring-observability.md
2026-01-19 08:52:38 +01:00

2.6 KiB

19. Monitoring & Observability

Structured Logging (Pino)

// src/lib/server/logger.ts

import pino from 'pino';

const isDev = process.env.NODE_ENV === 'development';

export const logger = pino({
  level: process.env.LOG_LEVEL ?? (isDev ? 'debug' : 'info'),

  transport: isDev
    ? {
        target: 'pino-pretty',
        options: {
          colorize: true,
          translateTime: 'HH:MM:ss',
          ignore: 'pid,hostname',
        },
      }
    : undefined,

  formatters: {
    level: (label) => ({ level: label }),
  },

  base: {
    env: process.env.NODE_ENV,
    version: process.env.APP_VERSION ?? '0.0.0',
  },

  redact: {
    paths: ['password', 'token', 'authorization'],
    censor: '[REDACTED]',
  },
});

export const dbLogger = logger.child({ module: 'database' });
export const apiLogger = logger.child({ module: 'api' });

Health Check Endpoint

// src/server/functions/health.ts

import { createServerFn } from '@tanstack/react-start/server';
import { db } from '@/lib/server/db';

interface HealthStatus {
  status: 'healthy' | 'degraded' | 'unhealthy';
  timestamp: string;
  version: string;
  checks: {
    database: boolean;
    cache: boolean;
  };
}

export const getHealth = createServerFn({ method: 'GET' }).handler(
  async (): Promise<HealthStatus> => {
    const checks = {
      database: false,
      cache: true,
    };

    try {
      await db.$queryRaw`SELECT 1`;
      checks.database = true;
    } catch {
      // Database check failed
    }

    const allHealthy = Object.values(checks).every(Boolean);

    return {
      status: allHealthy ? 'healthy' : 'degraded',
      timestamp: new Date().toISOString(),
      version: process.env.APP_VERSION ?? '0.0.0',
      checks,
    };
  }
);

Application Metrics

// src/lib/server/metrics.ts

class MetricsCollector {
  private startTime = Date.now();
  private data = {
    requests: { total: 0, errors: 0 },
    database: { queries: 0, slowQueries: 0 },
    cache: { hits: 0, misses: 0 },
  };

  incrementRequest(isError = false): void {
    this.data.requests.total++;
    if (isError) this.data.requests.errors++;
  }

  incrementDbQuery(isSlow = false): void {
    this.data.database.queries++;
    if (isSlow) this.data.database.slowQueries++;
  }

  incrementCacheHit(): void {
    this.data.cache.hits++;
  }

  incrementCacheMiss(): void {
    this.data.cache.misses++;
  }

  getMetrics() {
    return {
      ...this.data,
      uptime: Math.floor((Date.now() - this.startTime) / 1000),
    };
  }
}

export const metrics = new MetricsCollector();