localgreenchain/lib/logging/logger.ts
Claude 5ea8bab5c3
Add production deployment infrastructure (Agent 4)
- Docker: Multi-stage Dockerfile with security hardening, docker-compose
  for production and development environments
- Environment: Comprehensive .env.example with all config options,
  lib/config/env.ts for typed environment validation
- Logging: Structured JSON logging with request/response middleware
- Monitoring: Prometheus metrics endpoint, Grafana dashboard, health
  checks (liveness/readiness probes)
- Security: Security headers, rate limiting, CORS middleware
- CI/CD: GitHub Actions workflows for CI, production deploy, and
  preview deployments
- Error tracking: Sentry integration foundation

Files created:
- Docker: Dockerfile, docker-compose.yml, docker-compose.dev.yml, .dockerignore
- Config: lib/config/env.ts, lib/config/index.ts
- Logging: lib/logging/logger.ts, lib/logging/middleware.ts
- Monitoring: lib/monitoring/sentry.ts, lib/monitoring/metrics.ts,
  lib/monitoring/health.ts
- Security: lib/security/headers.ts, lib/security/rateLimit.ts,
  lib/security/cors.ts
- API: pages/api/health/*, pages/api/metrics.ts
- Infra: infra/prometheus/prometheus.yml, infra/grafana/*
2025-11-23 03:54:03 +00:00

188 lines
4.6 KiB
TypeScript

/**
* Structured Logging System
* Agent 4: Production Deployment
*
* Provides structured JSON logging with support for different log levels,
* context enrichment, and production-ready formatting.
*/
import { env, LogLevel } from '../config';
interface LogContext {
[key: string]: unknown;
}
interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
service: string;
environment: string;
context?: LogContext;
error?: {
name: string;
message: string;
stack?: string;
};
}
type LogMethod = (message: string, context?: LogContext) => void;
interface Logger {
error: (message: string, errorOrContext?: Error | LogContext, context?: LogContext) => void;
warn: LogMethod;
info: LogMethod;
debug: LogMethod;
trace: LogMethod;
child: (context: LogContext) => Logger;
}
const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
error: 0,
warn: 1,
info: 2,
debug: 3,
trace: 4,
};
const LOG_LEVEL_COLORS: Record<LogLevel, string> = {
error: '\x1b[31m', // Red
warn: '\x1b[33m', // Yellow
info: '\x1b[36m', // Cyan
debug: '\x1b[35m', // Magenta
trace: '\x1b[90m', // Gray
};
const RESET_COLOR = '\x1b[0m';
class LoggerImpl implements Logger {
private baseContext: LogContext;
private minLevel: number;
private format: 'json' | 'pretty';
private serviceName: string;
constructor(context: LogContext = {}) {
this.baseContext = context;
this.minLevel = LOG_LEVEL_PRIORITY[env.logLevel];
this.format = env.logFormat;
this.serviceName = env.appName;
}
private shouldLog(level: LogLevel): boolean {
return LOG_LEVEL_PRIORITY[level] <= this.minLevel;
}
private formatEntry(entry: LogEntry): string {
if (this.format === 'pretty') {
return this.formatPretty(entry);
}
return JSON.stringify(entry);
}
private formatPretty(entry: LogEntry): string {
const color = LOG_LEVEL_COLORS[entry.level];
const timestamp = new Date(entry.timestamp).toLocaleTimeString();
const level = entry.level.toUpperCase().padEnd(5);
let output = `${color}[${timestamp}] ${level}${RESET_COLOR} ${entry.message}`;
if (entry.context && Object.keys(entry.context).length > 0) {
output += ` ${JSON.stringify(entry.context)}`;
}
if (entry.error) {
output += `\n ${color}Error: ${entry.error.message}${RESET_COLOR}`;
if (entry.error.stack) {
output += `\n${entry.error.stack.split('\n').slice(1).join('\n')}`;
}
}
return output;
}
private log(level: LogLevel, message: string, context?: LogContext, error?: Error): void {
if (!this.shouldLog(level)) {
return;
}
const entry: LogEntry = {
timestamp: new Date().toISOString(),
level,
message,
service: this.serviceName,
environment: env.nodeEnv,
};
// Merge base context with provided context
const mergedContext = { ...this.baseContext, ...context };
if (Object.keys(mergedContext).length > 0) {
entry.context = mergedContext;
}
// Add error details if present
if (error) {
entry.error = {
name: error.name,
message: error.message,
stack: error.stack,
};
}
const output = this.formatEntry(entry);
// Use appropriate console method
switch (level) {
case 'error':
console.error(output);
break;
case 'warn':
console.warn(output);
break;
case 'debug':
case 'trace':
console.debug(output);
break;
default:
console.log(output);
}
}
error(message: string, errorOrContext?: Error | LogContext, context?: LogContext): void {
if (errorOrContext instanceof Error) {
this.log('error', message, context, errorOrContext);
} else {
this.log('error', message, errorOrContext);
}
}
warn(message: string, context?: LogContext): void {
this.log('warn', message, context);
}
info(message: string, context?: LogContext): void {
this.log('info', message, context);
}
debug(message: string, context?: LogContext): void {
this.log('debug', message, context);
}
trace(message: string, context?: LogContext): void {
this.log('trace', message, context);
}
child(context: LogContext): Logger {
return new LoggerImpl({ ...this.baseContext, ...context });
}
}
// Create and export default logger instance
export const logger = new LoggerImpl();
// Export for creating child loggers with context
export function createLogger(context: LogContext): Logger {
return new LoggerImpl(context);
}
// Export types
export type { Logger, LogContext, LogEntry };