CommunitySolidServer/src/logging/LazyLoggerFactory.ts
2022-05-19 14:13:57 +02:00

94 lines
3.3 KiB
TypeScript

import cluster from 'cluster';
import { WrappingLogger } from './Logger';
import type { Logger } from './Logger';
import type { LoggerFactory } from './LoggerFactory';
import type { LogLevel } from './LogLevel';
/**
* Temporary {@link LoggerFactory} that buffers log messages in memory
* until the {@link TemporaryLoggerFactory#switch} method is called.
*/
class TemporaryLoggerFactory implements LoggerFactory {
private bufferSpaces: number;
private readonly wrappers: { wrapper: WrappingLogger; label: string }[] = [];
private readonly buffer: { logger: Logger; level: LogLevel; message: string }[] = [];
public constructor(bufferSize = 1024) {
this.bufferSpaces = bufferSize;
}
public createLogger(label: string): WrappingLogger {
const wrapper = new WrappingLogger({
log: (level: LogLevel, message: string): Logger =>
this.bufferLogEntry(wrapper, level, message),
});
this.wrappers.push({ wrapper, label });
return wrapper;
}
private bufferLogEntry(logger: WrappingLogger, level: LogLevel, message: string): Logger {
// Buffer the message if spaces are still available
if (this.bufferSpaces > 0) {
this.bufferSpaces -= 1;
// If this is the last space, instead generate a warning through a new logger
if (this.bufferSpaces === 0) {
logger = this.createLogger('LazyLoggerFactory');
level = 'warn';
message = `Memory-buffered logging limit of ${this.buffer.length + 1} reached`;
}
this.buffer.push({ logger, level, message });
}
return logger;
}
/**
* Swaps all lazy loggers to new loggers from the given factory,
* and emits any buffered messages through those actual loggers.
*/
public switch(loggerFactory: LoggerFactory): void {
// Instantiate an actual logger within every lazy logger
for (const { wrapper, label } of this.wrappers.splice(0, this.wrappers.length)) {
wrapper.logger = loggerFactory.createLogger(label);
}
// Emit all buffered log messages
for (const { logger, level, message } of this.buffer.splice(0, this.buffer.length)) {
logger.log(level, message, { isPrimary: cluster.isMaster, pid: process.pid });
}
}
}
/**
* Wraps around another {@link LoggerFactory} that can be set lazily.
* This is useful when objects are instantiated (and when they create loggers)
* before the logging system has been fully instantiated,
* as is the case when using a dependency injection framework such as Components.js.
*
* Loggers can be created even before a {@link LoggerFactory} is set;
* any log messages will be buffered and re-emitted.
*/
export class LazyLoggerFactory implements LoggerFactory {
private factory: LoggerFactory;
public constructor(options: { bufferSize?: number } = {}) {
this.factory = new TemporaryLoggerFactory(options.bufferSize);
}
public get loggerFactory(): LoggerFactory {
if (this.factory instanceof TemporaryLoggerFactory) {
throw new Error('Logger factory not yet set.');
}
return this.factory;
}
public set loggerFactory(loggerFactory: LoggerFactory) {
if (this.factory instanceof TemporaryLoggerFactory) {
this.factory.switch(loggerFactory);
}
this.factory = loggerFactory;
}
public createLogger(label: string): Logger {
return this.factory.createLogger(label);
}
}