feat(logging): Add structured Logger and integrate into Smarts3Server; pass full config to server
This commit is contained in:
130
ts/classes/logger.ts
Normal file
130
ts/classes/logger.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import type { ILoggingConfig } from '../index.js';
|
||||
|
||||
/**
|
||||
* Log levels in order of severity
|
||||
*/
|
||||
const LOG_LEVELS = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
debug: 3,
|
||||
} as const;
|
||||
|
||||
type LogLevel = keyof typeof LOG_LEVELS;
|
||||
|
||||
/**
|
||||
* Structured logger with configurable levels and formats
|
||||
*/
|
||||
export class Logger {
|
||||
private config: Required<ILoggingConfig>;
|
||||
private minLevel: number;
|
||||
|
||||
constructor(config: ILoggingConfig) {
|
||||
// Apply defaults for any missing config
|
||||
this.config = {
|
||||
level: config.level ?? 'info',
|
||||
format: config.format ?? 'text',
|
||||
enabled: config.enabled ?? true,
|
||||
};
|
||||
this.minLevel = LOG_LEVELS[this.config.level];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a log level should be output
|
||||
*/
|
||||
private shouldLog(level: LogLevel): boolean {
|
||||
if (!this.config.enabled) {
|
||||
return false;
|
||||
}
|
||||
return LOG_LEVELS[level] <= this.minLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a log message
|
||||
*/
|
||||
private format(level: LogLevel, message: string, meta?: Record<string, any>): string {
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
if (this.config.format === 'json') {
|
||||
return JSON.stringify({
|
||||
timestamp,
|
||||
level,
|
||||
message,
|
||||
...(meta || {}),
|
||||
});
|
||||
}
|
||||
|
||||
// Text format
|
||||
const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';
|
||||
return `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log at error level
|
||||
*/
|
||||
public error(message: string, meta?: Record<string, any>): void {
|
||||
if (this.shouldLog('error')) {
|
||||
console.error(this.format('error', message, meta));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log at warn level
|
||||
*/
|
||||
public warn(message: string, meta?: Record<string, any>): void {
|
||||
if (this.shouldLog('warn')) {
|
||||
console.warn(this.format('warn', message, meta));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log at info level
|
||||
*/
|
||||
public info(message: string, meta?: Record<string, any>): void {
|
||||
if (this.shouldLog('info')) {
|
||||
console.log(this.format('info', message, meta));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log at debug level
|
||||
*/
|
||||
public debug(message: string, meta?: Record<string, any>): void {
|
||||
if (this.shouldLog('debug')) {
|
||||
console.log(this.format('debug', message, meta));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log HTTP request
|
||||
*/
|
||||
public request(method: string, url: string, meta?: Record<string, any>): void {
|
||||
this.info(`→ ${method} ${url}`, meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log HTTP response
|
||||
*/
|
||||
public response(method: string, url: string, statusCode: number, duration: number): void {
|
||||
const level: LogLevel = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
|
||||
|
||||
if (this.shouldLog(level)) {
|
||||
const message = `← ${method} ${url} - ${statusCode} (${duration}ms)`;
|
||||
|
||||
if (level === 'error') {
|
||||
this.error(message, { statusCode, duration });
|
||||
} else if (level === 'warn') {
|
||||
this.warn(message, { statusCode, duration });
|
||||
} else {
|
||||
this.info(message, { statusCode, duration });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log S3 error
|
||||
*/
|
||||
public s3Error(code: string, message: string, status: number): void {
|
||||
this.error(`[S3Error] ${code}: ${message}`, { code, status });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user