3 Commits

Author SHA1 Message Date
25dfdb470b Change the naming convention for LogMessage internal message field. 2025-05-19 17:59:46 -05:00
f21cce9944 Update eslint config. 2025-01-07 09:40:09 -06:00
a89a41520c ConsoleLogAppender writes a human-readable summary when logging structured data.
Taking advantage of the new LogMessageFormatter return type, the
ConsoleLogAppender logs the formatted message as-is if it is a string,
but when processing structured data inserts a string summary consisting
of the message level, scope, and message summary or method. The full
object is still logged to the console as well for inspection.
2025-01-07 09:30:21 -06:00
4 changed files with 61 additions and 25 deletions

View File

@ -25,6 +25,14 @@ const customTypescriptConfig = {
rules: { rules: {
'linebreak-style': ['error', 'unix'], 'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single', { avoidEscape: true }], quotes: ['error', 'single', { avoidEscape: true }],
semi: ['error', 'never'],
'@typescript-eslint/no-unused-vars': ['error', {
'args': 'all',
'argsIgnorePattern': '^_',
'caughtErrorsIgnorePattern': '^_',
'destructuredArrayIgnorePattern': '^_',
'varsIgnorePattern': '^_',
}],
}, },
} }
@ -51,4 +59,10 @@ export default [
eslintJs.configs.recommended, eslintJs.configs.recommended,
...recommendedTypeScriptConfigs, ...recommendedTypeScriptConfigs,
customTypescriptConfig, customTypescriptConfig,
{
rules: {
quotes: ['error', 'single', { avoidEscape: true }],
semi: ['error', 'never'],
},
},
] ]

View File

@ -1,6 +1,6 @@
{ {
"name": "@jdbernard/logging", "name": "@jdbernard/logging",
"version": "2.0.0", "version": "2.1.0",
"description": "Simple Javascript logging service.", "description": "Simple Javascript logging service.",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@ -3,12 +3,23 @@ import {
LogLevel, LogLevel,
LogMessage, LogMessage,
LogMessageFormatter, LogMessageFormatter,
} from './log-message'; } from "./log-message";
import { LogAppender } from './log-appender'; import { LogAppender } from "./log-appender";
/**
* A log appender that writes log messages to the console. The behavior of the
* log appender can be configured with a threshold level and a message
* formatter.
*
* When the message formatter returns a string value, that value is logged to
* the console as-is. When the message formatter returns an object, a summary
* string is logged to the console, followed by the object itself. This allows
* logs to be easily read in the console, while still providing the structured
* data for inspection in the browser's developer tools.
*/
export class ConsoleLogAppender implements LogAppender { export class ConsoleLogAppender implements LogAppender {
public threshold = LogLevel.ALL; public threshold = LogLevel.ALL;
public formatter: LogMessageFormatter = flattenMessage public formatter: LogMessageFormatter = flattenMessage;
constructor(threshold?: LogLevel, formatter?: LogMessageFormatter) { constructor(threshold?: LogLevel, formatter?: LogMessageFormatter) {
if (threshold) { if (threshold) {
@ -27,17 +38,13 @@ export class ConsoleLogAppender implements LogAppender {
let logMethod = console.log; let logMethod = console.log;
switch (msg.level) { switch (msg.level) {
case LogLevel.ALL: case LogLevel.ALL:
logMethod = console.log;
break;
case LogLevel.TRACE: case LogLevel.TRACE:
case LogLevel.LOG:
logMethod = console.log; logMethod = console.log;
break; break;
case LogLevel.DEBUG: case LogLevel.DEBUG:
logMethod = console.debug; logMethod = console.debug;
break; break;
case LogLevel.LOG:
logMethod = console.log;
break;
case LogLevel.INFO: case LogLevel.INFO:
logMethod = console.info; logMethod = console.info;
break; break;
@ -50,12 +57,25 @@ export class ConsoleLogAppender implements LogAppender {
break; break;
} }
const strMsg = this.formatter(msg); const fmtMsg = this.formatter(msg);
if (typeof fmtMsg === "string") {
if (msg.error || msg.stacktrace) {
logMethod(fmtMsg, msg.error ?? msg.stacktrace);
} else {
logMethod(fmtMsg);
}
} else {
const { message, _error, _stacktrace, ...rest } = fmtMsg;
const summary = `${LogLevel[msg.level]} -- ${msg.scope}: ${
message ?? fmtMsg.method
}\n`;
if (msg.error || msg.stacktrace) { if (msg.error || msg.stacktrace) {
logMethod(strMsg, msg.error ?? msg.stacktrace); logMethod(summary, msg.error ?? msg.stacktrace, rest);
} else { } else {
logMethod(strMsg); logMethod(summary, rest);
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
import { omit } from './util' import { omit } from "./util";
export enum LogLevel { export enum LogLevel {
ALL = 0, ALL = 0,
@ -25,7 +25,7 @@ export function parseLogLevel(
export interface LogMessage { export interface LogMessage {
scope: string; scope: string;
level: LogLevel; level: LogLevel;
message: string | Record<string, unknown>; msg: string | Record<string, unknown>;
stacktrace?: string; stacktrace?: string;
error?: Error; error?: Error;
timestamp: Date; timestamp: Date;
@ -54,31 +54,33 @@ export type FlattenedLogMessage = Record<string, unknown>;
* ``` * ```
*/ */
export function flattenMessage(msg: LogMessage): FlattenedLogMessage { export function flattenMessage(msg: LogMessage): FlattenedLogMessage {
if (typeof msg.message === 'string') { if (typeof msg.msg === "string") {
return { ...msg, level: LogLevel[msg.level] }; return { ...msg, level: LogLevel[msg.level] };
} else { } else {
const { message, ...rest } = msg; const { msg, ...rest } = msg;
return { return {
...omit(message, [ ...omit(msg, [
'scope', "scope",
'level', "level",
'stacktrace', "stacktrace",
'error', "error",
'timestamp', "timestamp",
]), ]),
...rest, ...rest,
level: LogLevel[msg.level], level: LogLevel[msg.level],
}; };
} }
} }
export type LogMessageFormatter = (msg: LogMessage) => string | FlattenedLogMessage; export type LogMessageFormatter = (
msg: LogMessage,
) => string | FlattenedLogMessage;
export function structuredLogMessageFormatter(msg: LogMessage): string { export function structuredLogMessageFormatter(msg: LogMessage): string {
return JSON.stringify(flattenMessage(msg)); return JSON.stringify(flattenMessage(msg));
} }
export function simpleTextLogMessageFormatter(msg: LogMessage): string { export function simpleTextLogMessageFormatter(msg: LogMessage): string {
return `[${msg.scope}] - ${msg.level}: ${msg.message}`; return `[${msg.scope}] - ${msg.level}: ${msg.msg}`;
} }
export default LogMessage; export default LogMessage;