StreamHandler.emit() writes formatted output to a Writable stream. ConsoleHandler maps log levels to console.warn/error/log. StderrHandler emits via console.error. handleError() logs diagnostics instead of throwing. FileHandler throws NotImplementedError on construction. Removed Node.js stream import and conditional require() block.
239 lines
6.8 KiB
TypeScript
239 lines
6.8 KiB
TypeScript
import { LogLevel, checkLevel, NOTSET, WARNING, ERROR } from './log-level';
|
|
import { LogRecord } from './log-record';
|
|
import { Formatter, DEFAULT_FORMATTER } from './formatter';
|
|
import { Filterer } from './filter';
|
|
import { NotImplementedError } from './helper/error';
|
|
import { Writable, DEFAULT_STREAM, StderrWritable } from './helper/stream';
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Handler classes and functions
|
|
//----------------------------------------------------------------------------
|
|
|
|
type Handlers = {[key: string]: Handler};
|
|
|
|
/**
|
|
* map of handler names to handlers
|
|
*/
|
|
const HANDLERS: Handlers = {};
|
|
|
|
/**
|
|
* added to allow handlers to be removed in reverse order of initialization
|
|
*/
|
|
const HANDLER_LIST: WeakRef<Handler>[] = [];
|
|
|
|
/**
|
|
* Add a handler to the internal cleanup list using a weak reference.
|
|
*
|
|
* @param handler -
|
|
*/
|
|
function addHandlerRef(handler: Handler) {
|
|
HANDLER_LIST.push(new WeakRef(handler));
|
|
}
|
|
|
|
/**
|
|
* Get a handler with the specified *name*, or None if there isn't one with
|
|
* that name.
|
|
*/
|
|
export function getHandlerByName(name: string): Handler|null {
|
|
return HANDLERS[name] ?? null
|
|
}
|
|
|
|
/**
|
|
* Return all known handler names as an immutable set
|
|
*/
|
|
export function getHandlerNames(): Handlers { return Object.freeze(HANDLERS) }
|
|
|
|
/**
|
|
* Handler instances dispatch logging events to specific destinations.
|
|
*
|
|
* The base handler class. Acts as a placeholder which defines the Handler
|
|
* interface. Handlers can optionally use Formatter instances to format
|
|
* records as desired. By default, no formatter is specified; in this case,
|
|
* the 'raw' message as determined by record.message is logged.
|
|
*/
|
|
export class Handler extends Filterer {
|
|
|
|
protected _scope: string|null = null;
|
|
protected _formatter: Formatter|null = null;
|
|
protected _level: number;
|
|
protected _closed: boolean = false;
|
|
|
|
/**
|
|
* Initializes the instance - basically setting the formatter to None
|
|
* and the filter list to empty
|
|
*/
|
|
constructor(level?: LogLevel) {
|
|
super();
|
|
this._level = checkLevel(level ?? NOTSET);
|
|
// Add the handler to the global HANDLER_LIST (for cleanup on shutdown)
|
|
addHandlerRef(this);
|
|
}
|
|
|
|
get level(): number { return this._level }
|
|
set level(level: LogLevel|string) { this._level = checkLevel(level) }
|
|
|
|
get scope(): string|null { return this._scope }
|
|
set scope(scope: string) { this._scope = scope }
|
|
get closed(): boolean { return this._closed }
|
|
|
|
/**
|
|
* Format the specified record.
|
|
*
|
|
* If a formatter is set, use it. Otherwise, use the default formatter for
|
|
* the module.
|
|
*/
|
|
format(record: LogRecord): string {
|
|
const fmt = this.formatter ?? DEFAULT_FORMATTER;
|
|
return fmt.format(record);
|
|
}
|
|
|
|
/**
|
|
* Do whatever it takes to actually log the specified logging record.
|
|
*
|
|
* This version is intended to be implemented by subclasses and so raises a
|
|
* NotImplementedError.
|
|
*/
|
|
emit(record: LogRecord) {
|
|
throw new NotImplementedError(
|
|
'emit must be implemented by Handler subclass'
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Conditionally emit the specfied logging record.
|
|
*
|
|
* Emission depends on filters which may have been added to the handler.
|
|
* Wrap the actual emission of the record with acquisition/release of the
|
|
* I/O thread lock.
|
|
*/
|
|
handle(record: LogRecord) {
|
|
const rv = this.filter(record);
|
|
if (!rv) { return }
|
|
let filtered = record;
|
|
if ((rv as any) instanceof LogRecord) {
|
|
filtered = rv as unknown as LogRecord
|
|
}
|
|
this.emit(filtered)
|
|
}
|
|
|
|
/**
|
|
* Tidy up any resources used by the handler
|
|
*
|
|
* This version removes the handler from an internal map of handlers, which
|
|
* is used for handler lookup by scope. Subclasses should ensure that this
|
|
* gets called from overriden close() methods.
|
|
*/
|
|
close() {
|
|
this._closed = true;
|
|
|
|
if (this.scope && Object.keys(HANDLERS).includes(this.scope)) {
|
|
delete HANDLERS[this.scope]
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle errors which occur during an emit() call.
|
|
*
|
|
* This method should be called from handlers when an exception is
|
|
* encountered during an emit() call.
|
|
*/
|
|
handleError(record: LogRecord) {
|
|
try {
|
|
console.error('--- Logging error ---');
|
|
console.error('Error in handler for record:', record.scope, record.msg);
|
|
}
|
|
catch (_) {
|
|
// silently ignore errors in error handling
|
|
}
|
|
}
|
|
|
|
get formatter(): Formatter|null { return this._formatter }
|
|
set formatter(fmt: Formatter) { this._formatter = fmt }
|
|
}
|
|
|
|
/**
|
|
* A handler class which writes logging records, appropriately formatted,
|
|
* to a stream. Note that this class does not close the stream, as
|
|
* the default stream may be shared.
|
|
*/
|
|
export class StreamHandler extends Handler {
|
|
protected stream: Writable;
|
|
|
|
constructor(stream?: Writable) {
|
|
super();
|
|
this.stream = stream ?? DEFAULT_STREAM;
|
|
}
|
|
|
|
emit(record: LogRecord) {
|
|
try {
|
|
const msg = this.format(record);
|
|
this.stream.write(msg);
|
|
}
|
|
catch (e) {
|
|
this.handleError(record);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A handler class which writes logging records to the browser console,
|
|
* mapping log levels to the appropriate console method.
|
|
*
|
|
* This is the primary handler for browser environments.
|
|
*/
|
|
export class ConsoleHandler extends Handler {
|
|
emit(record: LogRecord) {
|
|
try {
|
|
const msg = this.format(record);
|
|
|
|
if (record.levelno >= ERROR) {
|
|
console.error(msg);
|
|
}
|
|
else if (record.levelno >= WARNING) {
|
|
console.warn(msg);
|
|
}
|
|
else {
|
|
console.log(msg);
|
|
}
|
|
}
|
|
catch (e) {
|
|
this.handleError(record);
|
|
}
|
|
}
|
|
}
|
|
|
|
export interface FileHandlerOptions {
|
|
filename: string
|
|
filemode?: string
|
|
encoding?: string
|
|
errors?: string
|
|
}
|
|
|
|
export class FileHandler extends StreamHandler {
|
|
constructor(options: FileHandlerOptions) {
|
|
super();
|
|
throw new NotImplementedError(
|
|
'FileHandler is not available in browser environments. ' +
|
|
'Use ConsoleHandler or a storage-backed handler instead.'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class is like a StreamHandler using sys.stderr, but always uses
|
|
* whatever sys.stderr is currently set to rather than the value of
|
|
* sys.stderr at handler construction time.
|
|
*/
|
|
export class StderrHandler extends Handler {
|
|
constructor(level: LogLevel) { super(level) }
|
|
|
|
emit(record: LogRecord) {
|
|
try {
|
|
const msg = this.format(record);
|
|
console.error(msg);
|
|
}
|
|
catch (e) {
|
|
this.handleError(record);
|
|
}
|
|
}
|
|
}
|