@seawingai/winglog
Version:
A powerful TypeScript/JavaScript logging library built on top of Pino for structured logging with enhanced features
200 lines (175 loc) • 6.36 kB
text/typescript
import pino from 'pino';
import * as path from 'path';
import * as fs from 'fs';
export enum LogType {
FAILED = 'FAILED',
WARN = 'WARN',
DEBUG = 'DEBUG',
SUCCESS = 'SUCCESS',
STARTED = 'STARTED',
FINISHED = 'FINISHED',
INFO = 'INFO',
}
export class WingLog {
private name: string;
private logger: pino.Logger;
constructor(name: string) {
this.name = name;
// Ensure logs directory exists
const logsDir = path.join(process.cwd(), 'logs');
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
const logFile = path.join(logsDir, `${name}.log`);
const createConsoleTransport = () => {
try {
return pino.transport({
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname',
messageFormat: '{msg}',
},
});
} catch (error) {
return pino.destination(1); // stdout
}
};
this.logger = pino({
level: 'debug',
timestamp: () => `,"time":"${new Date().toISOString()}"`,
}, pino.multistream([
{
level: 'debug',
stream: createConsoleTransport(),
},
{
level: 'debug',
stream: pino.destination({
dest: logFile,
sync: false,
mkdir: true,
}),
},
]));
this.logger.debug(`Logger initialized: [${this.name}]`);
}
private formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60);
let secs = Math.round(seconds % 60);
if (secs === 60) {
secs = 0;
}
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
private log(message: string, type: LogType | '', icon: string, startTime?: number): number {
let timeSpent = 0;
let duration = '';
icon = icon || '';
if (startTime) {
timeSpent = (Date.now() - startTime) / 1000;
duration = `Duration:[${this.formatDuration(timeSpent)}]:`;
}
let logMessage = `[${this.name}]:${duration}${message}`;
switch (type) {
case LogType.FAILED:
this.logger.error(logMessage);
break;
case LogType.WARN:
this.logger.warn(logMessage);
break;
case LogType.DEBUG:
this.logger.debug(logMessage);
break;
case LogType.SUCCESS:
case LogType.STARTED:
case LogType.FINISHED:
case LogType.INFO:
default:
this.logger.info(logMessage);
break;
}
return Number(timeSpent.toFixed(2));
}
private logWithData(message: string, type: LogType, rec: Record<string, any>): void {
this.log(`${message} ${JSON.stringify(rec)}`, type, '', 0);
}
// Method overloads for started
started(message: string, startTime?: number): number;
started(message: string, rec: Record<string, any>): void;
started(message: string, startTimeOrRec?: number | Record<string, any>): number | void {
if (startTimeOrRec === undefined || typeof startTimeOrRec === 'number') {
return this.log(message, LogType.STARTED, '>', startTimeOrRec);
} else {
this.logWithData(message, LogType.STARTED, startTimeOrRec);
}
}
// Method overloads for finished
finished(message: string, startTime?: number): number;
finished(message: string, rec: Record<string, any>): void;
finished(message: string, startTimeOrRec?: number | Record<string, any>): number | void {
if (startTimeOrRec === undefined || typeof startTimeOrRec === 'number') {
return this.log(message, LogType.FINISHED, '✓', startTimeOrRec);
} else {
this.logWithData(message, LogType.FINISHED, startTimeOrRec);
}
}
// Method overloads for success
success(message: string, startTime?: number): number;
success(message: string, rec: Record<string, any>): void;
success(message: string, startTimeOrRec?: number | Record<string, any>): number | void {
if (startTimeOrRec === undefined || typeof startTimeOrRec === 'number') {
return this.log(message, LogType.SUCCESS, '+', startTimeOrRec);
} else {
this.logWithData(message, LogType.SUCCESS, startTimeOrRec);
}
}
// Method overloads for failed
failed(message: string, startTime?: number): number;
failed(message: string, rec: Record<string, any>): void;
failed(message: string, startTimeOrRec?: number | Record<string, any>): number | void {
if (startTimeOrRec === undefined || typeof startTimeOrRec === 'number') {
return this.log(message, LogType.FAILED, 'x', startTimeOrRec);
} else {
this.logWithData(message, LogType.FAILED, startTimeOrRec);
}
}
// Method overloads for info
info(message: string, startTime?: number): number;
info(message: string, rec: Record<string, any>): void;
info(message: string, startTimeOrRec?: number | Record<string, any>): number | void {
if (startTimeOrRec === undefined || typeof startTimeOrRec === 'number') {
return this.log(message, LogType.INFO, 'i', startTimeOrRec);
} else {
this.logWithData(message, LogType.INFO, startTimeOrRec);
}
}
// Method overloads for warn
warn(message: string, startTime?: number): number;
warn(message: string, rec: Record<string, any>): void;
warn(message: string, startTimeOrRec?: number | Record<string, any>): number | void {
if (startTimeOrRec === undefined || typeof startTimeOrRec === 'number') {
return this.log(message, LogType.WARN, '', startTimeOrRec);
} else {
this.logWithData(message, LogType.WARN, startTimeOrRec);
}
}
// Method overloads for debug
debug(message: string, startTime?: number): number;
debug(message: string, rec: Record<string, any>): void;
debug(message: string, startTimeOrRec?: number | Record<string, any>): number | void {
if (startTimeOrRec === undefined || typeof startTimeOrRec === 'number') {
return this.log(message, LogType.DEBUG, '', startTimeOrRec);
} else {
this.logWithData(message, LogType.DEBUG, startTimeOrRec);
}
}
error(message: string, err: unknown): void {
let error = err instanceof Error ? err : new Error(String(err))
let msg = `${message}: ${error.message}`;
let rec = {"error": error};
this.logWithData(msg, LogType.FAILED, rec);
}
}
export { WingLog as Logger };