CarouselForge Developer
feat: Phase 15 complete — mobile responsiveness, monitoring, Telegram polish, UAT
d0ded63
/**
* Structured logging module for enhanced observability
* Logs with severity levels, component tracking, and metrics
*/
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
interface LogEntry {
timestamp: string;
level: LogLevel;
component: string;
message: string;
duration?: number; // milliseconds
error?: string;
metadata?: Record<string, unknown>;
}
class Logger {
private isProduction = typeof process !== 'undefined' && process.env.NODE_ENV === 'production';
/**
* Log a message with timestamp and component context
*/
private log(level: LogLevel, component: string, message: string, metadata?: Record<string, unknown>) {
const entry: LogEntry = {
timestamp: new Date().toISOString(),
level,
component,
message,
metadata,
};
const prefix = `[${component}]`;
const formattedMessage = `${prefix} ${message}`;
const logWithMetadata = (consoleFn: typeof console.error) => {
if (metadata && Object.keys(metadata).length > 0) {
consoleFn(formattedMessage, metadata);
} else {
consoleFn(formattedMessage);
}
};
switch (level) {
case 'error':
logWithMetadata(console.error);
break;
case 'warn':
logWithMetadata(console.warn);
break;
case 'info':
if (!this.isProduction) {
logWithMetadata(console.log);
}
break;
case 'debug':
if (!this.isProduction) {
logWithMetadata(console.debug);
}
break;
}
}
/**
* Log debug message (development only)
*/
debug(component: string, message: string, metadata?: Record<string, unknown>) {
this.log('debug', component, message, metadata);
}
/**
* Log info message
*/
info(component: string, message: string, metadata?: Record<string, unknown>) {
this.log('info', component, message, metadata);
}
/**
* Log warning message
*/
warn(component: string, message: string, metadata?: Record<string, unknown>) {
this.log('warn', component, message, metadata);
}
/**
* Log error with optional error object
*/
error(component: string, message: string, error?: Error | unknown, metadata?: Record<string, unknown>) {
const errorMetadata = {
...metadata,
...(error instanceof Error && {
errorMessage: error.message,
errorStack: error.stack,
}),
};
this.log('error', component, message, errorMetadata);
}
/**
* Measure performance of an async function and log duration
*/
async measureAsync<T>(
component: string,
operationName: string,
fn: () => Promise<T>,
): Promise<T> {
const start = performance.now();
try {
const result = await fn();
const duration = performance.now() - start;
this.debug(component, `${operationName} completed`, { duration: `${duration.toFixed(2)}ms` });
return result;
} catch (error) {
const duration = performance.now() - start;
this.error(component, `${operationName} failed`, error, { duration: `${duration.toFixed(2)}ms` });
throw error;
}
}
/**
* Measure performance of a sync function and log duration
*/
measureSync<T>(
component: string,
operationName: string,
fn: () => T,
): T {
const start = performance.now();
try {
const result = fn();
const duration = performance.now() - start;
this.debug(component, `${operationName} completed`, { duration: `${duration.toFixed(2)}ms` });
return result;
} catch (error) {
const duration = performance.now() - start;
this.error(component, `${operationName} failed`, error, { duration: `${duration.toFixed(2)}ms` });
throw error;
}
}
}
// Export singleton instance
export const logger = new Logger();
/**
* Helper for tracking async operation metrics
*/
export function createAsyncMetricsTracker(component: string, operationName: string) {
const start = performance.now();
return {
success: (metadata?: Record<string, unknown>) => {
const duration = performance.now() - start;
logger.info(component, `${operationName} succeeded`, { duration: `${duration.toFixed(2)}ms`, ...metadata });
},
error: (error: Error | unknown, metadata?: Record<string, unknown>) => {
const duration = performance.now() - start;
logger.error(component, `${operationName} failed`, error, { duration: `${duration.toFixed(2)}ms`, ...metadata });
},
};
}