mirror_match/lib/logger.ts
ilia 08914dc469 Implements a comprehensive structured logging system to replace verbose console.* calls throughout the codebase, addressing all cleanup tasks from CLEANUP.md. (#4)
# 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
2026-01-04 19:42:49 -05:00

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 };