This commit is contained in:
Boki 2025-06-20 21:04:09 -04:00
parent caf1c5fcaf
commit 20b7180a43
5 changed files with 207 additions and 158 deletions

View file

@ -9,6 +9,7 @@
*/
import pino from 'pino';
import pretty from 'pino-pretty';
import type { LogContext, LoggerConfig, LogLevel, LogMetadata } from './types';
// Simple cache for logger instances
@ -35,68 +36,73 @@ export function setLoggerConfig(config: LoggerConfig): void {
console.log('Logger config updated:', globalConfig.logLevel);
}
/**
* Create transport configuration
* Create logger destination using multistream approach:
* - Console: In-process pretty stream (fast shutdown, disabled in production)
* - File/Loki: Worker transports (default timeout, ok to wait)
*/
function createTransports(serviceName: string, config: LoggerConfig = globalConfig): any {
const targets: any[] = [];
function createDestination(
serviceName: string,
config: LoggerConfig = globalConfig
): pino.DestinationStream | null {
const streams: pino.StreamEntry[] = [];
// Console transport
if (config.logConsole) {
targets.push({
target: 'pino-pretty',
level: config.logLevel || 'info',
options: {
colorize: true,
translateTime: 'yyyy-mm-dd HH:MM:ss.l',
messageFormat: '[{service}{childName}] {msg}',
singleLine: true,
hideObject: false,
ignore: 'pid,hostname,service,environment,version,childName',
errorLikeObjectKeys: ['err', 'error'],
errorProps: 'message,stack,name,code',
// Ensure the transport respects the configured level
minimumLevel: config.logLevel || 'info',
},
// Console: In-process pretty stream for dev (fast shutdown)
if (config.logConsole && config.environment !== 'production') {
const prettyStream = pretty({
sync: true,
colorize: true,
translateTime: 'yyyy-mm-dd HH:MM:ss.l',
messageFormat: '[{service}{childName}] {msg}',
singleLine: true,
hideObject: false,
ignore: 'pid,hostname,service,environment,version,childName',
errorLikeObjectKeys: ['err', 'error'],
errorProps: 'message,stack,name,code',
});
streams.push({ stream: prettyStream });
}
// File transport
// File: Worker transport (has timeout but acceptable)
if (config.logFile) {
targets.push({
target: 'pino/file',
level: config.logLevel || 'info',
options: {
destination: `${config.logFilePath}/${serviceName}.log`,
mkdir: true,
},
});
}
// Loki transport
if (config.logLoki && config.lokiHost) {
targets.push({
target: 'pino-loki',
level: config.logLevel || 'info',
options: {
host: config.lokiHost,
labels: {
service: serviceName,
environment: config.environment || 'development',
streams.push(
pino.transport({
target: 'pino/file',
level: config.logLevel || 'info',
options: {
destination: `${config.logFilePath}/${serviceName}.log`,
mkdir: true,
},
ignore: 'childName',
...(config.lokiUser && config.lokiPassword
? {
basicAuth: {
username: config.lokiUser,
password: config.lokiPassword,
},
}
: {}),
},
});
})
);
}
return targets.length > 0 ? { targets } : null;
// Loki: Worker transport (has timeout but acceptable)
if (config.logLoki && config.lokiHost) {
streams.push(
pino.transport({
target: 'pino-loki',
level: config.logLevel || 'info',
options: {
host: config.lokiHost,
labels: {
service: serviceName,
environment: config.environment || 'development',
},
ignore: 'childName',
...(config.lokiUser && config.lokiPassword
? {
basicAuth: {
username: config.lokiUser,
password: config.lokiPassword,
},
}
: {}),
},
})
);
}
return streams.length > 0 ? pino.multistream(streams) : null;
}
/**
@ -105,7 +111,7 @@ function createTransports(serviceName: string, config: LoggerConfig = globalConf
function getPinoLogger(serviceName: string, config: LoggerConfig = globalConfig): pino.Logger {
const cacheKey = `${serviceName}-${JSON.stringify(config)}`;
if (!loggerCache.has(cacheKey)) {
const transport = createTransports(serviceName, config);
const destination = createDestination(serviceName, config);
const loggerOptions: pino.LoggerOptions = {
level: config.logLevel || 'info',
@ -116,11 +122,8 @@ function getPinoLogger(serviceName: string, config: LoggerConfig = globalConfig)
},
};
if (transport) {
loggerOptions.transport = transport;
}
const logger = destination ? pino(loggerOptions, destination) : pino(loggerOptions);
const logger = pino(loggerOptions);
loggerCache.set(cacheKey, logger);
}
@ -356,5 +359,35 @@ export async function shutdownLoggers(): Promise<void> {
}
}
/**
* Graceful shutdown - flush all logger transports quickly
* Use this in your application shutdown handlers
*/
export async function gracefulShutdown(): Promise<void> {
const flushPromises: Promise<void>[] = [];
for (const logger of loggerCache.values()) {
// Use pino v9's flush() method - this is much faster than the complex shutdown
flushPromises.push(
new Promise<void>((resolve, reject) => {
logger.flush((err?: Error) => {
if (err) {
reject(err);
} else {
resolve();
}
});
})
);
}
try {
await Promise.all(flushPromises);
} catch (error) {
// eslint-disable-next-line no-console
console.error('Logger graceful shutdown failed:', error);
}
}
// Export types for convenience
export type { LogContext, LogLevel, LogMetadata } from './types';