web/log_system: bump version to 1.5.5 and improve logger output\n\n- Bump package version to 1.5.5\n- Include configurable milliseconds in timestamps and compact callsite labels\n- Improve console output formatting and grouping of expanded details\n- Parse and print structured JSON payloads in string args\n- Normalize and enrich callsite information for clearer source labels\n- Misc refactors and bugfixes to formatting and suffix logic\n\nReason: improve developer console readability and make logs easier to inspect and parse.

This commit is contained in:
Dariusz L
2026-05-26 14:58:11 +02:00
parent 7d8c8895f1
commit b4798333d6
3 changed files with 223 additions and 28 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
/**
author: Azornes
title: AzLogs
version: 1.5.3
version: 1.5.5
description: Logging Configuration
*/
// Log level for development.
@@ -12,7 +12,7 @@ export const LOG_LEVEL = 'DEBUG';
export const LOG_MODULE_NAME = "MLinker";
// Default project/logger name used when LOG_MODULE_NAME is not set.
export const DEFAULT_LOGGER_NAME = 'az-logs';
export const DEFAULT_LOGGER_NAME = 'MLinker';
// Enable styled console logs.
export const USE_COLORS = true;
+1 -1
View File
@@ -1,7 +1,7 @@
/**
author: Azornes
title: AzLogs
version: 1.5.3
version: 1.5.5
description: Logging Initializator
Features:
+220 -25
View File
@@ -1,7 +1,7 @@
/**
author: Azornes
title: AzLogs
version: 1.5.3
version: 1.5.5
description: Logging Setup - Central logging system
Features:
@@ -58,7 +58,9 @@ const DEFAULT_CONFIG = {
useColors: USE_COLORS,
saveToStorage: false,
maxStoredLogs: 1000,
timestampFormat: 'HH:mm:ss.SSS',
timestampFormat: 'HH:mm:ss',
includeMilliseconds: true,
compactCallsite: true,
storageKey: LOGGER_STORAGE_KEY
};
const COLORS = {
@@ -146,12 +148,17 @@ class Logger {
*/
formatTimestamp() {
const now = new Date();
const format = this.config.timestampFormat;
return format
const format = this.config.timestampFormat || DEFAULT_CONFIG.timestampFormat;
const milliseconds = padStart(String(now.getMilliseconds()), 3, '0');
const timestamp = format
.replace('HH', padStart(String(now.getHours()), 2, '0'))
.replace('mm', padStart(String(now.getMinutes()), 2, '0'))
.replace('ss', padStart(String(now.getSeconds()), 2, '0'))
.replace('SSS', padStart(String(now.getMilliseconds()), 3, '0'));
.replace('SSS', milliseconds);
if (this.config.includeMilliseconds !== false && !format.includes('SSS')) {
return `${timestamp}.${milliseconds}`;
}
return timestamp;
}
/**
* Save log
@@ -193,26 +200,33 @@ class Logger {
const consoleFn = typeof console[consoleMethod] === 'function'
? console[consoleMethod].bind(console)
: console.log.bind(console);
const detailFn = typeof console.log === 'function'
? console.log.bind(console)
: consoleFn;
const normalizedCallsite = this.normalizeCallsite(callsite);
const shouldGroupDetails = this.shouldGroupDetails(normalizedCallsite);
const outputFn = shouldGroupDetails
? console.groupCollapsed.bind(console)
: consoleFn;
const { root, detail } = this.splitModuleName(module);
const consoleArgs = this.formatConsoleArgs(args);
const suffix = this.formatSuffix(detail, normalizedCallsite);
const suffixArgs = suffix ? [suffix.trimStart()] : [];
if (this.config.useColors && typeof consoleFn === 'function') {
const color = COLORS[level] || '#000000';
const { root, detail } = this.splitModuleName(module);
const message = this.stringifyArgs(args);
const suffix = this.formatSuffix(detail, callsite);
consoleFn(
`%c ${levelName.padEnd(LEVEL_WIDTH, ' ')} %c ${timestamp} %c %c ${root} %c %c ${message}%c${suffix}`,
`background:${color};color:#fff;font-weight:bold;`,
`background:${TIME_BG};color:#fff;font-weight:bold;`,
`background:${color};color:${color};`,
`background:${TIME_BG};color:#fff;font-weight:bold;`,
`background:${color};color:${color};`,
`color:${color};font-weight:bold;`,
'',
);
outputFn(...this.formatStyledConsoleArgs({
levelName,
timestamp,
root,
args: consoleArgs,
suffix: suffixArgs[0] || '',
color
}));
this.printExpandedDetails(detailFn, normalizedCallsite);
return;
}
const { root, detail } = this.splitModuleName(module);
const suffix = this.formatSuffix(detail, callsite);
consoleFn(`${levelName.padEnd(LEVEL_WIDTH, ' ')} ${timestamp} ${root} ${this.stringifyArgs(args)}${suffix}`);
outputFn(`${levelName.padEnd(LEVEL_WIDTH, ' ')} ${timestamp} ${root}`, ...consoleArgs, ...suffixArgs);
this.printExpandedDetails(detailFn, normalizedCallsite);
}
splitModuleName(module) {
@@ -251,6 +265,75 @@ class Logger {
}).join(' ');
}
formatConsoleArgs(args = []) {
return args.flatMap((arg) => this.formatConsoleArg(arg));
}
formatConsoleArg(arg) {
if (typeof arg === 'string') {
const payload = this.parseJsonPayloadFromString(arg);
if (payload) {
return payload.prefix ? [payload.prefix, payload.value] : [payload.value];
}
}
return [arg];
}
formatStyledConsoleArgs({ levelName, timestamp, root, args = [], suffix = '', color }) {
const outputArgs = [
`%c ${levelName.padEnd(LEVEL_WIDTH, ' ')} %c ${timestamp} %c %c ${root} %c %c `,
`background:${color};color:#fff;font-weight:bold;`,
`background:${TIME_BG};color:#fff;font-weight:bold;`,
`background:${color};color:${color};`,
`background:${TIME_BG};color:#fff;font-weight:bold;`,
`background:${color};color:${color};`,
`color:${color};font-weight:bold;`
];
let format = outputArgs[0];
let hasContent = false;
args.forEach((arg) => {
if (hasContent) {
format += ' ';
}
if (this.isExpandableConsoleArg(arg)) {
format += '%o';
outputArgs.push(arg);
}
else {
format += this.escapeConsoleFormat(this.stringifyConsolePrimitive(arg));
}
hasContent = true;
});
if (suffix) {
if (hasContent) {
format += ' ';
}
format += `%c${this.escapeConsoleFormat(suffix)}`;
outputArgs.push('');
}
outputArgs[0] = format;
return outputArgs;
}
isExpandableConsoleArg(arg) {
return (typeof arg === 'object' && arg !== null && !(arg instanceof Error))
|| typeof arg === 'function';
}
stringifyConsolePrimitive(arg) {
if (arg instanceof Error) {
return arg.stack || arg.message;
}
return String(arg);
}
escapeConsoleFormat(value) {
return String(value).replace(/%/g, '%%');
}
getCallsite() {
const stack = new Error().stack;
if (!stack) {
@@ -269,7 +352,7 @@ class Logger {
continue;
}
return this.formatCallsite(parsed);
return this.createCallsite(parsed);
}
return '';
@@ -291,11 +374,123 @@ class Logger {
}
formatCallsite(callsite) {
if (!callsite) {
return '';
}
if (typeof callsite === 'string') {
return callsite;
}
if (callsite.full) {
return callsite.full;
}
return `${callsite.url}:${callsite.line}:${callsite.column}`;
}
formatSuffix(detail, callsite) {
const suffixDetail = callsite || detail;
createCallsite(callsite) {
const full = this.formatCallsite(callsite);
return {
url: callsite.url,
line: callsite.line,
column: callsite.column,
full,
label: this.formatCompactCallsite(callsite)
};
}
normalizeCallsite(callsite) {
if (!callsite) {
return null;
}
const full = this.formatCallsite(callsite);
if (!full) {
return null;
}
return {
...(typeof callsite === 'object' ? callsite : {}),
full,
label: callsite.label || this.formatCompactCallsite(callsite)
};
}
formatCompactCallsite(callsite) {
const source = typeof callsite === 'string'
? callsite.replace(/:\d+:\d+$/, '')
: callsite.url || callsite.full || '';
const normalizedSource = String(source).split(/[?#]/)[0].replace(/\\/g, '/');
const filename = normalizedSource.slice(normalizedSource.lastIndexOf('/') + 1);
return filename.replace(/\.(?:mjs|js|tsx?|jsx)$/i, '') || this.formatCallsite(callsite);
}
shouldGroupDetails(callsite) {
return this.config.compactCallsite !== false
&& !!callsite?.full
&& typeof console.groupCollapsed === 'function'
&& typeof console.groupEnd === 'function';
}
printExpandedDetails(consoleFn, callsite) {
if (!this.shouldGroupDetails(callsite)) {
return;
}
if (callsite?.full) {
consoleFn(`Source: ${callsite.full}`);
}
console.groupEnd();
}
parseJsonPayloadFromString(value) {
const parsed = this.parseJsonLikeString(value);
if (parsed !== null) {
return {
prefix: '',
value: parsed
};
}
const start = this.findJsonPayloadStart(value);
if (start <= 0) {
return null;
}
const payload = this.parseJsonLikeString(value.slice(start));
if (payload === null) {
return null;
}
return {
prefix: value.slice(0, start).trimEnd(),
value: payload
};
}
findJsonPayloadStart(value) {
const objectStart = value.indexOf('{');
const arrayStart = value.indexOf('[');
if (objectStart < 0) {
return arrayStart;
}
if (arrayStart < 0) {
return objectStart;
}
return Math.min(objectStart, arrayStart);
}
parseJsonLikeString(value) {
const trimmed = value.trim();
if (!trimmed || !/^[{[]/.test(trimmed)) {
return null;
}
try {
return JSON.parse(trimmed);
}
catch (e) {
return null;
}
}
formatSuffix(detail, callsite, options = {}) {
const normalizedCallsite = this.normalizeCallsite(callsite);
const compactCallsite = options.compactCallsite ?? this.config.compactCallsite !== false;
const suffixDetail = normalizedCallsite
? (compactCallsite ? normalizedCallsite.label : normalizedCallsite.full)
: detail;
return suffixDetail ? ` (${suffixDetail})` : '';
}
@@ -439,7 +634,7 @@ class Logger {
else {
content = this.logs.map((log) => {
const { root, detail } = this.splitModuleName(log.module);
const suffix = this.formatSuffix(detail, log.callsite);
const suffix = this.formatSuffix(detail, log.callsite, { compactCallsite: false });
return `${log.levelName.padEnd(LEVEL_WIDTH, ' ')} ${log.timestamp} ${root} ${this.stringifyArgs(log.args)}${suffix}`;
}).join('\n');
mimeType = 'text/plain';