Skip to content

Commit

Permalink
feat(logging): finish Formatter implementation (#3337)
Browse files Browse the repository at this point in the history
Refs #3197
  • Loading branch information
char0n authored Oct 30, 2023
1 parent a4e2357 commit 8993d69
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 3 deletions.
95 changes: 94 additions & 1 deletion packages/apidom-logging/src/Formatter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ApiDOMStructuredError } from '@swagger-api/apidom-error';

import STYLES, { Style } from './styles';
import type { LogRecordInstance } from './LogRecord';

export interface FormatterOptions {
readonly fmt?: Style['fmt'];
Expand All @@ -10,11 +11,60 @@ export interface FormatterOptions {
readonly defaults?: Record<string, unknown>;
}

class Formatter {
export interface FormatterInstance {
format(record: LogRecordInstance): string;
}

export interface FormatterConstructor {
new (options: FormatterOptions): FormatterInstance;
}

const momentToIntlFormat = (momentFormat: string): Intl.DateTimeFormatOptions => {
if (momentToIntlFormat.cache.has(momentFormat)) {
return momentToIntlFormat.cache.get(momentFormat)!;
}

const mapping = {
YYYY: { year: 'numeric' },
YY: { year: '2-digit' },
MMMM: { month: 'long' },
MMM: { month: 'short' },
MM: { month: '2-digit' },
DD: { day: '2-digit' },
dddd: { weekday: 'long' },
ddd: { weekday: 'short' },
HH: { hour: '2-digit', hour12: false },
hh: { hour: '2-digit', hour12: true },
mm: { minute: '2-digit' },
ss: { second: '2-digit' },
A: { hour12: true },
z: { timeZoneName: 'short' }, // abbreviated time zone name
Z: { timeZoneName: 'short' }, // offset from GMT
};
type MomentToken = keyof typeof mapping;

const intlOptions: Intl.DateTimeFormatOptions = Object.keys(mapping).reduce((opts, token) => {
if (momentFormat.includes(token)) {
return { ...opts, ...mapping[token as MomentToken] };
}
return opts;
}, {});

momentToIntlFormat.cache.set(momentFormat, intlOptions);

return intlOptions;
};
momentToIntlFormat.cache = new Map<string, Intl.DateTimeFormatOptions>();

class Formatter implements FormatterInstance {
protected readonly style: Style;

protected readonly datefmt?: string;

protected readonly defaultDateTimeFormat!: 'DD MM YYYY hh:mm:ss';

protected readonly appendMsecInfo = true;

constructor(options: FormatterOptions = {}) {
const style = options.style ?? '$';

Expand All @@ -37,6 +87,49 @@ class Formatter {

this.datefmt = options.datefmt;
}

protected usesTime(): boolean {
return this.style.usesTime();
}

protected formatTime(record: LogRecordInstance, datefmt?: string): string {
const intlOptions = momentToIntlFormat(datefmt ?? this.defaultDateTimeFormat);
const formattedTime = new Intl.DateTimeFormat(undefined, intlOptions).format(record.created);

if (this.appendMsecInfo) {
return `${formattedTime},${String(record.msecs).padStart(3, '0')}`;
}

return formattedTime;
}

protected formatMessage(record: LogRecordInstance): string {
return this.style.format(record);
}

// eslint-disable-next-line class-methods-use-this
protected formatError<T extends Error>(error: T): string {
return `Error: ${error.message}\nStack: ${error.stack ?? 'No stack available'}`;
}

public format(record: LogRecordInstance) {
if (this.usesTime()) {
record.asctime = this.formatTime(record, this.datefmt); // eslint-disable-line no-param-reassign
}

const formattedMessage = this.formatMessage(record);

if (record.error && typeof record.error_text === 'undefined') {
record.error_text = this.formatError(record.error); // eslint-disable-line no-param-reassign
}

if (record.error_text) {
const separator = formattedMessage.endsWith('\n') ? '' : '\n';
return `${formattedMessage}${separator}${record.error_text}`;
}

return formattedMessage;
}
}

export default Formatter;
13 changes: 11 additions & 2 deletions packages/apidom-logging/src/LogRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ const startTime: number = Date.now();
export interface LogRecordInstance<T extends Error = Error> {
readonly name: string;
readonly message: string;
readonly created: number;
readonly msecs: number;
readonly relativeCreated: number;
asctime?: string;
readonly levelname: string;
readonly levelno: number;
readonly process?: number;
readonly processName?: string;
readonly error?: T;
error_text?: string;
[key: string]: unknown;
}

Expand Down Expand Up @@ -39,12 +44,16 @@ class LogRecord<T extends Error = Error> implements LogRecordInstance<T> {

public readonly relativeCreated: number;

public asctime?: string;

public readonly process?: number;

public readonly processName?: string;

public readonly error?: T;

public error_text?: string;

[key: string]: unknown;

constructor(
Expand All @@ -61,8 +70,8 @@ class LogRecord<T extends Error = Error> implements LogRecordInstance<T> {
this.levelname = getLevelName(level);
this.message = message;
this.error = error;
this.created = Math.floor(created / 1000);
this.msecs = created - this.created * 1000;
this.created = created;
this.msecs = created % 1000;
this.relativeCreated = created - startTime;

if (globalThis.process?.pid) {
Expand Down
1 change: 1 addition & 0 deletions packages/apidom-logging/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const { CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET } = LoggingLevel;
export { getLevelName, getLevelNamesMapping, addLevelName } from './LoggingLevel';
export { getLogRecordClass, setLogRecordClass, default as LogRecord } from './LogRecord';
export { default as Filter } from './Filter';
export { default as Formatter } from './Formatter';

0 comments on commit 8993d69

Please sign in to comment.