packages/libs/common/src/logging/formatter.ts (104 lines of code) (raw):
import { EnhancedPosition } from "@azure-tools/datastore";
import { serializeJsonPointer } from "@azure-tools/json";
import chalk, { level } from "chalk";
import { color } from "../utils";
import { EnhancedLogInfo, EnhancedSourceLocation } from "./types";
import { LogLevel } from ".";
export interface LogFormatter {
log(log: EnhancedLogInfo): string;
}
export interface FormatterOptions {
color?: boolean;
timestamp?: boolean;
}
const defaultOptions = {
color: true,
timestamp: true,
};
export function createLogFormatter(format: "json" | "regular" | undefined, options = {}): LogFormatter {
return format === "json" ? new JsonLogFormatter(options) : new PrettyLogFormatter(options);
}
const LEVEL_STR: Record<LogLevel, string> = {
debug: "debug".padEnd(7),
verbose: "verbose".padEnd(7),
information: "info".padEnd(7),
warning: "warning".padEnd(7),
error: "error".padEnd(7),
fatal: "fatal".padEnd(7),
};
const LEVEL_COLORED_STR: Record<LogLevel, string> = {
debug: chalk.blue(LEVEL_STR.debug),
verbose: chalk.gray(LEVEL_STR.verbose),
information: chalk.green(LEVEL_STR.information),
warning: chalk.yellow.bold(LEVEL_STR.warning),
error: chalk.red.bold(LEVEL_STR.error),
fatal: chalk.redBright.bold(LEVEL_STR.fatal),
};
export class PrettyLogFormatter implements LogFormatter {
private options: { color: boolean; timestamp: boolean };
public constructor(options: FormatterOptions = {}) {
this.options = { ...defaultOptions, ...options };
}
public log(log: EnhancedLogInfo): string {
const useColor = this.options.color;
const t = this.formatTimestamp(log.level);
const level = useColor ? LEVEL_COLORED_STR[log.level] : LEVEL_STR[log.level];
const message = useColor ? color(log.message) : log.message;
let text = `${level} |${this.formatCode(log.code)}${t} ${message}`;
for (const source of log.source ?? []) {
text += this.formatSource(source);
}
return text;
}
private formatCode(code: string | undefined): string {
if (!code) {
return "";
}
return ` ${this.color(code, chalk.green)} |`;
}
private formatTimestamp(level: LogLevel): string {
if (!(this.options.timestamp && (level === "debug" || level === "verbose"))) {
return "";
}
const colored = this.color(`[${getUpTime()} s]`, chalk.yellow);
return ` ${colored}`;
}
private color(text: string, color: (text: string) => string) {
return this.options.color ? color(text) : text;
}
private formatSource(source: EnhancedSourceLocation): string {
if (!source.position) {
return "";
}
try {
return `\n - ${this.color(source.document, chalk.cyan)}${this.formatPosition(source.position)}`;
} catch (e) {
// no friendly name, so nothing more specific to show
return "";
}
}
private formatPosition(position: EnhancedPosition) {
let text = "";
if (position.line !== undefined) {
text += `:${this.color(position.line.toString(), chalk.cyan.bold)}`;
if (position.column !== undefined) {
text += `:${this.color(position.column.toString(), chalk.cyan.bold)}`;
}
}
const path = position.path ? ` (${serializeJsonPointer(position.path)})` : "";
return `${text}${path}`;
}
}
export class JsonLogFormatter implements LogFormatter {
private options: { timestamp: boolean };
public constructor(options: { timestamp?: boolean }) {
this.options = { timestamp: true, ...options };
}
public log(log: EnhancedLogInfo): string {
const addTimestamp = this.options.timestamp && (log.level === "debug" || log.level === "verbose");
const data = addTimestamp ? { ...log, uptime: getUpTime() } : log;
return JSON.stringify(data);
}
}
/**
* @returns uptime of process in seconds
*/
function getUpTime(): string {
return process.uptime().toFixed(2);
}