# Structured Logging System Implementation ## Summary Implements a comprehensive structured logging system to replace verbose console.* calls throughout the codebase, addressing all cleanup tasks from CLEANUP.md. ## What Changed ### Core Features - ✅ **Structured Logging System** - New `lib/logger.ts` with DEBUG, INFO, WARN, ERROR levels - ✅ **Environment-Based Control** - `LOG_LEVEL` env var controls verbosity (DEBUG/INFO/WARN/ERROR/NONE) - ✅ **JSON Logging Option** - `LOG_FORMAT=json` for structured JSON output - ✅ **Shared Constants** - Extracted session cookie name to `lib/constants.ts` ### Code Refactoring - ✅ Replaced all `console.*` calls in API routes with structured logger - ✅ Refactored `activity-log.ts` to use new logger system - ✅ Reduced verbose logging in auth, photos page, and upload routes - ✅ Updated proxy.ts to use structured logging - ✅ Removed unused legacy `/api/photos` route (replaced by `/api/photos/upload`) ### Security Improvements - ✅ Protected `/api/debug/session` endpoint with admin-only access - ✅ Added proper error logging with structured context ### Documentation - ✅ Documented multiple upload routes usage - ✅ Enhanced watch-activity.sh script documentation - ✅ Updated README.md with upload endpoint information - ✅ Added configuration documentation to next.config.ts ### Testing - ✅ Added 23 tests for logger system - ✅ Added 8 tests for refactored activity-log - ✅ All 43 tests passing ## Benefits 1. **Production-Ready Logging** - Environment-based control, defaults to INFO in production 2. **Reduced Verbosity** - DEBUG logs only show in development or when explicitly enabled 3. **Structured Output** - JSON format option for log aggregation tools 4. **Better Organization** - Shared constants, consistent logging patterns 5. **Improved Security** - Debug endpoint now requires admin access ## Testing ### Manual Testing - ✅ Server builds successfully - ✅ All tests pass (43/43) - ✅ Type checking passes - ✅ Linting passes - ✅ Production server runs with logs visible - ✅ Log levels work correctly (DEBUG shows all, INFO shows activity, etc.) ### Test Coverage - Logger system: 100% coverage - Activity log: 100% coverage - All existing tests still pass ## Configuration ### Environment Variables ```bash # Control log verbosity (DEBUG, INFO, WARN, ERROR, NONE) LOG_LEVEL=INFO # Use structured JSON logging LOG_FORMAT=json ``` ### Defaults - Development: `LOG_LEVEL=DEBUG` (shows all logs) - Production: `LOG_LEVEL=INFO` (shows activity and above) ## Migration Notes - No breaking changes (legacy route was unused) - All existing functionality preserved - Logs are now structured and filterable - Debug endpoint now requires admin authentication - Legacy `/api/photos` endpoint removed (use `/api/photos/upload` instead) ## Checklist - [x] All console.* calls replaced in API routes - [x] Logger system implemented with tests - [x] Activity logging refactored - [x] Debug endpoint protected - [x] Documentation updated - [x] All tests passing - [x] Type checking passes - [x] Linting passes - [x] Build succeeds - [x] Manual testing completed ## Related Issues Addresses cleanup tasks from CLEANUP.md: - Task 1: Verbose logging in production ✅ - Task 2: Activity logging optimization ✅ - Task 3: Upload verification logging ✅ - Task 4: Middleware debug logging ✅ - Task 5: Legacy upload route documentation ✅ - Task 6: Multiple upload routes documentation ✅ - Task 7: Cookie name constant extraction ✅ - Task 8: Next.js config documentation ✅ - Task 9: ARCHITECTURE.md (already correct) ✅ - Task 10: Watch activity script documentation ✅ Reviewed-on: #4
157 lines
4.0 KiB
TypeScript
157 lines
4.0 KiB
TypeScript
/**
|
|
* Structured logging utility with log levels and environment-based filtering
|
|
*
|
|
* Log levels (in order of severity):
|
|
* - DEBUG: Detailed information for debugging (only in development)
|
|
* - INFO: General informational messages
|
|
* - WARN: Warning messages for potentially harmful situations
|
|
* - ERROR: Error messages for error events
|
|
*
|
|
* Usage:
|
|
* import { logger } from '@/lib/logger'
|
|
* logger.debug('Debug message', { data })
|
|
* logger.info('Info message', { data })
|
|
* logger.warn('Warning message', { data })
|
|
* logger.error('Error message', { error })
|
|
*/
|
|
|
|
export enum LogLevel {
|
|
DEBUG = 0,
|
|
INFO = 1,
|
|
WARN = 2,
|
|
ERROR = 3,
|
|
NONE = 4, // Disable all logging
|
|
}
|
|
|
|
export interface LogContext {
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface Logger {
|
|
debug(message: string, context?: LogContext): void;
|
|
info(message: string, context?: LogContext): void;
|
|
warn(message: string, context?: LogContext): void;
|
|
error(message: string, context?: LogContext | Error): void;
|
|
isLevelEnabled(level: LogLevel): boolean;
|
|
}
|
|
|
|
/**
|
|
* Parse log level from environment variable or default based on NODE_ENV
|
|
*/
|
|
function getLogLevel(): LogLevel {
|
|
const envLogLevel = process.env.LOG_LEVEL?.toUpperCase();
|
|
|
|
// If explicitly set, use that
|
|
if (envLogLevel) {
|
|
switch (envLogLevel) {
|
|
case 'DEBUG':
|
|
return LogLevel.DEBUG;
|
|
case 'INFO':
|
|
return LogLevel.INFO;
|
|
case 'WARN':
|
|
return LogLevel.WARN;
|
|
case 'ERROR':
|
|
return LogLevel.ERROR;
|
|
case 'NONE':
|
|
return LogLevel.NONE;
|
|
default:
|
|
// Invalid value, fall through to default behavior
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Default behavior: DEBUG in development, INFO in production
|
|
return process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG;
|
|
}
|
|
|
|
/**
|
|
* Format log entry as structured JSON or human-readable string
|
|
*/
|
|
function formatLog(
|
|
level: LogLevel,
|
|
message: string,
|
|
context?: LogContext | Error
|
|
): string {
|
|
const timestamp = new Date().toISOString();
|
|
const levelName = LogLevel[level];
|
|
|
|
// If structured logging is enabled, output JSON
|
|
if (process.env.LOG_FORMAT === 'json') {
|
|
const logEntry: Record<string, unknown> = {
|
|
timestamp,
|
|
level: levelName,
|
|
message,
|
|
};
|
|
|
|
if (context) {
|
|
if (context instanceof Error) {
|
|
logEntry.error = {
|
|
name: context.name,
|
|
message: context.message,
|
|
stack: context.stack,
|
|
};
|
|
} else {
|
|
Object.assign(logEntry, context);
|
|
}
|
|
}
|
|
|
|
return JSON.stringify(logEntry);
|
|
}
|
|
|
|
// Human-readable format
|
|
const contextStr = context
|
|
? context instanceof Error
|
|
? ` | Error: ${context.name}: ${context.message}`
|
|
: ` | ${JSON.stringify(context)}`
|
|
: '';
|
|
|
|
return `[${levelName}] ${timestamp} | ${message}${contextStr}`;
|
|
}
|
|
|
|
/**
|
|
* Create a logger instance with the configured log level
|
|
*/
|
|
function createLogger(): Logger {
|
|
const currentLevel = getLogLevel();
|
|
|
|
const shouldLog = (level: LogLevel): boolean => {
|
|
return level >= currentLevel;
|
|
};
|
|
|
|
return {
|
|
debug(message: string, context?: LogContext): void {
|
|
if (shouldLog(LogLevel.DEBUG)) {
|
|
console.log(formatLog(LogLevel.DEBUG, message, context));
|
|
}
|
|
},
|
|
|
|
info(message: string, context?: LogContext): void {
|
|
if (shouldLog(LogLevel.INFO)) {
|
|
console.log(formatLog(LogLevel.INFO, message, context));
|
|
}
|
|
},
|
|
|
|
warn(message: string, context?: LogContext): void {
|
|
if (shouldLog(LogLevel.WARN)) {
|
|
console.warn(formatLog(LogLevel.WARN, message, context));
|
|
}
|
|
},
|
|
|
|
error(message: string, context?: LogContext | Error): void {
|
|
if (shouldLog(LogLevel.ERROR)) {
|
|
console.error(formatLog(LogLevel.ERROR, message, context));
|
|
}
|
|
},
|
|
|
|
isLevelEnabled(level: LogLevel): boolean {
|
|
return shouldLog(level);
|
|
},
|
|
};
|
|
}
|
|
|
|
// Export singleton logger instance
|
|
export const logger = createLogger();
|
|
|
|
// Export for testing
|
|
export { getLogLevel, formatLog, createLogger };
|