deleted a lot of the stuff
This commit is contained in:
parent
d22f7aafa0
commit
3e451558ac
173 changed files with 1313 additions and 30205 deletions
|
|
@ -1,23 +1,123 @@
|
|||
/**
|
||||
* Enhanced logger with Loki integration for Stock Bot platform
|
||||
* Enhanced Pino-based logger with Loki integration for Stock Bot platform
|
||||
*
|
||||
* Features:
|
||||
* - Multiple log levels (debug, info, warn, error)
|
||||
* - Console and file logging
|
||||
* - High performance JSON logging with Pino
|
||||
* - Multiple log levels (debug, info, warn, error, http, verbose, silly)
|
||||
* - Console and file logging with pino-pretty formatting
|
||||
* - Loki integration for centralized logging
|
||||
* - Structured logging with metadata
|
||||
* - Performance optimized with batching
|
||||
* - Flexible message handling (string or object)
|
||||
* - Service-specific context
|
||||
* - TypeScript-friendly interfaces
|
||||
*/
|
||||
|
||||
import winston from 'winston';
|
||||
import LokiTransport from 'winston-loki';
|
||||
import DailyRotateFile from 'winston-daily-rotate-file';
|
||||
import pino from 'pino';
|
||||
import { loggingConfig, lokiConfig } from '@stock-bot/config';
|
||||
import type { LogLevel, LogContext, LogMetadata } from './types';
|
||||
|
||||
// Global logger instances cache
|
||||
const loggerInstances = new Map<string, winston.Logger>();
|
||||
const loggerInstances = new Map<string, pino.Logger>();
|
||||
|
||||
// Pino log level mapping from string to number
|
||||
const PINO_LEVELS: Record<LogLevel, number> = {
|
||||
silly: 10,
|
||||
debug: 20,
|
||||
verbose: 25,
|
||||
http: 30,
|
||||
info: 30,
|
||||
warn: 40,
|
||||
error: 50
|
||||
};
|
||||
|
||||
/**
|
||||
* Create transport configuration for Pino based on options
|
||||
*/
|
||||
function createTransports(serviceName: string, options?: {
|
||||
enableConsole?: boolean;
|
||||
enableFile?: boolean;
|
||||
enableLoki?: boolean;
|
||||
}): any {
|
||||
const {
|
||||
enableConsole = loggingConfig.LOG_CONSOLE,
|
||||
enableFile = loggingConfig.LOG_FILE,
|
||||
enableLoki = true
|
||||
} = options || {};
|
||||
|
||||
const targets: any[] = [];
|
||||
|
||||
// Console transport with pretty formatting
|
||||
if (enableConsole) {
|
||||
targets.push({
|
||||
target: 'pino-pretty',
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'yyyy-mm-dd HH:MM:ss.l',
|
||||
ignore: 'pid,hostname',
|
||||
messageFormat: '[{service}] {msg}',
|
||||
customPrettifiers: {
|
||||
service: (service: string) => `${service}`,
|
||||
level: (level: string) => `[${level.toUpperCase()}]`
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// File transport for general logs
|
||||
if (enableFile) {
|
||||
targets.push({
|
||||
target: 'pino/file',
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
options: {
|
||||
destination: `${loggingConfig.LOG_FILE_PATH}/${serviceName}.log`,
|
||||
mkdir: true
|
||||
}
|
||||
});
|
||||
|
||||
// Separate error file if enabled
|
||||
if (loggingConfig.LOG_ERROR_FILE) {
|
||||
targets.push({
|
||||
target: 'pino/file',
|
||||
level: 'error',
|
||||
options: {
|
||||
destination: `${loggingConfig.LOG_FILE_PATH}/${serviceName}-error.log`,
|
||||
mkdir: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Loki transport for centralized logging
|
||||
if (enableLoki && lokiConfig.LOKI_HOST) {
|
||||
targets.push({
|
||||
target: 'pino-loki',
|
||||
level: loggingConfig.LOG_LEVEL,
|
||||
options: {
|
||||
batching: true,
|
||||
interval: lokiConfig.LOKI_FLUSH_INTERVAL_MS,
|
||||
host: lokiConfig.LOKI_URL || `http://${lokiConfig.LOKI_HOST}:${lokiConfig.LOKI_PORT}`,
|
||||
basicAuth: lokiConfig.LOKI_USERNAME && lokiConfig.LOKI_PASSWORD
|
||||
? {
|
||||
username: lokiConfig.LOKI_USERNAME,
|
||||
password: lokiConfig.LOKI_PASSWORD
|
||||
}
|
||||
: undefined,
|
||||
labels: {
|
||||
service: serviceName,
|
||||
environment: lokiConfig.LOKI_ENVIRONMENT_LABEL,
|
||||
...(lokiConfig.LOKI_DEFAULT_LABELS ? JSON.parse(lokiConfig.LOKI_DEFAULT_LABELS) : {})
|
||||
},
|
||||
timeout: lokiConfig.LOKI_PUSH_TIMEOUT || 10000,
|
||||
silenceErrors: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
targets
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or retrieve a logger instance for a specific service
|
||||
|
|
@ -27,7 +127,7 @@ export function createLogger(serviceName: string, options?: {
|
|||
enableLoki?: boolean;
|
||||
enableFile?: boolean;
|
||||
enableConsole?: boolean;
|
||||
}): winston.Logger {
|
||||
}): pino.Logger {
|
||||
const key = `${serviceName}-${JSON.stringify(options || {})}`;
|
||||
|
||||
if (loggerInstances.has(key)) {
|
||||
|
|
@ -41,124 +141,52 @@ export function createLogger(serviceName: string, options?: {
|
|||
}
|
||||
|
||||
/**
|
||||
* Build a winston logger with all configured transports
|
||||
* Build a Pino logger with all configured transports
|
||||
*/
|
||||
function buildLogger(serviceName: string, options?: {
|
||||
level?: LogLevel;
|
||||
enableLoki?: boolean;
|
||||
enableFile?: boolean;
|
||||
enableConsole?: boolean;
|
||||
}): winston.Logger {
|
||||
}): pino.Logger {
|
||||
const {
|
||||
level = loggingConfig.LOG_LEVEL as LogLevel,
|
||||
enableLoki = true,
|
||||
enableFile = loggingConfig.LOG_FILE,
|
||||
enableConsole = loggingConfig.LOG_CONSOLE
|
||||
} = options || {}; // Base logger configuration
|
||||
const transports: winston.transport[] = [];
|
||||
} = options || {};
|
||||
|
||||
// Console transport
|
||||
if (enableConsole) {
|
||||
transports.push(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple(),
|
||||
winston.format.printf(({ timestamp, level, service, message, metadata }) => {
|
||||
const meta = metadata && Object.keys(metadata).length > 0
|
||||
? `\n${JSON.stringify(metadata, null, 2)}`
|
||||
: '';
|
||||
return `${timestamp} [${level}] [${service}] ${message}${meta}`;
|
||||
})
|
||||
)
|
||||
}));
|
||||
}
|
||||
const transport = createTransports(serviceName, {
|
||||
enableConsole,
|
||||
enableFile,
|
||||
enableLoki
|
||||
});
|
||||
|
||||
// File transport with daily rotation
|
||||
if (enableFile) {
|
||||
// General log file
|
||||
transports.push(new DailyRotateFile({
|
||||
filename: `${loggingConfig.LOG_FILE_PATH}/${serviceName}-%DATE%.log`,
|
||||
datePattern: loggingConfig.LOG_FILE_DATE_PATTERN,
|
||||
zippedArchive: true,
|
||||
maxSize: loggingConfig.LOG_FILE_MAX_SIZE,
|
||||
maxFiles: loggingConfig.LOG_FILE_MAX_FILES,
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
)
|
||||
}));
|
||||
|
||||
// Separate error log file
|
||||
if (loggingConfig.LOG_ERROR_FILE) {
|
||||
transports.push(new DailyRotateFile({
|
||||
level: 'error',
|
||||
filename: `${loggingConfig.LOG_FILE_PATH}/${serviceName}-error-%DATE%.log`,
|
||||
datePattern: loggingConfig.LOG_FILE_DATE_PATTERN,
|
||||
zippedArchive: true,
|
||||
maxSize: loggingConfig.LOG_FILE_MAX_SIZE,
|
||||
maxFiles: loggingConfig.LOG_FILE_MAX_FILES,
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Loki transport for centralized logging
|
||||
if (enableLoki && lokiConfig.LOKI_HOST) {
|
||||
try {
|
||||
const lokiTransport = new LokiTransport({
|
||||
host: lokiConfig.LOKI_URL || `http://${lokiConfig.LOKI_HOST}:${lokiConfig.LOKI_PORT}`,
|
||||
labels: {
|
||||
service: serviceName,
|
||||
environment: lokiConfig.LOKI_ENVIRONMENT_LABEL,
|
||||
...(lokiConfig.LOKI_DEFAULT_LABELS ? JSON.parse(lokiConfig.LOKI_DEFAULT_LABELS) : {})
|
||||
},
|
||||
json: true,
|
||||
batching: true,
|
||||
interval: lokiConfig.LOKI_FLUSH_INTERVAL_MS,
|
||||
timeout: lokiConfig.LOKI_PUSH_TIMEOUT,
|
||||
basicAuth: lokiConfig.LOKI_USERNAME && lokiConfig.LOKI_PASSWORD
|
||||
? `${lokiConfig.LOKI_USERNAME}:${lokiConfig.LOKI_PASSWORD}`
|
||||
: undefined,
|
||||
onConnectionError: (err) => {
|
||||
console.error('Loki connection error:', err);
|
||||
}
|
||||
});
|
||||
|
||||
transports.push(lokiTransport);
|
||||
} catch (error) {
|
||||
console.warn('Failed to initialize Loki transport:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const loggerConfig: winston.LoggerOptions = {
|
||||
level,
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.metadata({
|
||||
fillExcept: ['message', 'level', 'timestamp', 'service']
|
||||
}),
|
||||
winston.format.json()
|
||||
),
|
||||
defaultMeta: {
|
||||
const loggerConfig: pino.LoggerOptions = {
|
||||
level: PINO_LEVELS[level] ? level : 'info',
|
||||
customLevels: PINO_LEVELS,
|
||||
useOnlyCustomLevels: false,
|
||||
timestamp: () => `,"timestamp":"${new Date().toISOString()}"`,
|
||||
formatters: {
|
||||
level: (label: string) => ({ level: label }),
|
||||
bindings: () => ({})
|
||||
},
|
||||
base: {
|
||||
service: serviceName,
|
||||
environment: loggingConfig.LOG_ENVIRONMENT,
|
||||
version: loggingConfig.LOG_SERVICE_VERSION
|
||||
},
|
||||
transports
|
||||
transport
|
||||
};
|
||||
|
||||
return winston.createLogger(loggerConfig);
|
||||
return pino(loggerConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced Logger class with convenience methods
|
||||
* Enhanced Logger class with convenience methods and flexible message handling
|
||||
*/
|
||||
export class Logger {
|
||||
private winston: winston.Logger;
|
||||
private pino: pino.Logger;
|
||||
private serviceName: string;
|
||||
private context: LogContext;
|
||||
|
||||
|
|
@ -170,34 +198,90 @@ export class Logger {
|
|||
}) {
|
||||
this.serviceName = serviceName;
|
||||
this.context = context;
|
||||
this.winston = createLogger(serviceName, options);
|
||||
this.pino = createLogger(serviceName, options);
|
||||
}
|
||||
/**
|
||||
* Flexible log method that accepts string or object messages
|
||||
*/
|
||||
log(level: LogLevel, message: string | object, metadata?: LogMetadata): void {
|
||||
const logData = {
|
||||
...this.context,
|
||||
...metadata,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Map custom log levels to Pino levels
|
||||
const pinoLevel = this.mapToPinoLevel(level);
|
||||
|
||||
if (typeof message === 'string') {
|
||||
(this.pino as any)[pinoLevel](logData, message);
|
||||
} else {
|
||||
(this.pino as any)[pinoLevel]({ ...logData, ...message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map custom log levels to Pino levels
|
||||
*/
|
||||
private mapToPinoLevel(level: LogLevel): string {
|
||||
switch (level) {
|
||||
case 'silly':
|
||||
case 'verbose':
|
||||
case 'debug':
|
||||
return 'debug';
|
||||
case 'http':
|
||||
case 'info':
|
||||
return 'info';
|
||||
case 'warn':
|
||||
return 'warn';
|
||||
case 'error':
|
||||
return 'error';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug level logging
|
||||
*/
|
||||
debug(message: string, metadata?: LogMetadata): void {
|
||||
debug(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('debug', message, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbose level logging
|
||||
*/
|
||||
verbose(message: string | object, metadata?: LogMetadata): void {
|
||||
// Map verbose to debug level since Pino doesn't have verbose by default
|
||||
this.log('debug', message, { ...metadata, originalLevel: 'verbose' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Silly level logging
|
||||
*/
|
||||
silly(message: string | object, metadata?: LogMetadata): void {
|
||||
// Map silly to debug level since Pino doesn't have silly by default
|
||||
this.log('debug', message, { ...metadata, originalLevel: 'silly' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Info level logging
|
||||
*/
|
||||
info(message: string, metadata?: LogMetadata): void {
|
||||
info(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('info', message, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning level logging
|
||||
*/
|
||||
warn(message: string, metadata?: LogMetadata): void {
|
||||
warn(message: string | object, metadata?: LogMetadata): void {
|
||||
this.log('warn', message, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error level logging
|
||||
*/
|
||||
error(message: string, error?: Error | any, metadata?: LogMetadata): void {
|
||||
error(message: string | object, error?: Error | any, metadata?: LogMetadata): void {
|
||||
const logData: LogMetadata = { ...metadata };
|
||||
|
||||
if (error) {
|
||||
|
|
@ -218,7 +302,7 @@ export class Logger {
|
|||
/**
|
||||
* HTTP request logging
|
||||
*/
|
||||
http(message: string, requestData?: {
|
||||
http(message: string | object, requestData?: {
|
||||
method?: string;
|
||||
url?: string;
|
||||
statusCode?: number;
|
||||
|
|
@ -228,7 +312,7 @@ export class Logger {
|
|||
}): void {
|
||||
if (!loggingConfig.LOG_HTTP_REQUESTS) return;
|
||||
|
||||
this.log('http', message, {
|
||||
this.log('info', message, {
|
||||
request: requestData,
|
||||
type: 'http_request'
|
||||
});
|
||||
|
|
@ -237,7 +321,7 @@ export class Logger {
|
|||
/**
|
||||
* Performance/timing logging
|
||||
*/
|
||||
performance(message: string, timing: {
|
||||
performance(message: string | object, timing: {
|
||||
operation: string;
|
||||
duration: number;
|
||||
startTime?: number;
|
||||
|
|
@ -254,7 +338,7 @@ export class Logger {
|
|||
/**
|
||||
* Business event logging
|
||||
*/
|
||||
business(message: string, event: {
|
||||
business(message: string | object, event: {
|
||||
type: string;
|
||||
entity?: string;
|
||||
action?: string;
|
||||
|
|
@ -271,7 +355,7 @@ export class Logger {
|
|||
/**
|
||||
* Security event logging
|
||||
*/
|
||||
security(message: string, event: {
|
||||
security(message: string | object, event: {
|
||||
type: 'authentication' | 'authorization' | 'access' | 'vulnerability';
|
||||
user?: string;
|
||||
resource?: string;
|
||||
|
|
@ -301,33 +385,27 @@ export class Logger {
|
|||
}
|
||||
|
||||
/**
|
||||
* Internal logging method
|
||||
* Get the underlying Pino logger
|
||||
*/
|
||||
private log(level: LogLevel, message: string, metadata?: LogMetadata): void {
|
||||
const logData = {
|
||||
...this.context,
|
||||
...metadata,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
this.winston.log(level, message, logData);
|
||||
getPinoLogger(): pino.Logger {
|
||||
return this.pino;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying winston logger
|
||||
* Compatibility method for existing code that expects Winston logger
|
||||
*/
|
||||
getWinstonLogger(): winston.Logger {
|
||||
return this.winston;
|
||||
getWinstonLogger(): any {
|
||||
console.warn('getWinstonLogger() is deprecated. Use getPinoLogger() instead.');
|
||||
return this.pino;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully close all transports
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
this.winston.end(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
// Pino doesn't require explicit closing like Winston
|
||||
// But we can flush any pending logs
|
||||
this.pino.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,15 +420,13 @@ export function getLogger(serviceName: string, context?: LogContext): Logger {
|
|||
* Shutdown all logger instances gracefully
|
||||
*/
|
||||
export async function shutdownLoggers(): Promise<void> {
|
||||
const closePromises = Array.from(loggerInstances.values()).map(logger =>
|
||||
new Promise<void>((resolve) => {
|
||||
logger.end(() => {
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
);
|
||||
// Flush all logger instances
|
||||
const flushPromises = Array.from(loggerInstances.values()).map(logger => {
|
||||
logger.flush();
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
await Promise.all(closePromises);
|
||||
await Promise.all(flushPromises);
|
||||
loggerInstances.clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue