From 6d184acc133311307cc16491c1174c316a39c88f Mon Sep 17 00:00:00 2001 From: Tamius Han Date: Mon, 22 Dec 2025 01:00:00 +0100 Subject: [PATCH] Work around chrome's console.log limitations. --- src/ext/lib/logging/ComponentLogger.ts | 221 ++++++++++++++++++++++++- src/ext/lib/logging/LogAggregator.ts | 18 +- 2 files changed, 227 insertions(+), 12 deletions(-) diff --git a/src/ext/lib/logging/ComponentLogger.ts b/src/ext/lib/logging/ComponentLogger.ts index f18de9d..fddf65b 100644 --- a/src/ext/lib/logging/ComponentLogger.ts +++ b/src/ext/lib/logging/ComponentLogger.ts @@ -14,6 +14,14 @@ export type ComponentLoggerOptions = { } } +const OBJECT_SEPARATOR_STYLE = {color: '#fff'}; +const OBJECT_KEY_STYLE = {color: '#ebe4ff69'}; +const OBJECT_STRING_STYLE = {color: '#80b6e5ff'}; +const OBJECT_NUMBER_STYLE = {color: 'rgba(127, 104, 204, 1)'}; +const OBJECT_BOOL_TRUE = {color: 'rgba(148, 206, 187, 1)'}; +const OBJECT_BOOL_FALSE = {color: 'rgba(214, 82, 49, 1)'}; +const OBJECT_UNDEFINED_STYLE = {color: '#ffffff3a'}; + export class ComponentLogger { private logAggregator: LogAggregator; private component: string; @@ -25,24 +33,227 @@ export class ComponentLogger { this.componentOptions = componentOptions; } + private parseStyle(s?: string) { + if (!s) { + return; + } + + const segments = s.split(';').map(x =>x.trim()); + const out = {}; + for (const sg of segments) { + const [key, value] = sg.split(':', 1); + out[key] = value; + } + + return out; + } + + private mix(base?: any, style?: any) { + if (!base && !style) { + return undefined; + } + + const combined = {...base, ...style}; + let out = []; + for (const key in combined) { + out.push(`${key}: ${combined[key]}`); + } + + return out.join('; '); + } + + private generateObjectPreview(obj: any, baseStyle?: any) { + const maxKeys = 5; + + const colorArgs = [this.mix(baseStyle, OBJECT_SEPARATOR_STYLE)]; + const objKeys = []; + + let keyCount = 0; + for (const key of Object.keys(obj)) { + if (keyCount >= maxKeys) { + objKeys.push('...'); + break; + } + + if (typeof obj[key] === 'object') { + if (obj[key] === null) { + objKeys.push(` %c${key}%c: %c${obj[key]}%c`); + colorArgs.push( + this.mix(baseStyle, OBJECT_KEY_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE), + this.mix(baseStyle, OBJECT_NUMBER_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } else { + if (Array.isArray(obj[key])) { + objKeys.push(`%c${key}%c: [...]`); + } else { + objKeys.push(`%c${key}%c: {...}`); + } + colorArgs.push( + this.mix(baseStyle, OBJECT_KEY_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } + } else { + if (typeof obj[key] === 'number' ) { + objKeys.push(`%c${key}%c: %c${obj[key]}%c`); + colorArgs.push( + this.mix(baseStyle, OBJECT_KEY_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE), + this.mix(baseStyle, OBJECT_NUMBER_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } else if (typeof obj[key] === 'boolean') { + objKeys.push(`%c${key}%c: %c${obj[key]}%c`); + colorArgs.push( + this.mix(baseStyle, OBJECT_KEY_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE), + this.mix(baseStyle, obj[key] ? OBJECT_BOOL_TRUE : OBJECT_BOOL_FALSE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } else if (typeof obj[key] === 'undefined') { + objKeys.push(`%c${key}%c: %o`); + colorArgs.push( + this.mix(baseStyle, OBJECT_KEY_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE), + obj[key] + ); + } else { + objKeys.push(`%c${key}%c: %c${obj[key]}%c`); + colorArgs.push( + this.mix(baseStyle, OBJECT_KEY_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE), + this.mix(baseStyle, OBJECT_STRING_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } + } + keyCount++; + } + + // unset colors + colorArgs.push(baseStyle ? this.mix(baseStyle, undefined) : ''); + return { + str: `%c{ ${objKeys.join(', ')} }%c`, + colors: colorArgs + } + } + + private generateArrayPreview(obj: any[], baseStyle?: any) { + const maxKeys = 5; + + const limit = Math.min(obj.length, maxKeys); + const arrValues = []; + const colorArgs = [this.mix(baseStyle, OBJECT_SEPARATOR_STYLE)]; + + for (let i = 0; i < limit; i++) { + + if (typeof obj[i] === 'object') { + if (obj[i] === null) { + arrValues.push(`%c${obj[i]}%c`); + colorArgs.push( + this.mix(baseStyle, OBJECT_NUMBER_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } if (Array.isArray(obj[i])) { + arrValues.push(` [...]`); + } else { + arrValues.push(` {...}`); + } + } else { + if (typeof obj[i] === 'number' ) { + arrValues.push(`%c${obj[i]}%c`); + colorArgs.push( + this.mix(baseStyle, OBJECT_NUMBER_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } else if (typeof obj[i] === 'boolean') { + arrValues.push(`%c${obj[i]}%c`); + colorArgs.push( + this.mix(baseStyle, obj[i] ? OBJECT_BOOL_TRUE : OBJECT_BOOL_FALSE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } else if (typeof obj[i] === 'undefined') { + arrValues.push(`%o`); + colorArgs.push( + obj[i], + ); + } else { + arrValues.push(`%c${obj[i]}%c`); + colorArgs.push( + this.mix(baseStyle, OBJECT_STRING_STYLE), + this.mix(baseStyle, OBJECT_SEPARATOR_STYLE) + ); + } + } + } + + if (obj.length > limit) { + arrValues.push('...'); + } + + // unset colors + colorArgs.push(baseStyle ? this.mix(baseStyle, undefined) : ''); + return { + str: `%c[ ${arrValues.join(', ')} ]%c`, + colors: colorArgs + } + } + private handleLog(logLevel: LogLevel, sourceFunction: string | LogSourceOptions, ...message: any) { let functionSource = typeof sourceFunction === 'string' ? sourceFunction : sourceFunction?.src; let consoleMessageString = `[${this.component}${functionSource ? `::${functionSource}` : ''}]`; const consoleMessageData = [] + const styleObj = this.parseStyle(this.componentOptions?.styles?.[logLevel] ?? this.componentOptions?.styles?.[LogLevel.Log]); + + let styleArgsCount = 0; + for (const m of message) { + if (styleArgsCount --> 0) { + consoleMessageData.push(m); + continue; + } + if (typeof m === 'string') { consoleMessageString = `${consoleMessageString} ${m}`; + + styleArgsCount = m.split('%c').length - 1; } else if (typeof m === 'number') { - consoleMessageString = `${consoleMessageString} %f`; - consoleMessageData.push(m); + consoleMessageString = `${consoleMessageString} %c${m}%c`; + consoleMessageData.push( + this.mix(styleObj, OBJECT_NUMBER_STYLE), + '' + ); + } else if (typeof m === 'undefined') { + consoleMessageString = `${consoleMessageString} %c%o%c`; + consoleMessageData.push( + this.mix(styleObj, OBJECT_UNDEFINED_STYLE), + m, + '' + ); } else if (typeof HTMLElement !== 'undefined' && m instanceof HTMLElement) { // HTMLElement does not exist in background script, but this class may consoleMessageString = `${consoleMessageString} %o`; consoleMessageData.push(m); } else { - consoleMessageString = `${consoleMessageString} %O`; - consoleMessageData.push(m); + if (m === null) { + consoleMessageString = `${consoleMessageString} %c${m}%c`; + consoleMessageData.push( + this.mix(styleObj, OBJECT_NUMBER_STYLE), + m, + '' + ); + } else if (Array.isArray(m)) { + const {str, colors} = this.generateArrayPreview(m, styleObj); + consoleMessageString = `${consoleMessageString} ${str}%O`; + consoleMessageData.push(...colors, m); + } else { + const {str, colors} = this.generateObjectPreview(m, styleObj); + consoleMessageString = `${consoleMessageString} ${str}%O`; + consoleMessageData.push(...colors, m); + } } } @@ -52,7 +263,7 @@ export class ComponentLogger { consoleMessageData.unshift(style); } - this.logAggregator.log(this.component, logLevel, typeof sourceFunction === 'object' ? sourceFunction : undefined, consoleMessageString, ...consoleMessageData); + this.logAggregator.log(logLevel, consoleMessageString, ...consoleMessageData); } debug(sourceFunction: string | LogSourceOptions, ...message: any[]) { diff --git a/src/ext/lib/logging/LogAggregator.ts b/src/ext/lib/logging/LogAggregator.ts index 8c6c030..1013b8f 100644 --- a/src/ext/lib/logging/LogAggregator.ts +++ b/src/ext/lib/logging/LogAggregator.ts @@ -94,7 +94,7 @@ export class LogAggregator { this.init(); } - private canLog(component: string, sourceOptions?: LogSourceOptions): boolean { + canLog(component: string, sourceOptions?: LogSourceOptions): boolean { // component is not in config, so we add a blank entry if (this.config && !this.config.component[component]) { this.config.component[component] = {enabled: false}; @@ -215,13 +215,17 @@ export class LogAggregator { } } - log(component: string, logLevel: string, sourceOptions: LogSourceOptions, message: string, ...data: any[]) { - if (! this.canLog(component, sourceOptions)) { - return; - } - + /** + * Logs message to console. + * This function does NOT check before logging, as canLog check should now be performed + * BEFORE ComponentLogger starts processing log calls. + * @param logLevel + * @param message + * @param data + */ + log(logLevel: string, message: string, ...data: any[]) { if (this.config.logToFile) { - + console.warn('log to file not implemented!') } if (this.config.logToConsole){