Skip to content

Latest commit

 

History

History
303 lines (254 loc) · 7 KB

File metadata and controls

303 lines (254 loc) · 7 KB

Logging Middleware

Provides comprehensive request and response logging for debugging, monitoring, and observability.

Usage

Simple Logging

import { addLogging } from '@fgrzl/fetch/middleware/logging';

// Default logging (info level)
const loggedClient = addLogging(client);

// Custom log level
const debugClient = addLogging(client, {
  level: 'debug',
});

Advanced Configuration

import {
  addLogging,
  createLoggingMiddleware,
} from '@fgrzl/fetch/middleware/logging';

// Comprehensive logging configuration
const loggedClient = addLogging(client, {
  level: 'debug',
  includeRequestHeaders: true,
  includeResponseHeaders: true,
  includeRequestBody: true,
  includeResponseBody: true,
  logger: console, // Custom logger
  filter: (request) => !request.url?.includes('/health'),
});

// Factory approach
const loggingMiddleware = createLoggingMiddleware({
  level: 'info',
  logger: customLogger,
});
client.use(loggingMiddleware);

Custom Logger

import { addLogging } from '@fgrzl/fetch/middleware/logging';

// Custom logger implementation
const customLogger = {
  debug: (message: string, meta?: any) =>
    console.debug(`[DEBUG] ${message}`, meta),
  info: (message: string, meta?: any) =>
    console.info(`[INFO] ${message}`, meta),
  warn: (message: string, meta?: any) =>
    console.warn(`[WARN] ${message}`, meta),
  error: (message: string, meta?: any) =>
    console.error(`[ERROR] ${message}`, meta),
};

const loggedClient = addLogging(client, {
  logger: customLogger,
  level: 'debug',
});

Configuration Options

interface LoggingOptions {
  level?: LogLevel; // Log level (default: 'info')
  logger?: Logger; // Custom logger (default: console)
  includeRequestHeaders?: boolean; // Log request headers (default: false)
  includeResponseHeaders?: boolean; // Log response headers (default: false)
  includeRequestBody?: boolean; // Log request body (default: false)
  includeResponseBody?: boolean; // Log response body (default: false)
  filter?: (request: RequestInit & { url: string }) => boolean; // Request filter
  sanitize?: (data: any) => any; // Data sanitization function
}

type LogLevel = 'debug' | 'info' | 'warn' | 'error';

interface Logger {
  debug(message: string, meta?: any): void;
  info(message: string, meta?: any): void;
  warn(message: string, meta?: any): void;
  error(message: string, meta?: any): void;
}

Log Output Examples

Basic Request/Response

→ GET /api/users → 200 (152ms)
← GET /api/users → 200 (152ms) {
  level: 'info',
  timestamp: 1234567890123,
  method: 'GET',
  url: '/api/users',
  status: 200,
  duration: 152
}

Debug Level with Headers

→ POST /api/users {
  level: 'debug',
  timestamp: 1234567890123,
  method: 'POST',
  url: '/api/users',
  requestHeaders: {
    'content-type': 'application/json',
    'authorization': '[REDACTED]'
  },
  requestBody: {
    name: 'John Doe',
    email: 'john@example.com'
  }
}

← POST /api/users → 201 (95ms) {
  level: 'info',
  timestamp: 1234567890218,
  method: 'POST',
  url: '/api/users',
  status: 201,
  duration: 95,
  responseHeaders: {
    'content-type': 'application/json',
    'location': '/api/users/456'
  },
  responseBody: {
    id: 456,
    name: 'John Doe',
    email: 'john@example.com',
    createdAt: '2023-01-01T12:00:00Z'
  }
}

Examples

Production Logging

// Production-safe logging configuration
const productionClient = addLogging(client, {
  level: 'info',
  includeRequestHeaders: false,
  includeResponseHeaders: false,
  includeRequestBody: false,
  includeResponseBody: false,
  sanitize: (data) => {
    // Remove sensitive data
    const sanitized = { ...data };
    if (sanitized.requestHeaders?.authorization) {
      sanitized.requestHeaders.authorization = '[REDACTED]';
    }
    return sanitized;
  },
});

Development Logging

// Full visibility for development
const devClient = addLogging(client, {
  level: 'debug',
  includeRequestHeaders: true,
  includeResponseHeaders: true,
  includeRequestBody: true,
  includeResponseBody: true,
});

Selective Logging

// Only log API requests, skip health checks
const selectiveClient = addLogging(client, {
  filter: (request) =>
    request.url?.startsWith('/api/') && !request.url.includes('/health'),
  level: 'info',
});

Structured Logging with Winston

import winston from 'winston';

const winstonLogger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [new winston.transports.File({ filename: 'api-requests.log' })],
});

const structuredClient = addLogging(client, {
  logger: {
    debug: (msg, meta) => winstonLogger.debug(msg, meta),
    info: (msg, meta) => winstonLogger.info(msg, meta),
    warn: (msg, meta) => winstonLogger.warn(msg, meta),
    error: (msg, meta) => winstonLogger.error(msg, meta),
  },
  includeRequestBody: true,
  includeResponseBody: true,
});

Integration Examples

Next.js API Monitoring

// Monitor API route performance
const monitoredClient = addLogging(new FetchClient(), {
  logger: {
    info: (message, meta) => {
      console.log(message);
      // Send metrics to monitoring service
      if (meta?.duration > 1000) {
        analytics.track('slow_api_request', {
          url: meta.url,
          duration: meta.duration,
        });
      }
    },
  },
});

Error Tracking Integration

import * as Sentry from '@sentry/node';

const errorTrackingClient = addLogging(client, {
  logger: {
    error: (message, meta) => {
      console.error(message, meta);
      if (meta?.status >= 400) {
        Sentry.addBreadcrumb({
          category: 'http',
          message: `${meta.method} ${meta.url}${meta.status}`,
          level: 'error',
          data: meta,
        });
      }
    },
  },
});

Security Considerations

Data Sanitization

const secureClient = addLogging(client, {
  sanitize: (data) => {
    const sanitized = { ...data };

    // Redact sensitive headers
    if (sanitized.requestHeaders) {
      ['authorization', 'cookie', 'x-api-key'].forEach((header) => {
        if (sanitized.requestHeaders[header]) {
          sanitized.requestHeaders[header] = '[REDACTED]';
        }
      });
    }

    // Redact sensitive body fields
    if (sanitized.requestBody && typeof sanitized.requestBody === 'object') {
      ['password', 'token', 'secret'].forEach((field) => {
        if (sanitized.requestBody[field]) {
          sanitized.requestBody[field] = '[REDACTED]';
        }
      });
    }

    return sanitized;
  },
});

Best Practices

  1. Production safety: Never log sensitive data in production
  2. Performance impact: Logging adds overhead, especially body logging
  3. Log levels: Use appropriate levels (debug for dev, info for prod)
  4. Sanitization: Always sanitize sensitive data before logging
  5. Filtering: Skip health checks and other noisy endpoints
  6. Structured logging: Use structured loggers for better observability
  7. Log rotation: Configure log rotation to prevent disk space issues