From 7e429e4f5692e0852db379a22137c792dafc9212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Wed, 5 Mar 2025 20:50:54 +0100 Subject: [PATCH 01/22] Add required dependencies --- packages/common-universal/package.json | 2 ++ pnpm-lock.yaml | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/packages/common-universal/package.json b/packages/common-universal/package.json index 1d8f8c1fd..b37743fb4 100644 --- a/packages/common-universal/package.json +++ b/packages/common-universal/package.json @@ -53,6 +53,8 @@ "mocha": "^10.8.2" }, "dependencies": { + "chalk": "^5.3.0", + "error-stack-parser": "^2.1.4", "fetch-retry": "^5.0.6", "ms": "^2.1.3", "p-limit": "^6.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ad083cae..883e1a724 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -478,6 +478,12 @@ importers: bignumber.js: specifier: ^9.1.2 version: 9.1.2 + chalk: + specifier: ^5.3.0 + version: 5.3.0 + error-stack-parser: + specifier: ^2.1.4 + version: 2.1.4 fetch-retry: specifier: ^5.0.6 version: 5.0.6 From c48e600650673108916a42c9326871c87b8faf11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 08:53:57 +0100 Subject: [PATCH 02/22] Add node types --- packages/common-universal/package.json | 1 + pnpm-lock.yaml | 47 ++++++++++++++++---------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/packages/common-universal/package.json b/packages/common-universal/package.json index b37743fb4..a12823a0c 100644 --- a/packages/common-universal/package.json +++ b/packages/common-universal/package.json @@ -48,6 +48,7 @@ "@sinonjs/fake-timers": "^13.0.5", "@types/mocha": "^10.0.10", "@types/ms": "^2.1.0", + "@types/node": "^22.13.9", "@types/sinonjs__fake-timers": "^8.1.5", "earl": "^1.3.0", "mocha": "^10.8.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 883e1a724..ef91397d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -506,6 +506,9 @@ importers: '@types/ms': specifier: ^2.1.0 version: 2.1.0 + '@types/node': + specifier: ^22.13.9 + version: 22.13.9 '@types/sinonjs__fake-timers': specifier: ^8.1.5 version: 8.1.5 @@ -3521,6 +3524,9 @@ packages: '@types/node@20.4.10': resolution: {integrity: sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==} + '@types/node@22.13.9': + resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -6250,6 +6256,7 @@ packages: lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -6694,9 +6701,6 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - object-inspect@1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -8182,6 +8186,9 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -10524,7 +10531,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.17 + '@types/node': 22.13.9 jest-mock: 29.7.0 optional: true @@ -10536,7 +10543,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.17.17 + '@types/node': 22.13.9 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -10581,7 +10588,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.17.17 + '@types/node': 22.13.9 '@types/yargs': 17.0.33 chalk: 4.1.2 optional: true @@ -12507,7 +12514,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.17.17 + '@types/node': 22.13.9 optional: true '@types/http-errors@2.0.4': {} @@ -12564,7 +12571,7 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 20.17.17 + '@types/node': 22.13.9 optional: true '@types/node@20.17.17': @@ -12573,6 +12580,10 @@ snapshots: '@types/node@20.4.10': {} + '@types/node@22.13.9': + dependencies: + undici-types: 6.20.0 + '@types/parse-json@4.0.2': optional: true @@ -14094,7 +14105,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 20.17.17 + '@types/node': 22.13.9 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -14104,7 +14115,7 @@ snapshots: chromium-edge-launcher@0.2.0: dependencies: - '@types/node': 20.17.17 + '@types/node': 22.13.9 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -15935,7 +15946,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.17 + '@types/node': 22.13.9 jest-mock: 29.7.0 jest-util: 29.7.0 optional: true @@ -15946,7 +15957,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.17.17 + '@types/node': 22.13.9 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -15994,7 +16005,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.17 + '@types/node': 22.13.9 jest-util: 29.7.0 optional: true @@ -16013,7 +16024,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.17 + '@types/node': 22.13.9 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -16032,7 +16043,7 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 20.17.17 + '@types/node': 22.13.9 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -16826,8 +16837,6 @@ snapshots: object-hash@3.0.0: {} - object-inspect@1.12.3: {} - object-inspect@1.13.4: {} object-is@1.1.5: @@ -18030,7 +18039,7 @@ snapshots: dependencies: call-bind: 1.0.8 get-intrinsic: 1.2.7 - object-inspect: 1.12.3 + object-inspect: 1.13.4 side-channel@1.1.0: dependencies: @@ -18544,6 +18553,8 @@ snapshots: undici-types@6.19.8: {} + undici-types@6.20.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: optional: true From 5381d1ef305aa4aa239db68abf5a9cd826558835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 08:54:19 +0100 Subject: [PATCH 03/22] Add util and path polyfills to vite config --- packages/app/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 5f57550cb..81977a64f 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -28,7 +28,7 @@ export default defineConfig({ // nodePolyfills needs to be first nodePolyfills({ // buffer is needed when connecting with a coinbase wallet installed on a phone - include: ['buffer'], + include: ['buffer', 'util', 'path'], }), react(), tsconfigPaths(), From 923db3f02554e3d9e8d448364e1babae8f464615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 08:54:39 +0100 Subject: [PATCH 04/22] Move Logger to common-universal --- .../src/logger/LogFormatterJson.ts | 20 + .../src/logger/LogFormatterPretty.ts | 125 +++++++ .../common-universal/src/logger/LogLevel.ts | 6 + .../src/logger/LogThrottle.ts | 74 ++++ .../src/logger/Logger.test.ts | 346 ++++++++++++++++++ .../common-universal/src/logger/Logger.ts | 236 ++++++++++++ .../common-universal/src/logger/README.md | 2 + packages/common-universal/src/logger/index.ts | 6 + .../src/logger/parseLogArguments.test.ts | 237 ++++++++++++ .../src/logger/parseLogArguments.ts | 59 +++ .../src/logger/resolveError.ts | 28 ++ .../common-universal/src/logger/tagService.ts | 4 + packages/common-universal/src/logger/types.ts | 39 ++ packages/common-universal/src/logger/utils.ts | 20 + 14 files changed, 1202 insertions(+) create mode 100644 packages/common-universal/src/logger/LogFormatterJson.ts create mode 100644 packages/common-universal/src/logger/LogFormatterPretty.ts create mode 100644 packages/common-universal/src/logger/LogLevel.ts create mode 100644 packages/common-universal/src/logger/LogThrottle.ts create mode 100644 packages/common-universal/src/logger/Logger.test.ts create mode 100644 packages/common-universal/src/logger/Logger.ts create mode 100644 packages/common-universal/src/logger/README.md create mode 100644 packages/common-universal/src/logger/index.ts create mode 100644 packages/common-universal/src/logger/parseLogArguments.test.ts create mode 100644 packages/common-universal/src/logger/parseLogArguments.ts create mode 100644 packages/common-universal/src/logger/resolveError.ts create mode 100644 packages/common-universal/src/logger/tagService.ts create mode 100644 packages/common-universal/src/logger/types.ts create mode 100644 packages/common-universal/src/logger/utils.ts diff --git a/packages/common-universal/src/logger/LogFormatterJson.ts b/packages/common-universal/src/logger/LogFormatterJson.ts new file mode 100644 index 000000000..631da7d1b --- /dev/null +++ b/packages/common-universal/src/logger/LogFormatterJson.ts @@ -0,0 +1,20 @@ +import { LogEntry, LogFormatter } from './types.js' +import { toJSON } from './utils.js' + +export class LogFormatterJson implements LogFormatter { + public format(entry: LogEntry): string { + const core = { + time: entry.time.toISOString(), + level: entry.level, + service: entry.service, + message: entry.message, + error: entry.resolvedError, + } + + try { + return toJSON({ ...core, parameters: entry.parameters }) + } catch { + return toJSON({ ...core }) + } + } +} diff --git a/packages/common-universal/src/logger/LogFormatterPretty.ts b/packages/common-universal/src/logger/LogFormatterPretty.ts new file mode 100644 index 000000000..4a251f4f8 --- /dev/null +++ b/packages/common-universal/src/logger/LogFormatterPretty.ts @@ -0,0 +1,125 @@ +import { inspect } from 'node:util' +import chalk from 'chalk' + +import { LogLevel } from './LogLevel.js' +import { LogEntry, LogFormatter } from './types.js' +import { toJSON } from './utils.js' + +const STYLES = { + bigint: 'white', + boolean: 'white', + date: 'white', + module: 'white', + name: 'blue', + null: 'white', + number: 'white', + regexp: 'white', + special: 'white', + string: 'white', + symbol: 'white', + undefined: 'white', +} + +const INDENT_SIZE = 4 +const INDENT = ' '.repeat(INDENT_SIZE) + +interface Options { + colors: boolean + utc: boolean +} + +// @note: format methods are protected to allow easy construction of simpler formatters for non-standard environments +export class LogFormatterPretty implements LogFormatter { + protected readonly options: Options + + constructor(options?: Partial) { + this.options = { + colors: options?.colors ?? true, + utc: options?.utc ?? false, + } + } + + public format(entry: LogEntry): string { + const timeOut = this.formatTimePretty(entry.time, this.options.utc, this.options.colors) + const levelOut = this.formatLevelPretty(entry.level, this.options.colors) + const serviceOut = this.formatServicePretty(entry.service, this.options.colors) + const messageOut = entry.message ? ` ${entry.message}` : '' + const paramsOut = this.formatParametersPretty( + this.sanitize(entry.resolvedError ? { ...entry.resolvedError, ...entry.parameters } : (entry.parameters ?? {})), + this.options.colors, + ) + + return `${timeOut} ${levelOut}${serviceOut}${messageOut}${paramsOut}` + } + + protected formatLevelPretty(level: LogLevel, colors: boolean): string { + if (colors) { + switch (level) { + case 'CRITICAL': + case 'ERROR': + return chalk.red(chalk.bold(level.toUpperCase())) + case 'WARN': + return chalk.yellow(chalk.bold(level.toUpperCase())) + case 'INFO': + return chalk.green(chalk.bold(level.toUpperCase())) + case 'DEBUG': + return chalk.magenta(chalk.bold(level.toUpperCase())) + case 'TRACE': + return chalk.gray(chalk.bold(level.toUpperCase())) + } + } + return level.toUpperCase() + } + + protected formatTimePretty(now: Date, utc: boolean, colors: boolean): string { + const h = (utc ? now.getUTCHours() : now.getHours()).toString().padStart(2, '0') + const m = (utc ? now.getUTCMinutes() : now.getMinutes()).toString().padStart(2, '0') + const s = (utc ? now.getUTCSeconds() : now.getSeconds()).toString().padStart(2, '0') + const ms = (utc ? now.getUTCMilliseconds() : now.getMilliseconds()).toString().padStart(3, '0') + + let result = `${h}:${m}:${s}.${ms}` + if (utc) { + result += 'Z' + } + + return colors ? chalk.gray(result) : result + } + + protected formatParametersPretty(parameters: object, colors: boolean): string { + const oldStyles = inspect.styles + inspect.styles = STYLES + + const inspected = inspect(parameters, { + colors, + breakLength: 80 - INDENT_SIZE, + depth: 5, + }) + + inspect.styles = oldStyles + + if (inspected === '{}') { + return '' + } + + const indented = inspected + .split('\n') + .map((x) => INDENT + x) + .join('\n') + + if (colors) { + return `\n${chalk.gray(indented)}` + } + return `\n${indented}` + } + + protected formatServicePretty(service: string | undefined, colors: boolean): string { + if (!service) { + return '' + } + return colors ? ` ${chalk.gray('[')} ${chalk.yellow(service)} ${chalk.gray(']')}` : ` [ ${service} ]` + } + + protected sanitize(parameters: object): object { + return JSON.parse(toJSON(parameters)) + } +} diff --git a/packages/common-universal/src/logger/LogLevel.ts b/packages/common-universal/src/logger/LogLevel.ts new file mode 100644 index 000000000..6d7e0be86 --- /dev/null +++ b/packages/common-universal/src/logger/LogLevel.ts @@ -0,0 +1,6 @@ +export const LOG_LEVELS = ['NONE', 'CRITICAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'] as const +export type LogLevel = (typeof LOG_LEVELS)[number] +export const LEVEL = {} as Record +for (const [index, level] of LOG_LEVELS.entries()) { + LEVEL[level] = index +} diff --git a/packages/common-universal/src/logger/LogThrottle.ts b/packages/common-universal/src/logger/LogThrottle.ts new file mode 100644 index 000000000..51b9f69fe --- /dev/null +++ b/packages/common-universal/src/logger/LogThrottle.ts @@ -0,0 +1,74 @@ +import { LogLevel } from './LogLevel.js' + +export interface Printer { + print(logLevel: LogLevel, service: string | undefined, message: string | undefined, parameters: object): void +} + +export interface LogThrottleOptions { + readonly callsUntilThrottle: number + readonly clearIntervalMs: number + readonly throttleTimeMs: number +} + +export class LogThrottle { + constructor( + private readonly printer: Printer, + private readonly options: LogThrottleOptions, + ) {} + + private callCount: Record = {} + private throttleCount: Record = {} + private started = false + + throttle(logLevel: LogLevel, service: string | undefined, message: string | undefined): boolean { + this.start() + + const key = `${logLevel}:${service ?? ''}:${message ?? ''}` + let isThrottling = key in this.throttleCount + + if (!isThrottling) { + const count = (this.callCount[key] ?? 0) + 1 + this.callCount[key] = count + + if (count > this.options.callsUntilThrottle) { + delete this.callCount[key] + + isThrottling = true + this.waitAndClear(key, logLevel, service, message) + } + } + + if (isThrottling) { + this.throttleCount[key] = (this.throttleCount[key] ?? 0) + 1 + } + + return isThrottling + } + + private waitAndClear( + key: string, + logLevel: LogLevel, + service: string | undefined, + message: string | undefined, + ): void { + setTimeout(() => { + const messageCount = this.throttleCount[key] ?? 0 + delete this.throttleCount[key] + this.printer.print(logLevel, service, `Throttled ${JSON.stringify(message ?? '')}`, { + messageCount, + throttleTimeMs: this.options.throttleTimeMs, + }) + }, this.options.throttleTimeMs) + } + + private start(): void { + if (this.started) { + return + } + + this.started = true + setInterval(() => { + this.callCount = {} + }, this.options.clearIntervalMs) + } +} diff --git a/packages/common-universal/src/logger/Logger.test.ts b/packages/common-universal/src/logger/Logger.test.ts new file mode 100644 index 000000000..d4ec36347 --- /dev/null +++ b/packages/common-universal/src/logger/Logger.test.ts @@ -0,0 +1,346 @@ +/* eslint-disable */ +import { expect, formatCompact, mockFn } from 'earl' + +import { LogFormatterJson } from './LogFormatterJson.js' +import { LogFormatterPretty } from './LogFormatterPretty.js' +import { Logger } from './Logger.js' +import { LogEntry } from './types.js' + +describe(Logger.name, () => { + it('calls correct transport', () => { + const transport = createTestTransport() + const logger = new Logger({ + transports: [ + { + transport: transport, + formatter: new LogFormatterJson(), + }, + ], + logLevel: 'TRACE', + }) + + logger.trace('foo') + logger.debug('foo') + expect(transport.debug).toHaveBeenCalledTimes(2) + + logger.info('foo') + expect(transport.log).toHaveBeenCalledTimes(1) + + logger.warn('foo') + expect(transport.warn).toHaveBeenCalledTimes(1) + + logger.error('foo') + logger.critical('foo') + expect(transport.error).toHaveBeenCalledTimes(2) + }) + + it('supports bigint values in json output', () => { + const transport = createTestTransport() + const logger = new Logger({ + transports: [ + { + transport: transport, + formatter: new LogFormatterJson(), + }, + ], + logLevel: 'TRACE', + getTime: () => new Date(0), + utc: true, + }) + + logger.info({ foo: 123n, bar: [4n, 56n] }) + expect(transport.log).toHaveBeenOnlyCalledWith( + JSON.stringify({ + time: '1970-01-01T00:00:00.000Z', + level: 'INFO', + parameters: { + foo: '123', + bar: ['4', '56'], + }, + }), + ) + }) + + it('supports bigint values in pretty output', () => { + const transport = createTestTransport() + const logger = new Logger({ + transports: [ + { + transport: transport, + formatter: new LogFormatterPretty({ colors: false, utc: true }), + }, + ], + logLevel: 'TRACE', + getTime: () => new Date(0), + utc: true, + }) + + logger.info({ foo: 123n, bar: [4n, 56n] }) + const lines = ['00:00:00.000Z INFO\n', " { foo: '123', bar: [ '4', '56' ] }", ''] + expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) + }) + + it('marks promised values in pretty output', () => { + const transport = createTestTransport() + const logger = new Logger({ + transports: [ + { + transport: transport, + formatter: new LogFormatterPretty({ colors: false, utc: true }), + }, + ], + logLevel: 'TRACE', + getTime: () => new Date(0), + utc: true, + }) + + logger.info({ test: Promise.resolve(1234) }) + const lines = ['00:00:00.000Z INFO\n', " { test: 'Promise' }", ''] + expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) + }) + + describe('for', () => { + function setup() { + const transport = createTestTransport() + const baseLogger = new Logger({ + transports: [ + { + transport: transport, + formatter: new LogFormatterPretty({ colors: false, utc: true }), + }, + ], + logLevel: 'TRACE', + getTime: () => new Date(0), + utc: true, + }) + return { transport, baseLogger } + } + + it('single service (string)', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.for('FooService') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') + }) + + it('single service (object)', () => { + const { transport, baseLogger } = setup() + + class FooService {} + const instance = new FooService() + const logger = baseLogger.for(instance) + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') + }) + + it('service with member', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.for('FooService').for('queue') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue ] hello') + }) + + it('service with tag', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red').for('FooService') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService:Red ] hello') + }) + + it('service with tag and member', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red').for('FooService').for('queue') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue:Red ] hello') + }) + + it('lone tag', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ :Red ] hello') + }) + }) + + describe('error reporting', () => { + const oldConsoleError = console.error + beforeEach(() => { + console.error = () => {} + }) + afterEach(() => { + console.error = oldConsoleError + }) + + it('reports error and critical error', () => { + const mockReportError = mockFn((_: unknown) => {}) + const logger = new Logger({ + reportError: mockReportError, + }) + + logger.error('foo') + logger.critical('bar') + + expect(mockReportError).toHaveBeenNthCalledWith(1, { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: 'foo', + parameters: undefined, + error: undefined, + resolvedError: undefined, + }) + expect(mockReportError).toHaveBeenNthCalledWith(2, { + level: 'CRITICAL', + time: expect.a(Date), + service: undefined, + message: 'bar', + parameters: undefined, + error: undefined, + resolvedError: undefined, + }) + }) + + describe('usage patterns', () => { + const patterns: [unknown[], LogEntry][] = [ + [ + ['message'], + { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: 'message', + parameters: undefined, + error: undefined, + resolvedError: undefined, + }, + ], + [ + [new Error('message')], + { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: undefined, + parameters: undefined, + error: new Error('message'), + resolvedError: { + name: 'Error', + error: 'message', + stack: expect.a(Array), + }, + }, + ], + [ + ['foo', new Error('bar')], + { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: 'foo', + parameters: undefined, + error: new Error('bar'), + resolvedError: { + name: 'Error', + error: 'bar', + stack: expect.a(Array), + }, + }, + ], + [ + [{ x: 1, y: 2 }], + { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: undefined, + parameters: { x: 1, y: 2 }, + error: undefined, + resolvedError: undefined, + }, + ], + [ + ['message', { x: 1, y: 2 }], + { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: 'message', + parameters: { x: 1, y: 2 }, + error: undefined, + resolvedError: undefined, + }, + ], + [ + [{ x: 1, y: 2, message: 'message' }], + { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: 'message', + parameters: { x: 1, y: 2 }, + error: undefined, + resolvedError: undefined, + }, + ], + [ + [{ x: 1, y: 2, message: true }], + { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: undefined, + parameters: { x: 1, y: 2, message: true }, + error: undefined, + resolvedError: undefined, + }, + ], + [ + [new Error('foo'), 'bar', { x: 1, y: 2 }], + { + level: 'ERROR', + time: expect.a(Date), + service: undefined, + message: 'bar', + parameters: { x: 1, y: 2 }, + error: new Error('foo'), + resolvedError: { + name: 'Error', + error: 'foo', + stack: expect.a(Array), + }, + }, + ], + ] + + for (const [args, expected] of patterns) { + it(`supports ${formatCompact(args, 60)}`, () => { + const mockReportError = mockFn((_: unknown) => {}) + const logger = new Logger({ reportError: mockReportError }) + + logger.error(...args) + expect(mockReportError).toHaveBeenOnlyCalledWith(expected) + }) + } + }) + }) +}) + +function createTestTransport() { + return { + debug: mockFn((_: string): void => {}), + log: mockFn((_: string): void => {}), + warn: mockFn((_: string): void => {}), + error: mockFn((_: string): void => {}), + } +} diff --git a/packages/common-universal/src/logger/Logger.ts b/packages/common-universal/src/logger/Logger.ts new file mode 100644 index 000000000..0dfec6a2f --- /dev/null +++ b/packages/common-universal/src/logger/Logger.ts @@ -0,0 +1,236 @@ +import { join } from 'node:path' + +import { assertNever } from '@marsfoundation/common-universal' +import { LogFormatterJson } from './LogFormatterJson.js' +import { LogFormatterPretty } from './LogFormatterPretty.js' +import { LEVEL, LogLevel } from './LogLevel.js' +import { LogThrottle, LogThrottleOptions } from './LogThrottle.js' +import { parseLogArguments } from './parseLogArguments.js' +import { resolveError } from './resolveError.js' +import { tagService } from './tagService.js' +import { LogEntry, LoggerOptions } from './types.js' + +export interface ILogger { + critical(...args: unknown[]): void + error(...args: unknown[]): void + warn(...args: unknown[]): void + info(...args: unknown[]): void + debug(...args: unknown[]): void + trace(...args: unknown[]): void + + configure(options: Partial): ILogger + for(object: {} | string): ILogger + tag(tag: string | undefined): ILogger +} + +/** + * [Read full documentation](https://github.com/l2beat/tools/blob/master/packages/backend-tools/src/logger/docs.md) + */ +export class Logger implements ILogger { + private readonly options: LoggerOptions + private readonly logLevel: number + private readonly cwd: string + private throttle?: LogThrottle + + constructor(options: Partial) { + this.options = { + logLevel: options.logLevel ?? 'INFO', + service: options.service, + tag: options.tag, + utc: options.utc ?? false, + cwd: options.cwd ?? process.cwd(), + getTime: options.getTime ?? (() => new Date()), + reportError: options.reportError ?? (() => {}), + transports: options.transports ?? [ + { + transport: console, + formatter: new LogFormatterJson(), + }, + ], + } + this.cwd = join(this.options.cwd, '/') + this.logLevel = LEVEL[this.options.logLevel] + } + + static SILENT = new Logger({ logLevel: 'NONE' }) + + static CRITICAL = new Logger({ + logLevel: 'CRITICAL', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static ERROR = new Logger({ + logLevel: 'ERROR', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static WARN = new Logger({ + logLevel: 'WARN', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static INFO = new Logger({ + logLevel: 'INFO', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static DEBUG = new Logger({ + logLevel: 'DEBUG', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static TRACE = new Logger({ + logLevel: 'TRACE', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + configure(options: Partial): Logger { + const logger = new Logger({ ...this.options, ...options }) + logger.throttle = this.throttle + return logger + } + + for(object: {} | string): Logger { + const name = typeof object === 'string' ? object : object.constructor.name + return this.configure({ + service: this.options.service ? `${this.options.service}.${name}` : name, + }) + } + + tag(tag: string | undefined): Logger { + return this.configure({ tag }) + } + + withThrottling(options: LogThrottleOptions): Logger { + const logger = new Logger(this.options) + logger.throttle = new LogThrottle( + { + print: (level, service, message, parameters) => + logger.print({ + time: logger.options.getTime(), + level, + service, + message, + parameters, + }), + }, + options, + ) + return logger + } + + critical(...args: unknown[]): void { + if (this.logLevel >= LEVEL.CRITICAL) { + const parsed = this.parseEntry('CRITICAL', args) + this.print(parsed) + this.options.reportError(parsed) + } + } + + error(...args: unknown[]): void { + if (this.logLevel >= LEVEL.ERROR) { + const entry = this.parseEntry('ERROR', args) + this.print(entry) + this.options.reportError(entry) + } + } + + warn(...args: unknown[]): void { + if (this.logLevel >= LEVEL.WARN) { + this.print(this.parseEntry('WARN', args)) + } + } + + info(...args: unknown[]): void { + if (this.logLevel >= LEVEL.INFO) { + this.print(this.parseEntry('INFO', args)) + } + } + + debug(...args: unknown[]): void { + if (this.logLevel >= LEVEL.DEBUG) { + this.print(this.parseEntry('DEBUG', args)) + } + } + + trace(...args: unknown[]): void { + if (this.logLevel >= LEVEL.TRACE) { + this.print(this.parseEntry('TRACE', args)) + } + } + + private parseEntry(level: LogLevel, args: unknown[]): LogEntry { + const parsed = parseLogArguments(args) + return { + ...parsed, + resolvedError: parsed.error ? resolveError(parsed.error, this.cwd) : undefined, + level, + time: this.options.getTime(), + service: tagService(this.options.service, this.options.tag), + } + } + + private print(entry: LogEntry): void { + if (this.throttle?.throttle(entry.level, entry.service, entry.message)) { + return + } + + this.printExactly(entry) + } + + private printExactly(entry: LogEntry): void { + for (const transportOptions of this.options.transports) { + const output = transportOptions.formatter.format(entry) + switch (entry.level) { + case 'CRITICAL': + case 'ERROR': + transportOptions.transport.error(output) + break + case 'WARN': + transportOptions.transport.warn(output) + break + case 'INFO': + transportOptions.transport.log(output) + break + case 'DEBUG': + case 'TRACE': + transportOptions.transport.debug(output) + break + case 'NONE': + break + default: + assertNever(entry.level) + } + } + } +} diff --git a/packages/common-universal/src/logger/README.md b/packages/common-universal/src/logger/README.md new file mode 100644 index 000000000..2bff0bd41 --- /dev/null +++ b/packages/common-universal/src/logger/README.md @@ -0,0 +1,2 @@ +Based on https://github.com/l2beat/l2beat +License: MIT diff --git a/packages/common-universal/src/logger/index.ts b/packages/common-universal/src/logger/index.ts new file mode 100644 index 000000000..e53fbffd2 --- /dev/null +++ b/packages/common-universal/src/logger/index.ts @@ -0,0 +1,6 @@ +export * from './Logger.js' +export * from './LogLevel.js' +export * from './types.js' +export * from './LogFormatterPretty.js' +export * from './LogFormatterJson.js' +export * from './LogThrottle.js' diff --git a/packages/common-universal/src/logger/parseLogArguments.test.ts b/packages/common-universal/src/logger/parseLogArguments.test.ts new file mode 100644 index 000000000..fde6f003b --- /dev/null +++ b/packages/common-universal/src/logger/parseLogArguments.test.ts @@ -0,0 +1,237 @@ +import { expect, formatCompact } from 'earl' + +import { ParsedLogArguments, parseLogArguments } from './parseLogArguments.js' + +describe(parseLogArguments.name, () => { + const patterns: [unknown[], ParsedLogArguments][] = [ + [ + [], + { + message: undefined, + error: undefined, + parameters: undefined, + }, + ], + [ + ['message'], + { + message: 'message', + error: undefined, + parameters: undefined, + }, + ], + [ + // passing another string causes it to be treated as a regular value + ['foo', 'bar'], + { + message: 'foo', + error: undefined, + parameters: { value: 'bar' }, + }, + ], + [ + // without a message, we extract it from parameters.message + [{ message: 'message' }], + { + message: 'message', + error: undefined, + parameters: undefined, + }, + ], + [ + // only parameters.message works + [{ notMessage: 'message' }], + { + message: undefined, + error: undefined, + parameters: { notMessage: 'message' }, + }, + ], + [ + // in case we have a string value, message in parameters is ignored + ['foo', { message: 'bar' }], + { + message: 'foo', + error: undefined, + parameters: { message: 'bar' }, + }, + ], + [ + // in case we have a string value, message in parameters is ignored + [{ message: 'foo' }, 'bar'], + { + message: 'bar', + error: undefined, + parameters: { message: 'foo' }, + }, + ], + [ + ['foo', 'bar', 'baz'], + { + message: 'foo', + error: undefined, + parameters: { values: ['bar', 'baz'] }, + }, + ], + [ + [new Error('error')], + { + message: undefined, + error: new Error('error'), + parameters: undefined, + }, + ], + [ + [new Error('foo'), new Error('bar')], + { + message: undefined, + error: new Error('foo'), + parameters: { value: new Error('bar') }, + }, + ], + [ + [new Error('foo'), new Error('bar'), new Error('baz')], + { + message: undefined, + error: new Error('foo'), + parameters: { values: [new Error('bar'), new Error('baz')] }, + }, + ], + [ + ['message', new Error('error')], + { + message: 'message', + error: new Error('error'), + parameters: undefined, + }, + ], + [ + [new Error('error'), 'message'], + { + message: 'message', + error: new Error('error'), + parameters: undefined, + }, + ], + [ + // without an error, we extract it from parameters.error + [{ error: new Error('error') }], + { + message: undefined, + error: new Error('error'), + parameters: undefined, + }, + ], + [ + // only parameters.error works + [{ notError: new Error('error') }], + { + message: undefined, + error: undefined, + parameters: { notError: new Error('error') }, + }, + ], + [ + ['message', new Error('error'), { foo: 'bar' }], + { + message: 'message', + error: new Error('error'), + parameters: { foo: 'bar' }, + }, + ], + [ + [123], + { + message: undefined, + error: undefined, + parameters: { value: 123 }, + }, + ], + [ + [123, 45], + { + message: undefined, + error: undefined, + parameters: { values: [123, 45] }, + }, + ], + [ + [[123, 45]], + { + message: undefined, + error: undefined, + parameters: { value: [123, 45] }, + }, + ], + [ + // we allow parameters to override the value field + [123, { value: 45 }], + { + message: undefined, + error: undefined, + parameters: { value: 45 }, + }, + ], + [ + // we allow parameters to override the values field + [1, 2, 3, { values: 42 }], + { + message: undefined, + error: undefined, + parameters: { values: 42 }, + }, + ], + [ + [{}], + { + message: undefined, + error: undefined, + parameters: undefined, + }, + ], + [ + [{ foo: 'bar', baz: true }], + { + message: undefined, + error: undefined, + parameters: { foo: 'bar', baz: true }, + }, + ], + [ + [ + { foo: 'bar', baz: true }, + { x: 1, y: 2 }, + ], + { + message: undefined, + error: undefined, + parameters: { foo: 'bar', baz: true, x: 1, y: 2 }, + }, + ], + [ + [ + { foo: 'bar', baz: true }, + { x: 1, y: 2, baz: false }, + ], + { + message: undefined, + error: undefined, + parameters: { foo: 'bar', baz: false, x: 1, y: 2 }, + }, + ], + [ + [123, { foo: 'bar', baz: true }, { x: 4, y: 5 }], + { + message: undefined, + error: undefined, + parameters: { foo: 'bar', baz: true, x: 4, y: 5, value: 123 }, + }, + ], + ] + + for (const [args, expected] of patterns) { + it(`parses ${formatCompact(args, 60)}`, () => { + expect(parseLogArguments(args)).toEqual(expected) + }) + } +}) diff --git a/packages/common-universal/src/logger/parseLogArguments.ts b/packages/common-universal/src/logger/parseLogArguments.ts new file mode 100644 index 000000000..79cbd91d9 --- /dev/null +++ b/packages/common-universal/src/logger/parseLogArguments.ts @@ -0,0 +1,59 @@ +export interface ParsedLogArguments { + message?: string + error?: Error + parameters?: object +} + +export function parseLogArguments(args: unknown[]): ParsedLogArguments { + let message: string | undefined + let error: Error | undefined + const values: unknown[] = [] + let parameters = {} + + for (const arg of args) { + if (typeof arg === 'string') { + if (message === undefined) { + message = arg + } else { + values.push(arg) + } + } else if (arg instanceof Error) { + if (error === undefined) { + error = arg + } else { + values.push(arg) + } + } else if (typeof arg !== 'object' || arg === null || Array.isArray(arg)) { + values.push(arg) + } else { + parameters = { ...parameters, ...arg } + } + } + + // place values in parameters + if (values.length === 1) { + parameters = { value: values[0], ...parameters } + } else if (values.length > 1) { + parameters = { values, ...parameters } + } + + // optionally extract message from parameters + const parameterMessage: unknown = Reflect.get(parameters, 'message') + if (message === undefined && typeof parameterMessage === 'string') { + message = parameterMessage + Reflect.deleteProperty(parameters, 'message') + } + + // optionally extract error from parameters + const parameterError: unknown = Reflect.get(parameters, 'error') + if (error === undefined && parameterError instanceof Error) { + error = parameterError + Reflect.deleteProperty(parameters, 'error') + } + + return { + message, + error, + parameters: Object.keys(parameters).length > 0 ? parameters : undefined, + } +} diff --git a/packages/common-universal/src/logger/resolveError.ts b/packages/common-universal/src/logger/resolveError.ts new file mode 100644 index 000000000..bc550d778 --- /dev/null +++ b/packages/common-universal/src/logger/resolveError.ts @@ -0,0 +1,28 @@ +import ErrorStackParser from 'error-stack-parser' + +export interface ResolvedError { + name: string + error: string + stack: string[] +} + +export function resolveError(error: Error, cwd: string): ResolvedError { + return { + name: error.name, + error: error.message, + stack: ErrorStackParser.parse(error).map((frame) => formatFrame(frame, cwd)), + } +} + +function formatFrame(frame: StackFrame, cwd: string): string { + const file = frame.fileName?.startsWith(cwd) ? frame.fileName.slice(cwd.length) : frame.fileName + const functionName = frame.functionName ? `${frame.functionName} ` : '' + + const fileLocation = + frame.lineNumber !== undefined && frame.columnNumber !== undefined + ? `:${frame.lineNumber}:${frame.columnNumber}` + : '' + const location = file !== undefined ? `(${file}${fileLocation})` : '' + + return `${functionName}${location}` +} diff --git a/packages/common-universal/src/logger/tagService.ts b/packages/common-universal/src/logger/tagService.ts new file mode 100644 index 000000000..19486c859 --- /dev/null +++ b/packages/common-universal/src/logger/tagService.ts @@ -0,0 +1,4 @@ +export function tagService(service: string | undefined, tag: string | undefined): string | undefined { + const concat = (service ?? '') + (tag ? `:${tag}` : '') + return concat ? concat : undefined +} diff --git a/packages/common-universal/src/logger/types.ts b/packages/common-universal/src/logger/types.ts new file mode 100644 index 000000000..20ec6001a --- /dev/null +++ b/packages/common-universal/src/logger/types.ts @@ -0,0 +1,39 @@ +import { LogLevel } from './LogLevel.js' +import { ResolvedError } from './resolveError.js' + +export interface LoggerTransport { + debug(message: string): void + log(message: string): void + warn(message: string): void + error(message: string): void +} + +export interface LogFormatter { + format(entry: LogEntry): string +} + +export interface LoggerTransportOptions { + transport: LoggerTransport + formatter: LogFormatter +} + +export interface LoggerOptions { + logLevel: LogLevel + service?: string + tag?: string + utc: boolean + cwd: string + getTime: () => Date + reportError: (entry: LogEntry) => void + transports: LoggerTransportOptions[] +} + +export interface LogEntry { + level: LogLevel + time: Date + service?: string + message?: string + error?: Error + resolvedError?: ResolvedError + parameters?: object +} diff --git a/packages/common-universal/src/logger/utils.ts b/packages/common-universal/src/logger/utils.ts new file mode 100644 index 000000000..3fdc887cb --- /dev/null +++ b/packages/common-universal/src/logger/utils.ts @@ -0,0 +1,20 @@ +/* eslint-disable */ +export function toJSON(parameters: object): string { + return JSON.stringify(parameters, (_k, v: unknown) => { + if (typeof v === 'bigint') { + return v.toString() + } + + // uses a generic check to catch any promise-like objects (e.g. Promise, Bluebird, etc.) + const isPromise = + typeof v === 'object' && + v !== null && + typeof (v as any).then === 'function' && + typeof (v as any).catch === 'function' + if (isPromise) { + return 'Promise' + } + + return v + }) +} From f296f6f23d9a8a9dd336e05b4218fc8288f8c2ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 10:00:17 +0100 Subject: [PATCH 05/22] Export logger --- packages/common-universal/src/index.ts | 7 +++++++ packages/common-universal/src/logger/index.ts | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 packages/common-universal/src/logger/index.ts diff --git a/packages/common-universal/src/index.ts b/packages/common-universal/src/index.ts index 3d259aa0f..4cbddf195 100644 --- a/packages/common-universal/src/index.ts +++ b/packages/common-universal/src/index.ts @@ -26,6 +26,13 @@ export * from './data/filterOutFalsy.js' export * from './data/reverseDictionary.js' export * from './data/unique.js' +export * from './logger/Logger.js' +export * from './logger/LogLevel.js' +export * from './logger/types.js' +export * from './logger/LogFormatterPretty.js' +export * from './logger/LogFormatterJson.js' +export * from './logger/LogThrottle.js' + export * from './typeHelpers.js' export * from './networking/solidFetch.js' diff --git a/packages/common-universal/src/logger/index.ts b/packages/common-universal/src/logger/index.ts deleted file mode 100644 index e53fbffd2..000000000 --- a/packages/common-universal/src/logger/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './Logger.js' -export * from './LogLevel.js' -export * from './types.js' -export * from './LogFormatterPretty.js' -export * from './LogFormatterJson.js' -export * from './LogThrottle.js' From 8ffcfde980ec4b28e7a99e56fe2b5b69dcd3e086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 10:04:55 +0100 Subject: [PATCH 06/22] Drop path dependency for browsers --- packages/app/vite.config.ts | 3 ++- packages/common-universal/src/logger/Logger.ts | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 81977a64f..4867fbff2 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -28,7 +28,8 @@ export default defineConfig({ // nodePolyfills needs to be first nodePolyfills({ // buffer is needed when connecting with a coinbase wallet installed on a phone - include: ['buffer', 'util', 'path'], + // util is needed for common-universal's logger + include: ['buffer', 'util'], }), react(), tsconfigPaths(), diff --git a/packages/common-universal/src/logger/Logger.ts b/packages/common-universal/src/logger/Logger.ts index 0dfec6a2f..74bbb01de 100644 --- a/packages/common-universal/src/logger/Logger.ts +++ b/packages/common-universal/src/logger/Logger.ts @@ -1,5 +1,3 @@ -import { join } from 'node:path' - import { assertNever } from '@marsfoundation/common-universal' import { LogFormatterJson } from './LogFormatterJson.js' import { LogFormatterPretty } from './LogFormatterPretty.js' @@ -29,7 +27,6 @@ export interface ILogger { export class Logger implements ILogger { private readonly options: LoggerOptions private readonly logLevel: number - private readonly cwd: string private throttle?: LogThrottle constructor(options: Partial) { @@ -38,7 +35,7 @@ export class Logger implements ILogger { service: options.service, tag: options.tag, utc: options.utc ?? false, - cwd: options.cwd ?? process.cwd(), + cwd: this.getCwdPath(options.cwd), getTime: options.getTime ?? (() => new Date()), reportError: options.reportError ?? (() => {}), transports: options.transports ?? [ @@ -48,7 +45,6 @@ export class Logger implements ILogger { }, ], } - this.cwd = join(this.options.cwd, '/') this.logLevel = LEVEL[this.options.logLevel] } @@ -193,7 +189,7 @@ export class Logger implements ILogger { const parsed = parseLogArguments(args) return { ...parsed, - resolvedError: parsed.error ? resolveError(parsed.error, this.cwd) : undefined, + resolvedError: parsed.error ? resolveError(parsed.error, this.options.cwd) : undefined, level, time: this.options.getTime(), service: tagService(this.options.service, this.options.tag), @@ -233,4 +229,12 @@ export class Logger implements ILogger { } } } + + private getCwdPath(cwd: string | undefined): string { + if (typeof window !== 'undefined') { + return cwd ?? '' + } + const path = require('node:path') + return path.join(cwd ?? process.cwd(), '/') + } } From f8e91d126a0d4ea5a58d7765b6fd91ee88248abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 10:15:41 +0100 Subject: [PATCH 07/22] Remove logger from common-nodejs --- packages/common-nodejs/package.json | 15 - .../src/logger/LogFormatterJson.ts | 20 - .../src/logger/LogFormatterPretty.ts | 125 ------- packages/common-nodejs/src/logger/LogLevel.ts | 6 - .../common-nodejs/src/logger/LogThrottle.ts | 74 ---- .../common-nodejs/src/logger/Logger.test.ts | 346 ------------------ packages/common-nodejs/src/logger/Logger.ts | 236 ------------ packages/common-nodejs/src/logger/README.md | 2 - packages/common-nodejs/src/logger/index.ts | 6 - .../src/logger/parseLogArguments.test.ts | 237 ------------ .../src/logger/parseLogArguments.ts | 59 --- .../common-nodejs/src/logger/resolveError.ts | 28 -- .../common-nodejs/src/logger/tagService.ts | 4 - packages/common-nodejs/src/logger/types.ts | 39 -- packages/common-nodejs/src/logger/utils.ts | 20 - packages/common-nodejs/src/tracer/Tracer.ts | 2 +- pnpm-lock.yaml | 6 - 17 files changed, 1 insertion(+), 1224 deletions(-) delete mode 100644 packages/common-nodejs/src/logger/LogFormatterJson.ts delete mode 100644 packages/common-nodejs/src/logger/LogFormatterPretty.ts delete mode 100644 packages/common-nodejs/src/logger/LogLevel.ts delete mode 100644 packages/common-nodejs/src/logger/LogThrottle.ts delete mode 100644 packages/common-nodejs/src/logger/Logger.test.ts delete mode 100644 packages/common-nodejs/src/logger/Logger.ts delete mode 100644 packages/common-nodejs/src/logger/README.md delete mode 100644 packages/common-nodejs/src/logger/index.ts delete mode 100644 packages/common-nodejs/src/logger/parseLogArguments.test.ts delete mode 100644 packages/common-nodejs/src/logger/parseLogArguments.ts delete mode 100644 packages/common-nodejs/src/logger/resolveError.ts delete mode 100644 packages/common-nodejs/src/logger/tagService.ts delete mode 100644 packages/common-nodejs/src/logger/types.ts delete mode 100644 packages/common-nodejs/src/logger/utils.ts diff --git a/packages/common-nodejs/package.json b/packages/common-nodejs/package.json index 8eac63c06..6305f7f44 100644 --- a/packages/common-nodejs/package.json +++ b/packages/common-nodejs/package.json @@ -23,18 +23,6 @@ "default": "./dist/cjs/env/index.js" } }, - "./logger": { - "import": { - "@marsfoundation/local-spark-monorepo": "./src/logger/index.ts", - "types": "./dist/types/logger/index.d.ts", - "default": "./dist/esm/logger/index.js" - }, - "require": { - "@marsfoundation/local-spark-monorepo": "./src/logger/index.ts", - "types": "./dist/types/logger/index.d.ts", - "default": "./dist/cjs/logger/index.js" - } - }, "./tracer": { "import": { "@marsfoundation/local-spark-monorepo": "./src/tracer/index.ts", @@ -51,7 +39,6 @@ "typesVersions": { "*": { "env": ["dist/types/env/index.d.ts"], - "logger": ["dist/types/logger/index.d.ts"], "tracer": ["dist/types/tracer/index.d.ts"] } }, @@ -81,9 +68,7 @@ "tinyspy": "^3.0.2" }, "dependencies": { - "chalk": "^5.3.0", "dotenv": "^16.3.1", - "error-stack-parser": "^2.1.4", "pretty-ms": "^9.2.0", "uuid": "^11.0.3" }, diff --git a/packages/common-nodejs/src/logger/LogFormatterJson.ts b/packages/common-nodejs/src/logger/LogFormatterJson.ts deleted file mode 100644 index 631da7d1b..000000000 --- a/packages/common-nodejs/src/logger/LogFormatterJson.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { LogEntry, LogFormatter } from './types.js' -import { toJSON } from './utils.js' - -export class LogFormatterJson implements LogFormatter { - public format(entry: LogEntry): string { - const core = { - time: entry.time.toISOString(), - level: entry.level, - service: entry.service, - message: entry.message, - error: entry.resolvedError, - } - - try { - return toJSON({ ...core, parameters: entry.parameters }) - } catch { - return toJSON({ ...core }) - } - } -} diff --git a/packages/common-nodejs/src/logger/LogFormatterPretty.ts b/packages/common-nodejs/src/logger/LogFormatterPretty.ts deleted file mode 100644 index 4a251f4f8..000000000 --- a/packages/common-nodejs/src/logger/LogFormatterPretty.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { inspect } from 'node:util' -import chalk from 'chalk' - -import { LogLevel } from './LogLevel.js' -import { LogEntry, LogFormatter } from './types.js' -import { toJSON } from './utils.js' - -const STYLES = { - bigint: 'white', - boolean: 'white', - date: 'white', - module: 'white', - name: 'blue', - null: 'white', - number: 'white', - regexp: 'white', - special: 'white', - string: 'white', - symbol: 'white', - undefined: 'white', -} - -const INDENT_SIZE = 4 -const INDENT = ' '.repeat(INDENT_SIZE) - -interface Options { - colors: boolean - utc: boolean -} - -// @note: format methods are protected to allow easy construction of simpler formatters for non-standard environments -export class LogFormatterPretty implements LogFormatter { - protected readonly options: Options - - constructor(options?: Partial) { - this.options = { - colors: options?.colors ?? true, - utc: options?.utc ?? false, - } - } - - public format(entry: LogEntry): string { - const timeOut = this.formatTimePretty(entry.time, this.options.utc, this.options.colors) - const levelOut = this.formatLevelPretty(entry.level, this.options.colors) - const serviceOut = this.formatServicePretty(entry.service, this.options.colors) - const messageOut = entry.message ? ` ${entry.message}` : '' - const paramsOut = this.formatParametersPretty( - this.sanitize(entry.resolvedError ? { ...entry.resolvedError, ...entry.parameters } : (entry.parameters ?? {})), - this.options.colors, - ) - - return `${timeOut} ${levelOut}${serviceOut}${messageOut}${paramsOut}` - } - - protected formatLevelPretty(level: LogLevel, colors: boolean): string { - if (colors) { - switch (level) { - case 'CRITICAL': - case 'ERROR': - return chalk.red(chalk.bold(level.toUpperCase())) - case 'WARN': - return chalk.yellow(chalk.bold(level.toUpperCase())) - case 'INFO': - return chalk.green(chalk.bold(level.toUpperCase())) - case 'DEBUG': - return chalk.magenta(chalk.bold(level.toUpperCase())) - case 'TRACE': - return chalk.gray(chalk.bold(level.toUpperCase())) - } - } - return level.toUpperCase() - } - - protected formatTimePretty(now: Date, utc: boolean, colors: boolean): string { - const h = (utc ? now.getUTCHours() : now.getHours()).toString().padStart(2, '0') - const m = (utc ? now.getUTCMinutes() : now.getMinutes()).toString().padStart(2, '0') - const s = (utc ? now.getUTCSeconds() : now.getSeconds()).toString().padStart(2, '0') - const ms = (utc ? now.getUTCMilliseconds() : now.getMilliseconds()).toString().padStart(3, '0') - - let result = `${h}:${m}:${s}.${ms}` - if (utc) { - result += 'Z' - } - - return colors ? chalk.gray(result) : result - } - - protected formatParametersPretty(parameters: object, colors: boolean): string { - const oldStyles = inspect.styles - inspect.styles = STYLES - - const inspected = inspect(parameters, { - colors, - breakLength: 80 - INDENT_SIZE, - depth: 5, - }) - - inspect.styles = oldStyles - - if (inspected === '{}') { - return '' - } - - const indented = inspected - .split('\n') - .map((x) => INDENT + x) - .join('\n') - - if (colors) { - return `\n${chalk.gray(indented)}` - } - return `\n${indented}` - } - - protected formatServicePretty(service: string | undefined, colors: boolean): string { - if (!service) { - return '' - } - return colors ? ` ${chalk.gray('[')} ${chalk.yellow(service)} ${chalk.gray(']')}` : ` [ ${service} ]` - } - - protected sanitize(parameters: object): object { - return JSON.parse(toJSON(parameters)) - } -} diff --git a/packages/common-nodejs/src/logger/LogLevel.ts b/packages/common-nodejs/src/logger/LogLevel.ts deleted file mode 100644 index 6d7e0be86..000000000 --- a/packages/common-nodejs/src/logger/LogLevel.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const LOG_LEVELS = ['NONE', 'CRITICAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'] as const -export type LogLevel = (typeof LOG_LEVELS)[number] -export const LEVEL = {} as Record -for (const [index, level] of LOG_LEVELS.entries()) { - LEVEL[level] = index -} diff --git a/packages/common-nodejs/src/logger/LogThrottle.ts b/packages/common-nodejs/src/logger/LogThrottle.ts deleted file mode 100644 index 51b9f69fe..000000000 --- a/packages/common-nodejs/src/logger/LogThrottle.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { LogLevel } from './LogLevel.js' - -export interface Printer { - print(logLevel: LogLevel, service: string | undefined, message: string | undefined, parameters: object): void -} - -export interface LogThrottleOptions { - readonly callsUntilThrottle: number - readonly clearIntervalMs: number - readonly throttleTimeMs: number -} - -export class LogThrottle { - constructor( - private readonly printer: Printer, - private readonly options: LogThrottleOptions, - ) {} - - private callCount: Record = {} - private throttleCount: Record = {} - private started = false - - throttle(logLevel: LogLevel, service: string | undefined, message: string | undefined): boolean { - this.start() - - const key = `${logLevel}:${service ?? ''}:${message ?? ''}` - let isThrottling = key in this.throttleCount - - if (!isThrottling) { - const count = (this.callCount[key] ?? 0) + 1 - this.callCount[key] = count - - if (count > this.options.callsUntilThrottle) { - delete this.callCount[key] - - isThrottling = true - this.waitAndClear(key, logLevel, service, message) - } - } - - if (isThrottling) { - this.throttleCount[key] = (this.throttleCount[key] ?? 0) + 1 - } - - return isThrottling - } - - private waitAndClear( - key: string, - logLevel: LogLevel, - service: string | undefined, - message: string | undefined, - ): void { - setTimeout(() => { - const messageCount = this.throttleCount[key] ?? 0 - delete this.throttleCount[key] - this.printer.print(logLevel, service, `Throttled ${JSON.stringify(message ?? '')}`, { - messageCount, - throttleTimeMs: this.options.throttleTimeMs, - }) - }, this.options.throttleTimeMs) - } - - private start(): void { - if (this.started) { - return - } - - this.started = true - setInterval(() => { - this.callCount = {} - }, this.options.clearIntervalMs) - } -} diff --git a/packages/common-nodejs/src/logger/Logger.test.ts b/packages/common-nodejs/src/logger/Logger.test.ts deleted file mode 100644 index d4ec36347..000000000 --- a/packages/common-nodejs/src/logger/Logger.test.ts +++ /dev/null @@ -1,346 +0,0 @@ -/* eslint-disable */ -import { expect, formatCompact, mockFn } from 'earl' - -import { LogFormatterJson } from './LogFormatterJson.js' -import { LogFormatterPretty } from './LogFormatterPretty.js' -import { Logger } from './Logger.js' -import { LogEntry } from './types.js' - -describe(Logger.name, () => { - it('calls correct transport', () => { - const transport = createTestTransport() - const logger = new Logger({ - transports: [ - { - transport: transport, - formatter: new LogFormatterJson(), - }, - ], - logLevel: 'TRACE', - }) - - logger.trace('foo') - logger.debug('foo') - expect(transport.debug).toHaveBeenCalledTimes(2) - - logger.info('foo') - expect(transport.log).toHaveBeenCalledTimes(1) - - logger.warn('foo') - expect(transport.warn).toHaveBeenCalledTimes(1) - - logger.error('foo') - logger.critical('foo') - expect(transport.error).toHaveBeenCalledTimes(2) - }) - - it('supports bigint values in json output', () => { - const transport = createTestTransport() - const logger = new Logger({ - transports: [ - { - transport: transport, - formatter: new LogFormatterJson(), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - - logger.info({ foo: 123n, bar: [4n, 56n] }) - expect(transport.log).toHaveBeenOnlyCalledWith( - JSON.stringify({ - time: '1970-01-01T00:00:00.000Z', - level: 'INFO', - parameters: { - foo: '123', - bar: ['4', '56'], - }, - }), - ) - }) - - it('supports bigint values in pretty output', () => { - const transport = createTestTransport() - const logger = new Logger({ - transports: [ - { - transport: transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - - logger.info({ foo: 123n, bar: [4n, 56n] }) - const lines = ['00:00:00.000Z INFO\n', " { foo: '123', bar: [ '4', '56' ] }", ''] - expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) - }) - - it('marks promised values in pretty output', () => { - const transport = createTestTransport() - const logger = new Logger({ - transports: [ - { - transport: transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - - logger.info({ test: Promise.resolve(1234) }) - const lines = ['00:00:00.000Z INFO\n', " { test: 'Promise' }", ''] - expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) - }) - - describe('for', () => { - function setup() { - const transport = createTestTransport() - const baseLogger = new Logger({ - transports: [ - { - transport: transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - return { transport, baseLogger } - } - - it('single service (string)', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.for('FooService') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') - }) - - it('single service (object)', () => { - const { transport, baseLogger } = setup() - - class FooService {} - const instance = new FooService() - const logger = baseLogger.for(instance) - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') - }) - - it('service with member', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.for('FooService').for('queue') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue ] hello') - }) - - it('service with tag', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.tag('Red').for('FooService') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService:Red ] hello') - }) - - it('service with tag and member', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.tag('Red').for('FooService').for('queue') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue:Red ] hello') - }) - - it('lone tag', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.tag('Red') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ :Red ] hello') - }) - }) - - describe('error reporting', () => { - const oldConsoleError = console.error - beforeEach(() => { - console.error = () => {} - }) - afterEach(() => { - console.error = oldConsoleError - }) - - it('reports error and critical error', () => { - const mockReportError = mockFn((_: unknown) => {}) - const logger = new Logger({ - reportError: mockReportError, - }) - - logger.error('foo') - logger.critical('bar') - - expect(mockReportError).toHaveBeenNthCalledWith(1, { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: 'foo', - parameters: undefined, - error: undefined, - resolvedError: undefined, - }) - expect(mockReportError).toHaveBeenNthCalledWith(2, { - level: 'CRITICAL', - time: expect.a(Date), - service: undefined, - message: 'bar', - parameters: undefined, - error: undefined, - resolvedError: undefined, - }) - }) - - describe('usage patterns', () => { - const patterns: [unknown[], LogEntry][] = [ - [ - ['message'], - { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: 'message', - parameters: undefined, - error: undefined, - resolvedError: undefined, - }, - ], - [ - [new Error('message')], - { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: undefined, - parameters: undefined, - error: new Error('message'), - resolvedError: { - name: 'Error', - error: 'message', - stack: expect.a(Array), - }, - }, - ], - [ - ['foo', new Error('bar')], - { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: 'foo', - parameters: undefined, - error: new Error('bar'), - resolvedError: { - name: 'Error', - error: 'bar', - stack: expect.a(Array), - }, - }, - ], - [ - [{ x: 1, y: 2 }], - { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: undefined, - parameters: { x: 1, y: 2 }, - error: undefined, - resolvedError: undefined, - }, - ], - [ - ['message', { x: 1, y: 2 }], - { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: 'message', - parameters: { x: 1, y: 2 }, - error: undefined, - resolvedError: undefined, - }, - ], - [ - [{ x: 1, y: 2, message: 'message' }], - { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: 'message', - parameters: { x: 1, y: 2 }, - error: undefined, - resolvedError: undefined, - }, - ], - [ - [{ x: 1, y: 2, message: true }], - { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: undefined, - parameters: { x: 1, y: 2, message: true }, - error: undefined, - resolvedError: undefined, - }, - ], - [ - [new Error('foo'), 'bar', { x: 1, y: 2 }], - { - level: 'ERROR', - time: expect.a(Date), - service: undefined, - message: 'bar', - parameters: { x: 1, y: 2 }, - error: new Error('foo'), - resolvedError: { - name: 'Error', - error: 'foo', - stack: expect.a(Array), - }, - }, - ], - ] - - for (const [args, expected] of patterns) { - it(`supports ${formatCompact(args, 60)}`, () => { - const mockReportError = mockFn((_: unknown) => {}) - const logger = new Logger({ reportError: mockReportError }) - - logger.error(...args) - expect(mockReportError).toHaveBeenOnlyCalledWith(expected) - }) - } - }) - }) -}) - -function createTestTransport() { - return { - debug: mockFn((_: string): void => {}), - log: mockFn((_: string): void => {}), - warn: mockFn((_: string): void => {}), - error: mockFn((_: string): void => {}), - } -} diff --git a/packages/common-nodejs/src/logger/Logger.ts b/packages/common-nodejs/src/logger/Logger.ts deleted file mode 100644 index 0dfec6a2f..000000000 --- a/packages/common-nodejs/src/logger/Logger.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { join } from 'node:path' - -import { assertNever } from '@marsfoundation/common-universal' -import { LogFormatterJson } from './LogFormatterJson.js' -import { LogFormatterPretty } from './LogFormatterPretty.js' -import { LEVEL, LogLevel } from './LogLevel.js' -import { LogThrottle, LogThrottleOptions } from './LogThrottle.js' -import { parseLogArguments } from './parseLogArguments.js' -import { resolveError } from './resolveError.js' -import { tagService } from './tagService.js' -import { LogEntry, LoggerOptions } from './types.js' - -export interface ILogger { - critical(...args: unknown[]): void - error(...args: unknown[]): void - warn(...args: unknown[]): void - info(...args: unknown[]): void - debug(...args: unknown[]): void - trace(...args: unknown[]): void - - configure(options: Partial): ILogger - for(object: {} | string): ILogger - tag(tag: string | undefined): ILogger -} - -/** - * [Read full documentation](https://github.com/l2beat/tools/blob/master/packages/backend-tools/src/logger/docs.md) - */ -export class Logger implements ILogger { - private readonly options: LoggerOptions - private readonly logLevel: number - private readonly cwd: string - private throttle?: LogThrottle - - constructor(options: Partial) { - this.options = { - logLevel: options.logLevel ?? 'INFO', - service: options.service, - tag: options.tag, - utc: options.utc ?? false, - cwd: options.cwd ?? process.cwd(), - getTime: options.getTime ?? (() => new Date()), - reportError: options.reportError ?? (() => {}), - transports: options.transports ?? [ - { - transport: console, - formatter: new LogFormatterJson(), - }, - ], - } - this.cwd = join(this.options.cwd, '/') - this.logLevel = LEVEL[this.options.logLevel] - } - - static SILENT = new Logger({ logLevel: 'NONE' }) - - static CRITICAL = new Logger({ - logLevel: 'CRITICAL', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static ERROR = new Logger({ - logLevel: 'ERROR', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static WARN = new Logger({ - logLevel: 'WARN', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static INFO = new Logger({ - logLevel: 'INFO', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static DEBUG = new Logger({ - logLevel: 'DEBUG', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static TRACE = new Logger({ - logLevel: 'TRACE', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - configure(options: Partial): Logger { - const logger = new Logger({ ...this.options, ...options }) - logger.throttle = this.throttle - return logger - } - - for(object: {} | string): Logger { - const name = typeof object === 'string' ? object : object.constructor.name - return this.configure({ - service: this.options.service ? `${this.options.service}.${name}` : name, - }) - } - - tag(tag: string | undefined): Logger { - return this.configure({ tag }) - } - - withThrottling(options: LogThrottleOptions): Logger { - const logger = new Logger(this.options) - logger.throttle = new LogThrottle( - { - print: (level, service, message, parameters) => - logger.print({ - time: logger.options.getTime(), - level, - service, - message, - parameters, - }), - }, - options, - ) - return logger - } - - critical(...args: unknown[]): void { - if (this.logLevel >= LEVEL.CRITICAL) { - const parsed = this.parseEntry('CRITICAL', args) - this.print(parsed) - this.options.reportError(parsed) - } - } - - error(...args: unknown[]): void { - if (this.logLevel >= LEVEL.ERROR) { - const entry = this.parseEntry('ERROR', args) - this.print(entry) - this.options.reportError(entry) - } - } - - warn(...args: unknown[]): void { - if (this.logLevel >= LEVEL.WARN) { - this.print(this.parseEntry('WARN', args)) - } - } - - info(...args: unknown[]): void { - if (this.logLevel >= LEVEL.INFO) { - this.print(this.parseEntry('INFO', args)) - } - } - - debug(...args: unknown[]): void { - if (this.logLevel >= LEVEL.DEBUG) { - this.print(this.parseEntry('DEBUG', args)) - } - } - - trace(...args: unknown[]): void { - if (this.logLevel >= LEVEL.TRACE) { - this.print(this.parseEntry('TRACE', args)) - } - } - - private parseEntry(level: LogLevel, args: unknown[]): LogEntry { - const parsed = parseLogArguments(args) - return { - ...parsed, - resolvedError: parsed.error ? resolveError(parsed.error, this.cwd) : undefined, - level, - time: this.options.getTime(), - service: tagService(this.options.service, this.options.tag), - } - } - - private print(entry: LogEntry): void { - if (this.throttle?.throttle(entry.level, entry.service, entry.message)) { - return - } - - this.printExactly(entry) - } - - private printExactly(entry: LogEntry): void { - for (const transportOptions of this.options.transports) { - const output = transportOptions.formatter.format(entry) - switch (entry.level) { - case 'CRITICAL': - case 'ERROR': - transportOptions.transport.error(output) - break - case 'WARN': - transportOptions.transport.warn(output) - break - case 'INFO': - transportOptions.transport.log(output) - break - case 'DEBUG': - case 'TRACE': - transportOptions.transport.debug(output) - break - case 'NONE': - break - default: - assertNever(entry.level) - } - } - } -} diff --git a/packages/common-nodejs/src/logger/README.md b/packages/common-nodejs/src/logger/README.md deleted file mode 100644 index 2bff0bd41..000000000 --- a/packages/common-nodejs/src/logger/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Based on https://github.com/l2beat/l2beat -License: MIT diff --git a/packages/common-nodejs/src/logger/index.ts b/packages/common-nodejs/src/logger/index.ts deleted file mode 100644 index e53fbffd2..000000000 --- a/packages/common-nodejs/src/logger/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './Logger.js' -export * from './LogLevel.js' -export * from './types.js' -export * from './LogFormatterPretty.js' -export * from './LogFormatterJson.js' -export * from './LogThrottle.js' diff --git a/packages/common-nodejs/src/logger/parseLogArguments.test.ts b/packages/common-nodejs/src/logger/parseLogArguments.test.ts deleted file mode 100644 index fde6f003b..000000000 --- a/packages/common-nodejs/src/logger/parseLogArguments.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { expect, formatCompact } from 'earl' - -import { ParsedLogArguments, parseLogArguments } from './parseLogArguments.js' - -describe(parseLogArguments.name, () => { - const patterns: [unknown[], ParsedLogArguments][] = [ - [ - [], - { - message: undefined, - error: undefined, - parameters: undefined, - }, - ], - [ - ['message'], - { - message: 'message', - error: undefined, - parameters: undefined, - }, - ], - [ - // passing another string causes it to be treated as a regular value - ['foo', 'bar'], - { - message: 'foo', - error: undefined, - parameters: { value: 'bar' }, - }, - ], - [ - // without a message, we extract it from parameters.message - [{ message: 'message' }], - { - message: 'message', - error: undefined, - parameters: undefined, - }, - ], - [ - // only parameters.message works - [{ notMessage: 'message' }], - { - message: undefined, - error: undefined, - parameters: { notMessage: 'message' }, - }, - ], - [ - // in case we have a string value, message in parameters is ignored - ['foo', { message: 'bar' }], - { - message: 'foo', - error: undefined, - parameters: { message: 'bar' }, - }, - ], - [ - // in case we have a string value, message in parameters is ignored - [{ message: 'foo' }, 'bar'], - { - message: 'bar', - error: undefined, - parameters: { message: 'foo' }, - }, - ], - [ - ['foo', 'bar', 'baz'], - { - message: 'foo', - error: undefined, - parameters: { values: ['bar', 'baz'] }, - }, - ], - [ - [new Error('error')], - { - message: undefined, - error: new Error('error'), - parameters: undefined, - }, - ], - [ - [new Error('foo'), new Error('bar')], - { - message: undefined, - error: new Error('foo'), - parameters: { value: new Error('bar') }, - }, - ], - [ - [new Error('foo'), new Error('bar'), new Error('baz')], - { - message: undefined, - error: new Error('foo'), - parameters: { values: [new Error('bar'), new Error('baz')] }, - }, - ], - [ - ['message', new Error('error')], - { - message: 'message', - error: new Error('error'), - parameters: undefined, - }, - ], - [ - [new Error('error'), 'message'], - { - message: 'message', - error: new Error('error'), - parameters: undefined, - }, - ], - [ - // without an error, we extract it from parameters.error - [{ error: new Error('error') }], - { - message: undefined, - error: new Error('error'), - parameters: undefined, - }, - ], - [ - // only parameters.error works - [{ notError: new Error('error') }], - { - message: undefined, - error: undefined, - parameters: { notError: new Error('error') }, - }, - ], - [ - ['message', new Error('error'), { foo: 'bar' }], - { - message: 'message', - error: new Error('error'), - parameters: { foo: 'bar' }, - }, - ], - [ - [123], - { - message: undefined, - error: undefined, - parameters: { value: 123 }, - }, - ], - [ - [123, 45], - { - message: undefined, - error: undefined, - parameters: { values: [123, 45] }, - }, - ], - [ - [[123, 45]], - { - message: undefined, - error: undefined, - parameters: { value: [123, 45] }, - }, - ], - [ - // we allow parameters to override the value field - [123, { value: 45 }], - { - message: undefined, - error: undefined, - parameters: { value: 45 }, - }, - ], - [ - // we allow parameters to override the values field - [1, 2, 3, { values: 42 }], - { - message: undefined, - error: undefined, - parameters: { values: 42 }, - }, - ], - [ - [{}], - { - message: undefined, - error: undefined, - parameters: undefined, - }, - ], - [ - [{ foo: 'bar', baz: true }], - { - message: undefined, - error: undefined, - parameters: { foo: 'bar', baz: true }, - }, - ], - [ - [ - { foo: 'bar', baz: true }, - { x: 1, y: 2 }, - ], - { - message: undefined, - error: undefined, - parameters: { foo: 'bar', baz: true, x: 1, y: 2 }, - }, - ], - [ - [ - { foo: 'bar', baz: true }, - { x: 1, y: 2, baz: false }, - ], - { - message: undefined, - error: undefined, - parameters: { foo: 'bar', baz: false, x: 1, y: 2 }, - }, - ], - [ - [123, { foo: 'bar', baz: true }, { x: 4, y: 5 }], - { - message: undefined, - error: undefined, - parameters: { foo: 'bar', baz: true, x: 4, y: 5, value: 123 }, - }, - ], - ] - - for (const [args, expected] of patterns) { - it(`parses ${formatCompact(args, 60)}`, () => { - expect(parseLogArguments(args)).toEqual(expected) - }) - } -}) diff --git a/packages/common-nodejs/src/logger/parseLogArguments.ts b/packages/common-nodejs/src/logger/parseLogArguments.ts deleted file mode 100644 index 79cbd91d9..000000000 --- a/packages/common-nodejs/src/logger/parseLogArguments.ts +++ /dev/null @@ -1,59 +0,0 @@ -export interface ParsedLogArguments { - message?: string - error?: Error - parameters?: object -} - -export function parseLogArguments(args: unknown[]): ParsedLogArguments { - let message: string | undefined - let error: Error | undefined - const values: unknown[] = [] - let parameters = {} - - for (const arg of args) { - if (typeof arg === 'string') { - if (message === undefined) { - message = arg - } else { - values.push(arg) - } - } else if (arg instanceof Error) { - if (error === undefined) { - error = arg - } else { - values.push(arg) - } - } else if (typeof arg !== 'object' || arg === null || Array.isArray(arg)) { - values.push(arg) - } else { - parameters = { ...parameters, ...arg } - } - } - - // place values in parameters - if (values.length === 1) { - parameters = { value: values[0], ...parameters } - } else if (values.length > 1) { - parameters = { values, ...parameters } - } - - // optionally extract message from parameters - const parameterMessage: unknown = Reflect.get(parameters, 'message') - if (message === undefined && typeof parameterMessage === 'string') { - message = parameterMessage - Reflect.deleteProperty(parameters, 'message') - } - - // optionally extract error from parameters - const parameterError: unknown = Reflect.get(parameters, 'error') - if (error === undefined && parameterError instanceof Error) { - error = parameterError - Reflect.deleteProperty(parameters, 'error') - } - - return { - message, - error, - parameters: Object.keys(parameters).length > 0 ? parameters : undefined, - } -} diff --git a/packages/common-nodejs/src/logger/resolveError.ts b/packages/common-nodejs/src/logger/resolveError.ts deleted file mode 100644 index bc550d778..000000000 --- a/packages/common-nodejs/src/logger/resolveError.ts +++ /dev/null @@ -1,28 +0,0 @@ -import ErrorStackParser from 'error-stack-parser' - -export interface ResolvedError { - name: string - error: string - stack: string[] -} - -export function resolveError(error: Error, cwd: string): ResolvedError { - return { - name: error.name, - error: error.message, - stack: ErrorStackParser.parse(error).map((frame) => formatFrame(frame, cwd)), - } -} - -function formatFrame(frame: StackFrame, cwd: string): string { - const file = frame.fileName?.startsWith(cwd) ? frame.fileName.slice(cwd.length) : frame.fileName - const functionName = frame.functionName ? `${frame.functionName} ` : '' - - const fileLocation = - frame.lineNumber !== undefined && frame.columnNumber !== undefined - ? `:${frame.lineNumber}:${frame.columnNumber}` - : '' - const location = file !== undefined ? `(${file}${fileLocation})` : '' - - return `${functionName}${location}` -} diff --git a/packages/common-nodejs/src/logger/tagService.ts b/packages/common-nodejs/src/logger/tagService.ts deleted file mode 100644 index 19486c859..000000000 --- a/packages/common-nodejs/src/logger/tagService.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function tagService(service: string | undefined, tag: string | undefined): string | undefined { - const concat = (service ?? '') + (tag ? `:${tag}` : '') - return concat ? concat : undefined -} diff --git a/packages/common-nodejs/src/logger/types.ts b/packages/common-nodejs/src/logger/types.ts deleted file mode 100644 index 20ec6001a..000000000 --- a/packages/common-nodejs/src/logger/types.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { LogLevel } from './LogLevel.js' -import { ResolvedError } from './resolveError.js' - -export interface LoggerTransport { - debug(message: string): void - log(message: string): void - warn(message: string): void - error(message: string): void -} - -export interface LogFormatter { - format(entry: LogEntry): string -} - -export interface LoggerTransportOptions { - transport: LoggerTransport - formatter: LogFormatter -} - -export interface LoggerOptions { - logLevel: LogLevel - service?: string - tag?: string - utc: boolean - cwd: string - getTime: () => Date - reportError: (entry: LogEntry) => void - transports: LoggerTransportOptions[] -} - -export interface LogEntry { - level: LogLevel - time: Date - service?: string - message?: string - error?: Error - resolvedError?: ResolvedError - parameters?: object -} diff --git a/packages/common-nodejs/src/logger/utils.ts b/packages/common-nodejs/src/logger/utils.ts deleted file mode 100644 index 3fdc887cb..000000000 --- a/packages/common-nodejs/src/logger/utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-disable */ -export function toJSON(parameters: object): string { - return JSON.stringify(parameters, (_k, v: unknown) => { - if (typeof v === 'bigint') { - return v.toString() - } - - // uses a generic check to catch any promise-like objects (e.g. Promise, Bluebird, etc.) - const isPromise = - typeof v === 'object' && - v !== null && - typeof (v as any).then === 'function' && - typeof (v as any).catch === 'function' - if (isPromise) { - return 'Promise' - } - - return v - }) -} diff --git a/packages/common-nodejs/src/tracer/Tracer.ts b/packages/common-nodejs/src/tracer/Tracer.ts index 763e6c9aa..2f1660e2b 100644 --- a/packages/common-nodejs/src/tracer/Tracer.ts +++ b/packages/common-nodejs/src/tracer/Tracer.ts @@ -1,5 +1,5 @@ +import { ILogger } from '@marsfoundation/common-universal' import prettyMilliseconds from 'pretty-ms' -import { ILogger } from '../logger/index.js' interface TracerOptions { service?: string diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef91397d0..f44e71579 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -377,15 +377,9 @@ importers: '@marsfoundation/common-universal': specifier: workspace:^ version: link:../common-universal - chalk: - specifier: ^5.3.0 - version: 5.3.0 dotenv: specifier: ^16.3.1 version: 16.3.1 - error-stack-parser: - specifier: ^2.1.4 - version: 2.1.4 pretty-ms: specifier: ^9.2.0 version: 9.2.0 From 8838901edfd746a2f3119b9a96fd3e5cb3ef52dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 10:44:31 +0100 Subject: [PATCH 08/22] Revert changes to logger --- packages/common-universal/src/logger/Logger.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/common-universal/src/logger/Logger.ts b/packages/common-universal/src/logger/Logger.ts index 74bbb01de..0dfec6a2f 100644 --- a/packages/common-universal/src/logger/Logger.ts +++ b/packages/common-universal/src/logger/Logger.ts @@ -1,3 +1,5 @@ +import { join } from 'node:path' + import { assertNever } from '@marsfoundation/common-universal' import { LogFormatterJson } from './LogFormatterJson.js' import { LogFormatterPretty } from './LogFormatterPretty.js' @@ -27,6 +29,7 @@ export interface ILogger { export class Logger implements ILogger { private readonly options: LoggerOptions private readonly logLevel: number + private readonly cwd: string private throttle?: LogThrottle constructor(options: Partial) { @@ -35,7 +38,7 @@ export class Logger implements ILogger { service: options.service, tag: options.tag, utc: options.utc ?? false, - cwd: this.getCwdPath(options.cwd), + cwd: options.cwd ?? process.cwd(), getTime: options.getTime ?? (() => new Date()), reportError: options.reportError ?? (() => {}), transports: options.transports ?? [ @@ -45,6 +48,7 @@ export class Logger implements ILogger { }, ], } + this.cwd = join(this.options.cwd, '/') this.logLevel = LEVEL[this.options.logLevel] } @@ -189,7 +193,7 @@ export class Logger implements ILogger { const parsed = parseLogArguments(args) return { ...parsed, - resolvedError: parsed.error ? resolveError(parsed.error, this.options.cwd) : undefined, + resolvedError: parsed.error ? resolveError(parsed.error, this.cwd) : undefined, level, time: this.options.getTime(), service: tagService(this.options.service, this.options.tag), @@ -229,12 +233,4 @@ export class Logger implements ILogger { } } } - - private getCwdPath(cwd: string | undefined): string { - if (typeof window !== 'undefined') { - return cwd ?? '' - } - const path = require('node:path') - return path.join(cwd ?? process.cwd(), '/') - } } From 5b05a79d0b1a9f4527b36e9b6de57c5fcbada829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 10:44:41 +0100 Subject: [PATCH 09/22] Add path to vite.config --- packages/app/vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 4867fbff2..4ebee2808 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -28,8 +28,8 @@ export default defineConfig({ // nodePolyfills needs to be first nodePolyfills({ // buffer is needed when connecting with a coinbase wallet installed on a phone - // util is needed for common-universal's logger - include: ['buffer', 'util'], + // util and path is needed for common-universal's logger + include: ['buffer', 'util', 'path'], }), react(), tsconfigPaths(), From 3918cd21ac8e2f2d5ee32f98b2f5d644a6676ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 10:52:29 +0100 Subject: [PATCH 10/22] Move tracer to common-universal --- packages/common-nodejs/package.json | 1 - packages/common-universal/package.json | 3 +- .../src/tracer/Tracer.mock.ts | 16 +++++++++ .../common-universal/src/tracer/Tracer.ts | 36 +++++++++++++++++++ packages/common-universal/src/tracer/index.ts | 2 ++ pnpm-lock.yaml | 6 ++-- 6 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 packages/common-universal/src/tracer/Tracer.mock.ts create mode 100644 packages/common-universal/src/tracer/Tracer.ts create mode 100644 packages/common-universal/src/tracer/index.ts diff --git a/packages/common-nodejs/package.json b/packages/common-nodejs/package.json index 6305f7f44..3a02fe10b 100644 --- a/packages/common-nodejs/package.json +++ b/packages/common-nodejs/package.json @@ -69,7 +69,6 @@ }, "dependencies": { "dotenv": "^16.3.1", - "pretty-ms": "^9.2.0", "uuid": "^11.0.3" }, "peerDependencies": { diff --git a/packages/common-universal/package.json b/packages/common-universal/package.json index a12823a0c..fb0099a12 100644 --- a/packages/common-universal/package.json +++ b/packages/common-universal/package.json @@ -58,6 +58,7 @@ "error-stack-parser": "^2.1.4", "fetch-retry": "^5.0.6", "ms": "^2.1.3", - "p-limit": "^6.1.0" + "p-limit": "^6.1.0", + "pretty-ms": "^9.2.0" } } diff --git a/packages/common-universal/src/tracer/Tracer.mock.ts b/packages/common-universal/src/tracer/Tracer.mock.ts new file mode 100644 index 000000000..f9c5ed843 --- /dev/null +++ b/packages/common-universal/src/tracer/Tracer.mock.ts @@ -0,0 +1,16 @@ +import { PublicInterface } from '../typeHelpers.js' +import { Tracer } from './Tracer.js' + +export function TracerMock(): Tracer { + return new TracerMockClass() as any +} + +class TracerMockClass implements PublicInterface { + for(_object: {} | string): Tracer { + return this as any + } + + async track(_name: string, fn: () => T): Promise { + return fn() + } +} diff --git a/packages/common-universal/src/tracer/Tracer.ts b/packages/common-universal/src/tracer/Tracer.ts new file mode 100644 index 000000000..2dc57f0be --- /dev/null +++ b/packages/common-universal/src/tracer/Tracer.ts @@ -0,0 +1,36 @@ +import prettyMilliseconds from 'pretty-ms' +import { ILogger } from '../logger/Logger.js' + +interface TracerOptions { + service?: string + externalTrackPerformance: (fullName: string, fn: () => T) => Promise +} + +export class Tracer { + private readonly logger: ILogger + constructor( + logger: ILogger, + private readonly options: TracerOptions, + ) { + this.logger = logger.configure({ service: Tracer.name }) + } + + for(object: {} | string): Tracer { + const name = typeof object === 'string' ? object : object.constructor.name + const service = this.options.service ? `${this.options.service}.${name}` : name + + return new Tracer(this.logger, { ...this.options, service }) + } + + async track(name: string, fn: () => T): Promise { + const fullName = `[${this.options.service}] ${name}` + + const start = Date.now() + const result = await this.options.externalTrackPerformance(fullName, fn) + const durationMs = Date.now() - start + + this.logger.tag(this.options.service).trace(`"${name}" took ${prettyMilliseconds(durationMs)}`) + + return result + } +} diff --git a/packages/common-universal/src/tracer/index.ts b/packages/common-universal/src/tracer/index.ts new file mode 100644 index 000000000..08c9349e5 --- /dev/null +++ b/packages/common-universal/src/tracer/index.ts @@ -0,0 +1,2 @@ +export * from './Tracer.js' +export * from './Tracer.mock.js' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f44e71579..88ece748a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -380,9 +380,6 @@ importers: dotenv: specifier: ^16.3.1 version: 16.3.1 - pretty-ms: - specifier: ^9.2.0 - version: 9.2.0 remeda: specifier: ^2.10.0 version: 2.20.2 @@ -487,6 +484,9 @@ importers: p-limit: specifier: ^6.1.0 version: 6.1.0 + pretty-ms: + specifier: ^9.2.0 + version: 9.2.0 viem: specifier: ^2.0.0 version: 2.22.8(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@6.0.3)(zod@3.24.2) From 357aa0230d791495117bc8b11a63a34b529c29f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 10:57:53 +0100 Subject: [PATCH 11/22] Export logger and tracer separately --- packages/common-universal/package.json | 24 +++++++++++++++++++ packages/common-universal/src/index.ts | 7 ------ packages/common-universal/src/logger/index.ts | 6 +++++ 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 packages/common-universal/src/logger/index.ts diff --git a/packages/common-universal/package.json b/packages/common-universal/package.json index fb0099a12..f43b1ec82 100644 --- a/packages/common-universal/package.json +++ b/packages/common-universal/package.json @@ -25,6 +25,30 @@ "types": "./dist/types/index.d.ts", "default": "./dist/cjs/index.js" } + }, + "./logger": { + "import": { + "@marsfoundation/local-spark-monorepo": "./src/logger/index.ts", + "types": "./dist/types/logger/index.d.ts", + "default": "./dist/esm/logger/index.js" + }, + "require": { + "@marsfoundation/local-spark-monorepo": "./src/logger/index.ts", + "types": "./dist/types/logger/index.d.ts", + "default": "./dist/cjs/logger/index.js" + } + }, + "./tracer": { + "import": { + "@marsfoundation/local-spark-monorepo": "./src/tracer/index.ts", + "types": "./dist/types/tracer/index.d.ts", + "default": "./dist/esm/tracer/index.js" + }, + "require": { + "@marsfoundation/local-spark-monorepo": "./src/tracer/index.ts", + "types": "./dist/types/tracer/index.d.ts", + "default": "./dist/cjs/tracer/index.js" + } } }, "files": ["dist"], diff --git a/packages/common-universal/src/index.ts b/packages/common-universal/src/index.ts index 4cbddf195..3d259aa0f 100644 --- a/packages/common-universal/src/index.ts +++ b/packages/common-universal/src/index.ts @@ -26,13 +26,6 @@ export * from './data/filterOutFalsy.js' export * from './data/reverseDictionary.js' export * from './data/unique.js' -export * from './logger/Logger.js' -export * from './logger/LogLevel.js' -export * from './logger/types.js' -export * from './logger/LogFormatterPretty.js' -export * from './logger/LogFormatterJson.js' -export * from './logger/LogThrottle.js' - export * from './typeHelpers.js' export * from './networking/solidFetch.js' diff --git a/packages/common-universal/src/logger/index.ts b/packages/common-universal/src/logger/index.ts new file mode 100644 index 000000000..e53fbffd2 --- /dev/null +++ b/packages/common-universal/src/logger/index.ts @@ -0,0 +1,6 @@ +export * from './Logger.js' +export * from './LogLevel.js' +export * from './types.js' +export * from './LogFormatterPretty.js' +export * from './LogFormatterJson.js' +export * from './LogThrottle.js' From 377cc210fc2ade74c79c3a0af4d0834591afdb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 11:08:32 +0100 Subject: [PATCH 12/22] Update imports and package.json --- packages/common-nodejs/package.json | 12 ------- .../common-nodejs/src/http/HttpClient.test.ts | 2 +- packages/common-nodejs/src/http/HttpClient.ts | 2 +- .../common-nodejs/src/tracer/Tracer.mock.ts | 16 --------- packages/common-nodejs/src/tracer/Tracer.ts | 36 ------------------- packages/common-nodejs/src/tracer/index.ts | 2 -- .../common-universal/src/logger/Logger.ts | 2 +- 7 files changed, 3 insertions(+), 69 deletions(-) delete mode 100644 packages/common-nodejs/src/tracer/Tracer.mock.ts delete mode 100644 packages/common-nodejs/src/tracer/Tracer.ts delete mode 100644 packages/common-nodejs/src/tracer/index.ts diff --git a/packages/common-nodejs/package.json b/packages/common-nodejs/package.json index 3a02fe10b..f7ec21d5e 100644 --- a/packages/common-nodejs/package.json +++ b/packages/common-nodejs/package.json @@ -22,18 +22,6 @@ "types": "./dist/types/env/index.d.ts", "default": "./dist/cjs/env/index.js" } - }, - "./tracer": { - "import": { - "@marsfoundation/local-spark-monorepo": "./src/tracer/index.ts", - "types": "./dist/types/tracer/index.d.ts", - "default": "./dist/esm/tracer/index.js" - }, - "require": { - "@marsfoundation/local-spark-monorepo": "./src/tracer/index.ts", - "types": "./dist/types/tracer/index.d.ts", - "default": "./dist/cjs/tracer/index.js" - } } }, "typesVersions": { diff --git a/packages/common-nodejs/src/http/HttpClient.test.ts b/packages/common-nodejs/src/http/HttpClient.test.ts index 34c722ea0..a0af6d197 100644 --- a/packages/common-nodejs/src/http/HttpClient.test.ts +++ b/packages/common-nodejs/src/http/HttpClient.test.ts @@ -1,6 +1,6 @@ +import { Logger } from '@marsfoundation/common-universal/logger' import { MockObject, expect, mockFn, mockObject } from 'earl' import { ZodError, z } from 'zod' -import { Logger } from '../logger/index.js' import { HttpClient, HttpError } from './HttpClient.js' import { HttpServerMock, PostBody, getResponseSchema, postBodySchema } from './HttpServer.mock.js' diff --git a/packages/common-nodejs/src/http/HttpClient.ts b/packages/common-nodejs/src/http/HttpClient.ts index 034ba46c7..c6c0e9137 100644 --- a/packages/common-nodejs/src/http/HttpClient.ts +++ b/packages/common-nodejs/src/http/HttpClient.ts @@ -1,6 +1,6 @@ +import { Logger } from '@marsfoundation/common-universal/logger' import { mergeDeep } from 'remeda' import { z } from 'zod' -import { Logger } from '../logger/index.js' import { RetryOptions, defaultRetryOptions, fetchRetry } from './fetchRetry.js' export class HttpClient { diff --git a/packages/common-nodejs/src/tracer/Tracer.mock.ts b/packages/common-nodejs/src/tracer/Tracer.mock.ts deleted file mode 100644 index 1cad919d2..000000000 --- a/packages/common-nodejs/src/tracer/Tracer.mock.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PublicInterface } from '@marsfoundation/common-universal' -import { Tracer } from './Tracer.js' - -export function TracerMock(): Tracer { - return new TracerMockClass() as any -} - -class TracerMockClass implements PublicInterface { - for(_object: {} | string): Tracer { - return this as any - } - - async track(_name: string, fn: () => T): Promise { - return fn() - } -} diff --git a/packages/common-nodejs/src/tracer/Tracer.ts b/packages/common-nodejs/src/tracer/Tracer.ts deleted file mode 100644 index 2f1660e2b..000000000 --- a/packages/common-nodejs/src/tracer/Tracer.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ILogger } from '@marsfoundation/common-universal' -import prettyMilliseconds from 'pretty-ms' - -interface TracerOptions { - service?: string - externalTrackPerformance: (fullName: string, fn: () => T) => Promise -} - -export class Tracer { - private readonly logger: ILogger - constructor( - logger: ILogger, - private readonly options: TracerOptions, - ) { - this.logger = logger.configure({ service: Tracer.name }) - } - - for(object: {} | string): Tracer { - const name = typeof object === 'string' ? object : object.constructor.name - const service = this.options.service ? `${this.options.service}.${name}` : name - - return new Tracer(this.logger, { ...this.options, service }) - } - - async track(name: string, fn: () => T): Promise { - const fullName = `[${this.options.service}] ${name}` - - const start = Date.now() - const result = await this.options.externalTrackPerformance(fullName, fn) - const durationMs = Date.now() - start - - this.logger.tag(this.options.service).trace(`"${name}" took ${prettyMilliseconds(durationMs)}`) - - return result - } -} diff --git a/packages/common-nodejs/src/tracer/index.ts b/packages/common-nodejs/src/tracer/index.ts deleted file mode 100644 index 08c9349e5..000000000 --- a/packages/common-nodejs/src/tracer/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './Tracer.js' -export * from './Tracer.mock.js' diff --git a/packages/common-universal/src/logger/Logger.ts b/packages/common-universal/src/logger/Logger.ts index 0dfec6a2f..bbac0b16f 100644 --- a/packages/common-universal/src/logger/Logger.ts +++ b/packages/common-universal/src/logger/Logger.ts @@ -1,6 +1,6 @@ import { join } from 'node:path' -import { assertNever } from '@marsfoundation/common-universal' +import { assertNever } from '../assert/assertNever.js' import { LogFormatterJson } from './LogFormatterJson.js' import { LogFormatterPretty } from './LogFormatterPretty.js' import { LEVEL, LogLevel } from './LogLevel.js' From 3d5336006db9010af9a27afb4e01faf3d0b0da21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 11:21:17 +0100 Subject: [PATCH 13/22] Update typesVersions --- packages/common-nodejs/package.json | 4 +--- packages/common-universal/package.json | 7 +++++++ pnpm-lock.yaml | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/common-nodejs/package.json b/packages/common-nodejs/package.json index f7ec21d5e..7b3ae67a5 100644 --- a/packages/common-nodejs/package.json +++ b/packages/common-nodejs/package.json @@ -26,8 +26,7 @@ }, "typesVersions": { "*": { - "env": ["dist/types/env/index.d.ts"], - "tracer": ["dist/types/tracer/index.d.ts"] + "env": ["dist/types/env/index.d.ts"] } }, "files": ["dist"], @@ -47,7 +46,6 @@ "@sinonjs/fake-timers": "^13.0.5", "@types/express": "^5.0.0", "@types/mocha": "^10.0.10", - "@types/pretty-ms": "^5.0.1", "@types/sinonjs__fake-timers": "^8.1.5", "@types/uuid": "^10.0.0", "earl": "^1.3.0", diff --git a/packages/common-universal/package.json b/packages/common-universal/package.json index f43b1ec82..452f4c562 100644 --- a/packages/common-universal/package.json +++ b/packages/common-universal/package.json @@ -51,6 +51,12 @@ } } }, + "typesVersions": { + "*": { + "logger": ["dist/types/logger/index.d.ts"], + "tracer": ["dist/types/tracer/index.d.ts"] + } + }, "files": ["dist"], "scripts": { "lint": "eslint src", @@ -73,6 +79,7 @@ "@types/mocha": "^10.0.10", "@types/ms": "^2.1.0", "@types/node": "^22.13.9", + "@types/pretty-ms": "^5.0.1", "@types/sinonjs__fake-timers": "^8.1.5", "earl": "^1.3.0", "mocha": "^10.8.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88ece748a..5a2b4b4f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -399,9 +399,6 @@ importers: '@types/mocha': specifier: ^10.0.10 version: 10.0.10 - '@types/pretty-ms': - specifier: ^5.0.1 - version: 5.0.1 '@types/sinonjs__fake-timers': specifier: ^8.1.5 version: 8.1.5 @@ -503,6 +500,9 @@ importers: '@types/node': specifier: ^22.13.9 version: 22.13.9 + '@types/pretty-ms': + specifier: ^5.0.1 + version: 5.0.1 '@types/sinonjs__fake-timers': specifier: ^8.1.5 version: 8.1.5 From 8b5fd757d2254393b2001dde6d36ecc1fabbc86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 13:47:39 +0100 Subject: [PATCH 14/22] Extract PrettyLogger --- packages/app/vite.config.ts | 2 +- packages/common-nodejs/package.json | 16 ++- .../src/logger/LogFormatterPretty.ts | 5 +- .../src/logger/PrettyLogger.test.ts | 127 ++++++++++++++++++ .../common-nodejs/src/logger/PrettyLogger.ts | 74 ++++++++++ packages/common-nodejs/src/logger/index.ts | 2 + .../src/logger/Logger.test.ts | 67 +++------ .../common-universal/src/logger/Logger.ts | 13 +- packages/common-universal/src/logger/index.ts | 2 +- pnpm-lock.yaml | 3 + 10 files changed, 250 insertions(+), 61 deletions(-) rename packages/{common-universal => common-nodejs}/src/logger/LogFormatterPretty.ts (96%) create mode 100644 packages/common-nodejs/src/logger/PrettyLogger.test.ts create mode 100644 packages/common-nodejs/src/logger/PrettyLogger.ts create mode 100644 packages/common-nodejs/src/logger/index.ts diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 4ebee2808..52fcb6c19 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ nodePolyfills({ // buffer is needed when connecting with a coinbase wallet installed on a phone // util and path is needed for common-universal's logger - include: ['buffer', 'util', 'path'], + include: ['buffer'], }), react(), tsconfigPaths(), diff --git a/packages/common-nodejs/package.json b/packages/common-nodejs/package.json index 7b3ae67a5..9f0881c1e 100644 --- a/packages/common-nodejs/package.json +++ b/packages/common-nodejs/package.json @@ -22,11 +22,24 @@ "types": "./dist/types/env/index.d.ts", "default": "./dist/cjs/env/index.js" } + }, + "./logger": { + "import": { + "@marsfoundation/local-spark-monorepo": "./src/logger/index.ts", + "types": "./dist/types/logger/index.d.ts", + "default": "./dist/esm/logger/index.js" + }, + "require": { + "@marsfoundation/local-spark-monorepo": "./src/logger/index.ts", + "types": "./dist/types/logger/index.d.ts", + "default": "./dist/cjs/logger/index.js" + } } }, "typesVersions": { "*": { - "env": ["dist/types/env/index.d.ts"] + "env": ["dist/types/env/index.d.ts"], + "logger": ["dist/types/logger/index.d.ts"] } }, "files": ["dist"], @@ -54,6 +67,7 @@ "tinyspy": "^3.0.2" }, "dependencies": { + "chalk": "^5.3.0", "dotenv": "^16.3.1", "uuid": "^11.0.3" }, diff --git a/packages/common-universal/src/logger/LogFormatterPretty.ts b/packages/common-nodejs/src/logger/LogFormatterPretty.ts similarity index 96% rename from packages/common-universal/src/logger/LogFormatterPretty.ts rename to packages/common-nodejs/src/logger/LogFormatterPretty.ts index 4a251f4f8..7fe888027 100644 --- a/packages/common-universal/src/logger/LogFormatterPretty.ts +++ b/packages/common-nodejs/src/logger/LogFormatterPretty.ts @@ -1,10 +1,7 @@ import { inspect } from 'node:util' +import { LogEntry, LogFormatter, LogLevel, toJSON } from '@marsfoundation/common-universal/logger' import chalk from 'chalk' -import { LogLevel } from './LogLevel.js' -import { LogEntry, LogFormatter } from './types.js' -import { toJSON } from './utils.js' - const STYLES = { bigint: 'white', boolean: 'white', diff --git a/packages/common-nodejs/src/logger/PrettyLogger.test.ts b/packages/common-nodejs/src/logger/PrettyLogger.test.ts new file mode 100644 index 000000000..b225b4761 --- /dev/null +++ b/packages/common-nodejs/src/logger/PrettyLogger.test.ts @@ -0,0 +1,127 @@ +import { Logger } from '@marsfoundation/common-universal/logger' +import { expect, mockFn } from 'earl' +import { LogFormatterPretty } from './LogFormatterPretty.js' +import { PrettyLogger } from './PrettyLogger.js' + +describe(PrettyLogger.name, () => { + it('supports bigint values in pretty output', () => { + const transport = createTestTransport() + const logger = new Logger({ + transports: [ + { + transport: transport, + formatter: new LogFormatterPretty({ colors: false, utc: true }), + }, + ], + logLevel: 'TRACE', + getTime: () => new Date(0), + utc: true, + }) + + logger.info({ foo: 123n, bar: [4n, 56n] }) + const lines = ['00:00:00.000Z INFO\n', " { foo: '123', bar: [ '4', '56' ] }", ''] + expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) + }) + + it('marks promised values in pretty output', () => { + const transport = createTestTransport() + const logger = new Logger({ + transports: [ + { + transport: transport, + formatter: new LogFormatterPretty({ colors: false, utc: true }), + }, + ], + logLevel: 'TRACE', + getTime: () => new Date(0), + utc: true, + }) + + logger.info({ test: Promise.resolve(1234) }) + const lines = ['00:00:00.000Z INFO\n', " { test: 'Promise' }", ''] + expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) + }) + + describe(PrettyLogger.prototype.for.name, () => { + function setup() { + const transport = createTestTransport() + const baseLogger = new Logger({ + transports: [ + { + transport: transport, + formatter: new LogFormatterPretty({ colors: false, utc: true }), + }, + ], + logLevel: 'TRACE', + getTime: () => new Date(0), + utc: true, + }) + return { transport, baseLogger } + } + + it('single service (string)', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.for('FooService') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') + }) + + it('single service (object)', () => { + const { transport, baseLogger } = setup() + + class FooService {} + const instance = new FooService() + const logger = baseLogger.for(instance) + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') + }) + + it('service with member', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.for('FooService').for('queue') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue ] hello') + }) + + it('service with tag', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red').for('FooService') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService:Red ] hello') + }) + + it('service with tag and member', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red').for('FooService').for('queue') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue:Red ] hello') + }) + + it('lone tag', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ :Red ] hello') + }) + }) +}) + +function createTestTransport() { + return { + debug: mockFn((_: string): void => {}), + log: mockFn((_: string): void => {}), + warn: mockFn((_: string): void => {}), + error: mockFn((_: string): void => {}), + } +} diff --git a/packages/common-nodejs/src/logger/PrettyLogger.ts b/packages/common-nodejs/src/logger/PrettyLogger.ts new file mode 100644 index 000000000..7d63170fa --- /dev/null +++ b/packages/common-nodejs/src/logger/PrettyLogger.ts @@ -0,0 +1,74 @@ +import { Logger, LoggerOptions } from '@marsfoundation/common-universal/logger' +import { LogFormatterPretty } from './LogFormatterPretty.js' + +export class PrettyLogger extends Logger { + constructor(options: Partial) { + options.transports = options.transports ?? [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ] + super(options) + } + + static CRITICAL = new Logger({ + logLevel: 'CRITICAL', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static ERROR = new Logger({ + logLevel: 'ERROR', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static WARN = new Logger({ + logLevel: 'WARN', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static INFO = new Logger({ + logLevel: 'INFO', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static DEBUG = new Logger({ + logLevel: 'DEBUG', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) + + static TRACE = new Logger({ + logLevel: 'TRACE', + transports: [ + { + transport: console, + formatter: new LogFormatterPretty(), + }, + ], + }) +} diff --git a/packages/common-nodejs/src/logger/index.ts b/packages/common-nodejs/src/logger/index.ts new file mode 100644 index 000000000..324bdf8b6 --- /dev/null +++ b/packages/common-nodejs/src/logger/index.ts @@ -0,0 +1,2 @@ +export * from './LogFormatterPretty.js' +export * from './PrettyLogger.js' diff --git a/packages/common-universal/src/logger/Logger.test.ts b/packages/common-universal/src/logger/Logger.test.ts index d4ec36347..69ad6fc47 100644 --- a/packages/common-universal/src/logger/Logger.test.ts +++ b/packages/common-universal/src/logger/Logger.test.ts @@ -2,7 +2,6 @@ import { expect, formatCompact, mockFn } from 'earl' import { LogFormatterJson } from './LogFormatterJson.js' -import { LogFormatterPretty } from './LogFormatterPretty.js' import { Logger } from './Logger.js' import { LogEntry } from './types.js' @@ -61,52 +60,14 @@ describe(Logger.name, () => { ) }) - it('supports bigint values in pretty output', () => { - const transport = createTestTransport() - const logger = new Logger({ - transports: [ - { - transport: transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - - logger.info({ foo: 123n, bar: [4n, 56n] }) - const lines = ['00:00:00.000Z INFO\n', " { foo: '123', bar: [ '4', '56' ] }", ''] - expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) - }) - - it('marks promised values in pretty output', () => { - const transport = createTestTransport() - const logger = new Logger({ - transports: [ - { - transport: transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - - logger.info({ test: Promise.resolve(1234) }) - const lines = ['00:00:00.000Z INFO\n', " { test: 'Promise' }", ''] - expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) - }) - - describe('for', () => { + describe(Logger.prototype.for.name, () => { function setup() { const transport = createTestTransport() const baseLogger = new Logger({ transports: [ { transport: transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), + formatter: new LogFormatterJson(), }, ], logLevel: 'TRACE', @@ -122,7 +83,9 @@ describe(Logger.name, () => { const logger = baseLogger.for('FooService') logger.info('hello') - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') + expect(transport.log).toHaveBeenOnlyCalledWith( + '{"time":"1970-01-01T00:00:00.000Z","level":"INFO","service":"FooService","message":"hello"}', + ) }) it('single service (object)', () => { @@ -133,7 +96,9 @@ describe(Logger.name, () => { const logger = baseLogger.for(instance) logger.info('hello') - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') + expect(transport.log).toHaveBeenOnlyCalledWith( + '{"time":"1970-01-01T00:00:00.000Z","level":"INFO","service":"FooService","message":"hello"}', + ) }) it('service with member', () => { @@ -142,7 +107,9 @@ describe(Logger.name, () => { const logger = baseLogger.for('FooService').for('queue') logger.info('hello') - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue ] hello') + expect(transport.log).toHaveBeenOnlyCalledWith( + '{"time":"1970-01-01T00:00:00.000Z","level":"INFO","service":"FooService.queue","message":"hello"}', + ) }) it('service with tag', () => { @@ -151,7 +118,9 @@ describe(Logger.name, () => { const logger = baseLogger.tag('Red').for('FooService') logger.info('hello') - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService:Red ] hello') + expect(transport.log).toHaveBeenOnlyCalledWith( + '{"time":"1970-01-01T00:00:00.000Z","level":"INFO","service":"FooService:Red","message":"hello"}', + ) }) it('service with tag and member', () => { @@ -160,7 +129,9 @@ describe(Logger.name, () => { const logger = baseLogger.tag('Red').for('FooService').for('queue') logger.info('hello') - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue:Red ] hello') + expect(transport.log).toHaveBeenOnlyCalledWith( + '{"time":"1970-01-01T00:00:00.000Z","level":"INFO","service":"FooService.queue:Red","message":"hello"}', + ) }) it('lone tag', () => { @@ -169,7 +140,9 @@ describe(Logger.name, () => { const logger = baseLogger.tag('Red') logger.info('hello') - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ :Red ] hello') + expect(transport.log).toHaveBeenOnlyCalledWith( + '{"time":"1970-01-01T00:00:00.000Z","level":"INFO","service":":Red","message":"hello"}', + ) }) }) diff --git a/packages/common-universal/src/logger/Logger.ts b/packages/common-universal/src/logger/Logger.ts index bbac0b16f..2edffb46e 100644 --- a/packages/common-universal/src/logger/Logger.ts +++ b/packages/common-universal/src/logger/Logger.ts @@ -2,7 +2,6 @@ import { join } from 'node:path' import { assertNever } from '../assert/assertNever.js' import { LogFormatterJson } from './LogFormatterJson.js' -import { LogFormatterPretty } from './LogFormatterPretty.js' import { LEVEL, LogLevel } from './LogLevel.js' import { LogThrottle, LogThrottleOptions } from './LogThrottle.js' import { parseLogArguments } from './parseLogArguments.js' @@ -59,7 +58,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterPretty(), + formatter: new LogFormatterJson(), }, ], }) @@ -69,7 +68,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterPretty(), + formatter: new LogFormatterJson(), }, ], }) @@ -79,7 +78,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterPretty(), + formatter: new LogFormatterJson(), }, ], }) @@ -89,7 +88,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterPretty(), + formatter: new LogFormatterJson(), }, ], }) @@ -99,7 +98,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterPretty(), + formatter: new LogFormatterJson(), }, ], }) @@ -109,7 +108,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterPretty(), + formatter: new LogFormatterJson(), }, ], }) diff --git a/packages/common-universal/src/logger/index.ts b/packages/common-universal/src/logger/index.ts index e53fbffd2..7c2dd2360 100644 --- a/packages/common-universal/src/logger/index.ts +++ b/packages/common-universal/src/logger/index.ts @@ -1,6 +1,6 @@ export * from './Logger.js' export * from './LogLevel.js' export * from './types.js' -export * from './LogFormatterPretty.js' export * from './LogFormatterJson.js' export * from './LogThrottle.js' +export * from './utils.js' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a2b4b4f8..363e30200 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -377,6 +377,9 @@ importers: '@marsfoundation/common-universal': specifier: workspace:^ version: link:../common-universal + chalk: + specifier: ^5.3.0 + version: 5.3.0 dotenv: specifier: ^16.3.1 version: 16.3.1 From 463194c3658d3cf4f347dbc1680efa76108e82a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 14:02:47 +0100 Subject: [PATCH 15/22] Remove node deps in universal --- packages/app/vite.config.ts | 1 - .../src/logger/PrettyLogger.test.ts | 15 +++---- .../common-nodejs/src/logger/PrettyLogger.ts | 2 + packages/common-universal/package.json | 1 - .../common-universal/src/logger/Logger.ts | 8 +--- .../src/logger/resolveError.ts | 13 +++++-- packages/common-universal/src/logger/types.ts | 2 +- packages/common-universal/src/logger/utils.ts | 1 - pnpm-lock.yaml | 39 ++++++------------- 9 files changed, 35 insertions(+), 47 deletions(-) diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 52fcb6c19..5f57550cb 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -28,7 +28,6 @@ export default defineConfig({ // nodePolyfills needs to be first nodePolyfills({ // buffer is needed when connecting with a coinbase wallet installed on a phone - // util and path is needed for common-universal's logger include: ['buffer'], }), react(), diff --git a/packages/common-nodejs/src/logger/PrettyLogger.test.ts b/packages/common-nodejs/src/logger/PrettyLogger.test.ts index b225b4761..7a0215e4a 100644 --- a/packages/common-nodejs/src/logger/PrettyLogger.test.ts +++ b/packages/common-nodejs/src/logger/PrettyLogger.test.ts @@ -1,5 +1,5 @@ -import { Logger } from '@marsfoundation/common-universal/logger' -import { expect, mockFn } from 'earl' +import { Logger, LoggerTransport } from '@marsfoundation/common-universal/logger' +import { MockObject, expect, mockFn } from 'earl' import { LogFormatterPretty } from './LogFormatterPretty.js' import { PrettyLogger } from './PrettyLogger.js' @@ -9,7 +9,7 @@ describe(PrettyLogger.name, () => { const logger = new Logger({ transports: [ { - transport: transport, + transport, formatter: new LogFormatterPretty({ colors: false, utc: true }), }, ], @@ -28,7 +28,7 @@ describe(PrettyLogger.name, () => { const logger = new Logger({ transports: [ { - transport: transport, + transport, formatter: new LogFormatterPretty({ colors: false, utc: true }), }, ], @@ -43,12 +43,12 @@ describe(PrettyLogger.name, () => { }) describe(PrettyLogger.prototype.for.name, () => { - function setup() { + function setup(): { transport: TestTransport; baseLogger: Logger } { const transport = createTestTransport() const baseLogger = new Logger({ transports: [ { - transport: transport, + transport, formatter: new LogFormatterPretty({ colors: false, utc: true }), }, ], @@ -117,7 +117,8 @@ describe(PrettyLogger.name, () => { }) }) -function createTestTransport() { +type TestTransport = MockObject +function createTestTransport(): TestTransport { return { debug: mockFn((_: string): void => {}), log: mockFn((_: string): void => {}), diff --git a/packages/common-nodejs/src/logger/PrettyLogger.ts b/packages/common-nodejs/src/logger/PrettyLogger.ts index 7d63170fa..ed7666b44 100644 --- a/packages/common-nodejs/src/logger/PrettyLogger.ts +++ b/packages/common-nodejs/src/logger/PrettyLogger.ts @@ -1,3 +1,4 @@ +import { join } from 'node:path' import { Logger, LoggerOptions } from '@marsfoundation/common-universal/logger' import { LogFormatterPretty } from './LogFormatterPretty.js' @@ -9,6 +10,7 @@ export class PrettyLogger extends Logger { formatter: new LogFormatterPretty(), }, ] + options.cwd = join(options.cwd ?? process.cwd(), '/') super(options) } diff --git a/packages/common-universal/package.json b/packages/common-universal/package.json index 452f4c562..9a67fccee 100644 --- a/packages/common-universal/package.json +++ b/packages/common-universal/package.json @@ -78,7 +78,6 @@ "@sinonjs/fake-timers": "^13.0.5", "@types/mocha": "^10.0.10", "@types/ms": "^2.1.0", - "@types/node": "^22.13.9", "@types/pretty-ms": "^5.0.1", "@types/sinonjs__fake-timers": "^8.1.5", "earl": "^1.3.0", diff --git a/packages/common-universal/src/logger/Logger.ts b/packages/common-universal/src/logger/Logger.ts index 2edffb46e..1ae85195a 100644 --- a/packages/common-universal/src/logger/Logger.ts +++ b/packages/common-universal/src/logger/Logger.ts @@ -1,5 +1,3 @@ -import { join } from 'node:path' - import { assertNever } from '../assert/assertNever.js' import { LogFormatterJson } from './LogFormatterJson.js' import { LEVEL, LogLevel } from './LogLevel.js' @@ -28,7 +26,6 @@ export interface ILogger { export class Logger implements ILogger { private readonly options: LoggerOptions private readonly logLevel: number - private readonly cwd: string private throttle?: LogThrottle constructor(options: Partial) { @@ -37,7 +34,7 @@ export class Logger implements ILogger { service: options.service, tag: options.tag, utc: options.utc ?? false, - cwd: options.cwd ?? process.cwd(), + cwd: options.cwd, getTime: options.getTime ?? (() => new Date()), reportError: options.reportError ?? (() => {}), transports: options.transports ?? [ @@ -47,7 +44,6 @@ export class Logger implements ILogger { }, ], } - this.cwd = join(this.options.cwd, '/') this.logLevel = LEVEL[this.options.logLevel] } @@ -192,7 +188,7 @@ export class Logger implements ILogger { const parsed = parseLogArguments(args) return { ...parsed, - resolvedError: parsed.error ? resolveError(parsed.error, this.cwd) : undefined, + resolvedError: parsed.error ? resolveError(parsed.error, this.options.cwd) : undefined, level, time: this.options.getTime(), service: tagService(this.options.service, this.options.tag), diff --git a/packages/common-universal/src/logger/resolveError.ts b/packages/common-universal/src/logger/resolveError.ts index bc550d778..f37702bf2 100644 --- a/packages/common-universal/src/logger/resolveError.ts +++ b/packages/common-universal/src/logger/resolveError.ts @@ -6,7 +6,7 @@ export interface ResolvedError { stack: string[] } -export function resolveError(error: Error, cwd: string): ResolvedError { +export function resolveError(error: Error, cwd: string | undefined): ResolvedError { return { name: error.name, error: error.message, @@ -14,8 +14,8 @@ export function resolveError(error: Error, cwd: string): ResolvedError { } } -function formatFrame(frame: StackFrame, cwd: string): string { - const file = frame.fileName?.startsWith(cwd) ? frame.fileName.slice(cwd.length) : frame.fileName +function formatFrame(frame: StackFrame, cwd: string | undefined): string { + const file = getFile(frame, cwd) const functionName = frame.functionName ? `${frame.functionName} ` : '' const fileLocation = @@ -26,3 +26,10 @@ function formatFrame(frame: StackFrame, cwd: string): string { return `${functionName}${location}` } + +function getFile(frame: StackFrame, cwd: string | undefined): string | undefined { + if (!cwd) { + return frame.fileName + } + return frame.fileName?.startsWith(cwd) ? frame.fileName.slice(cwd.length) : frame.fileName +} diff --git a/packages/common-universal/src/logger/types.ts b/packages/common-universal/src/logger/types.ts index 20ec6001a..cb698ed89 100644 --- a/packages/common-universal/src/logger/types.ts +++ b/packages/common-universal/src/logger/types.ts @@ -22,7 +22,7 @@ export interface LoggerOptions { service?: string tag?: string utc: boolean - cwd: string + cwd?: string getTime: () => Date reportError: (entry: LogEntry) => void transports: LoggerTransportOptions[] diff --git a/packages/common-universal/src/logger/utils.ts b/packages/common-universal/src/logger/utils.ts index 3fdc887cb..6dc960d75 100644 --- a/packages/common-universal/src/logger/utils.ts +++ b/packages/common-universal/src/logger/utils.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ export function toJSON(parameters: object): string { return JSON.stringify(parameters, (_k, v: unknown) => { if (typeof v === 'bigint') { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 363e30200..3d2c4b444 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -500,9 +500,6 @@ importers: '@types/ms': specifier: ^2.1.0 version: 2.1.0 - '@types/node': - specifier: ^22.13.9 - version: 22.13.9 '@types/pretty-ms': specifier: ^5.0.1 version: 5.0.1 @@ -3521,9 +3518,6 @@ packages: '@types/node@20.4.10': resolution: {integrity: sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==} - '@types/node@22.13.9': - resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==} - '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -8183,9 +8177,6 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -10528,7 +10519,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.13.9 + '@types/node': 20.17.17 jest-mock: 29.7.0 optional: true @@ -10540,7 +10531,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.13.9 + '@types/node': 20.17.17 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -10585,7 +10576,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.13.9 + '@types/node': 20.17.17 '@types/yargs': 17.0.33 chalk: 4.1.2 optional: true @@ -12511,7 +12502,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.13.9 + '@types/node': 20.17.17 optional: true '@types/http-errors@2.0.4': {} @@ -12568,7 +12559,7 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 22.13.9 + '@types/node': 20.17.17 optional: true '@types/node@20.17.17': @@ -12577,10 +12568,6 @@ snapshots: '@types/node@20.4.10': {} - '@types/node@22.13.9': - dependencies: - undici-types: 6.20.0 - '@types/parse-json@4.0.2': optional: true @@ -14102,7 +14089,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 22.13.9 + '@types/node': 20.17.17 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -14112,7 +14099,7 @@ snapshots: chromium-edge-launcher@0.2.0: dependencies: - '@types/node': 22.13.9 + '@types/node': 20.17.17 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -15943,7 +15930,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.13.9 + '@types/node': 20.17.17 jest-mock: 29.7.0 jest-util: 29.7.0 optional: true @@ -15954,7 +15941,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.13.9 + '@types/node': 20.17.17 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -16002,7 +15989,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.13.9 + '@types/node': 20.17.17 jest-util: 29.7.0 optional: true @@ -16021,7 +16008,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.13.9 + '@types/node': 20.17.17 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -16040,7 +16027,7 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.13.9 + '@types/node': 20.17.17 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -18550,8 +18537,6 @@ snapshots: undici-types@6.19.8: {} - undici-types@6.20.0: {} - unicode-canonical-property-names-ecmascript@2.0.1: optional: true From e2dede1b4aaacef423f02df1501997fe9023de07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 14:15:56 +0100 Subject: [PATCH 16/22] Use pretty-ms instead of ms in universal --- packages/common-universal/package.json | 2 -- packages/common-universal/src/types/UnixTime.test.ts | 4 ++-- packages/common-universal/src/types/UnixTime.ts | 4 ++-- pnpm-lock.yaml | 6 ------ 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/common-universal/package.json b/packages/common-universal/package.json index 9a67fccee..d7e887762 100644 --- a/packages/common-universal/package.json +++ b/packages/common-universal/package.json @@ -77,7 +77,6 @@ "devDependencies": { "@sinonjs/fake-timers": "^13.0.5", "@types/mocha": "^10.0.10", - "@types/ms": "^2.1.0", "@types/pretty-ms": "^5.0.1", "@types/sinonjs__fake-timers": "^8.1.5", "earl": "^1.3.0", @@ -87,7 +86,6 @@ "chalk": "^5.3.0", "error-stack-parser": "^2.1.4", "fetch-retry": "^5.0.6", - "ms": "^2.1.3", "p-limit": "^6.1.0", "pretty-ms": "^9.2.0" } diff --git a/packages/common-universal/src/types/UnixTime.test.ts b/packages/common-universal/src/types/UnixTime.test.ts index dc43bacd9..b3ac15f73 100644 --- a/packages/common-universal/src/types/UnixTime.test.ts +++ b/packages/common-universal/src/types/UnixTime.test.ts @@ -52,9 +52,9 @@ describe(UnixTime.name, () => { expect(UnixTime.formatSpan(UnixTime.ONE_DAY())).toEqual('1d') expect(UnixTime.formatSpan(UnixTime.SEVEN_DAYS())).toEqual('7d') expect(UnixTime.formatSpan(UnixTime.THIRTY_DAYS())).toEqual('30d') - expect(UnixTime.formatSpan(UnixTime.ONE_YEAR())).toEqual('365d') + expect(UnixTime.formatSpan(UnixTime.ONE_YEAR())).toEqual('1y') expect(UnixTime.formatSpan(UnixTime(UnixTime.ONE_YEAR() - UnixTime.SEVEN_DAYS()))).toEqual('358d') - expect(UnixTime.formatSpan(UnixTime(UnixTime.ONE_YEAR() - UnixTime.ONE_HOUR()))).toEqual('365d') + expect(UnixTime.formatSpan(UnixTime(UnixTime.ONE_YEAR() - UnixTime.ONE_HOUR()))).toEqual('364d 23h') }) }) }) diff --git a/packages/common-universal/src/types/UnixTime.ts b/packages/common-universal/src/types/UnixTime.ts index 1445e3eec..52aad4df7 100644 --- a/packages/common-universal/src/types/UnixTime.ts +++ b/packages/common-universal/src/types/UnixTime.ts @@ -1,4 +1,4 @@ -import ms from 'ms' +import prettyMilliseconds from 'pretty-ms' import { assert } from '../assert/assert.js' import { NumberLike, bigNumberify } from '../math/bigNumber.js' import { Opaque } from './Opaque.js' @@ -69,7 +69,7 @@ UnixTime.toMilliseconds = (unixTime: UnixTime): number => { } UnixTime.formatSpan = (span: UnixTime): string => { - return ms(UnixTime.toMilliseconds(span)) + return prettyMilliseconds(UnixTime.toMilliseconds(span)) } const YEAR_3000_TIMESTAMP = Math.floor(new Date('3000-01-01T00:00:00.000Z').getTime() / 1000) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d2c4b444..7092b60c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -478,9 +478,6 @@ importers: fetch-retry: specifier: ^5.0.6 version: 5.0.6 - ms: - specifier: ^2.1.3 - version: 2.1.3 p-limit: specifier: ^6.1.0 version: 6.1.0 @@ -497,9 +494,6 @@ importers: '@types/mocha': specifier: ^10.0.10 version: 10.0.10 - '@types/ms': - specifier: ^2.1.0 - version: 2.1.0 '@types/pretty-ms': specifier: ^5.0.1 version: 5.0.1 From 20b568c02dd7b924a92518b3ef0e4f3f339f83ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 14:16:45 +0100 Subject: [PATCH 17/22] Revert "Use pretty-ms instead of ms in universal" This reverts commit e2dede1b4aaacef423f02df1501997fe9023de07. --- packages/common-universal/package.json | 2 ++ packages/common-universal/src/types/UnixTime.test.ts | 4 ++-- packages/common-universal/src/types/UnixTime.ts | 4 ++-- pnpm-lock.yaml | 6 ++++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/common-universal/package.json b/packages/common-universal/package.json index d7e887762..9a67fccee 100644 --- a/packages/common-universal/package.json +++ b/packages/common-universal/package.json @@ -77,6 +77,7 @@ "devDependencies": { "@sinonjs/fake-timers": "^13.0.5", "@types/mocha": "^10.0.10", + "@types/ms": "^2.1.0", "@types/pretty-ms": "^5.0.1", "@types/sinonjs__fake-timers": "^8.1.5", "earl": "^1.3.0", @@ -86,6 +87,7 @@ "chalk": "^5.3.0", "error-stack-parser": "^2.1.4", "fetch-retry": "^5.0.6", + "ms": "^2.1.3", "p-limit": "^6.1.0", "pretty-ms": "^9.2.0" } diff --git a/packages/common-universal/src/types/UnixTime.test.ts b/packages/common-universal/src/types/UnixTime.test.ts index b3ac15f73..dc43bacd9 100644 --- a/packages/common-universal/src/types/UnixTime.test.ts +++ b/packages/common-universal/src/types/UnixTime.test.ts @@ -52,9 +52,9 @@ describe(UnixTime.name, () => { expect(UnixTime.formatSpan(UnixTime.ONE_DAY())).toEqual('1d') expect(UnixTime.formatSpan(UnixTime.SEVEN_DAYS())).toEqual('7d') expect(UnixTime.formatSpan(UnixTime.THIRTY_DAYS())).toEqual('30d') - expect(UnixTime.formatSpan(UnixTime.ONE_YEAR())).toEqual('1y') + expect(UnixTime.formatSpan(UnixTime.ONE_YEAR())).toEqual('365d') expect(UnixTime.formatSpan(UnixTime(UnixTime.ONE_YEAR() - UnixTime.SEVEN_DAYS()))).toEqual('358d') - expect(UnixTime.formatSpan(UnixTime(UnixTime.ONE_YEAR() - UnixTime.ONE_HOUR()))).toEqual('364d 23h') + expect(UnixTime.formatSpan(UnixTime(UnixTime.ONE_YEAR() - UnixTime.ONE_HOUR()))).toEqual('365d') }) }) }) diff --git a/packages/common-universal/src/types/UnixTime.ts b/packages/common-universal/src/types/UnixTime.ts index 52aad4df7..1445e3eec 100644 --- a/packages/common-universal/src/types/UnixTime.ts +++ b/packages/common-universal/src/types/UnixTime.ts @@ -1,4 +1,4 @@ -import prettyMilliseconds from 'pretty-ms' +import ms from 'ms' import { assert } from '../assert/assert.js' import { NumberLike, bigNumberify } from '../math/bigNumber.js' import { Opaque } from './Opaque.js' @@ -69,7 +69,7 @@ UnixTime.toMilliseconds = (unixTime: UnixTime): number => { } UnixTime.formatSpan = (span: UnixTime): string => { - return prettyMilliseconds(UnixTime.toMilliseconds(span)) + return ms(UnixTime.toMilliseconds(span)) } const YEAR_3000_TIMESTAMP = Math.floor(new Date('3000-01-01T00:00:00.000Z').getTime() / 1000) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7092b60c0..3d2c4b444 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -478,6 +478,9 @@ importers: fetch-retry: specifier: ^5.0.6 version: 5.0.6 + ms: + specifier: ^2.1.3 + version: 2.1.3 p-limit: specifier: ^6.1.0 version: 6.1.0 @@ -494,6 +497,9 @@ importers: '@types/mocha': specifier: ^10.0.10 version: 10.0.10 + '@types/ms': + specifier: ^2.1.0 + version: 2.1.0 '@types/pretty-ms': specifier: ^5.0.1 version: 5.0.1 From 252eca9c0705c1b76f7fff79a8387f7a68be8ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 15:35:56 +0100 Subject: [PATCH 18/22] Drop inspect util --- .../src/logger/LogFormatterPretty.ts | 40 ++----------------- .../src/logger/PrettyLogger.test.ts | 17 ++++++-- packages/common-universal/src/logger/utils.ts | 36 +++++++++-------- 3 files changed, 37 insertions(+), 56 deletions(-) diff --git a/packages/common-nodejs/src/logger/LogFormatterPretty.ts b/packages/common-nodejs/src/logger/LogFormatterPretty.ts index 7fe888027..7a1128ad3 100644 --- a/packages/common-nodejs/src/logger/LogFormatterPretty.ts +++ b/packages/common-nodejs/src/logger/LogFormatterPretty.ts @@ -1,24 +1,7 @@ -import { inspect } from 'node:util' import { LogEntry, LogFormatter, LogLevel, toJSON } from '@marsfoundation/common-universal/logger' import chalk from 'chalk' -const STYLES = { - bigint: 'white', - boolean: 'white', - date: 'white', - module: 'white', - name: 'blue', - null: 'white', - number: 'white', - regexp: 'white', - special: 'white', - string: 'white', - symbol: 'white', - undefined: 'white', -} - const INDENT_SIZE = 4 -const INDENT = ' '.repeat(INDENT_SIZE) interface Options { colors: boolean @@ -83,30 +66,15 @@ export class LogFormatterPretty implements LogFormatter { } protected formatParametersPretty(parameters: object, colors: boolean): string { - const oldStyles = inspect.styles - inspect.styles = STYLES - - const inspected = inspect(parameters, { - colors, - breakLength: 80 - INDENT_SIZE, - depth: 5, - }) - - inspect.styles = oldStyles - - if (inspected === '{}') { + const jsonParameters = toJSON(parameters, INDENT_SIZE) + if (jsonParameters === '{}') { return '' } - const indented = inspected - .split('\n') - .map((x) => INDENT + x) - .join('\n') - if (colors) { - return `\n${chalk.gray(indented)}` + return `\n${chalk.gray(jsonParameters)}` } - return `\n${indented}` + return `\n${jsonParameters}` } protected formatServicePretty(service: string | undefined, colors: boolean): string { diff --git a/packages/common-nodejs/src/logger/PrettyLogger.test.ts b/packages/common-nodejs/src/logger/PrettyLogger.test.ts index 7a0215e4a..7bd6a529b 100644 --- a/packages/common-nodejs/src/logger/PrettyLogger.test.ts +++ b/packages/common-nodejs/src/logger/PrettyLogger.test.ts @@ -19,8 +19,17 @@ describe(PrettyLogger.name, () => { }) logger.info({ foo: 123n, bar: [4n, 56n] }) - const lines = ['00:00:00.000Z INFO\n', " { foo: '123', bar: [ '4', '56' ] }", ''] - expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) + const expectedOutput = [ + '00:00:00.000Z INFO', + '{', + ' "foo": "123",', + ' "bar": [', + ' "4",', + ' "56"', + ' ]', + '}', + ].join('\n') + expect(transport.log).toHaveBeenOnlyCalledWith(expectedOutput) }) it('marks promised values in pretty output', () => { @@ -38,8 +47,8 @@ describe(PrettyLogger.name, () => { }) logger.info({ test: Promise.resolve(1234) }) - const lines = ['00:00:00.000Z INFO\n', " { test: 'Promise' }", ''] - expect(transport.log).toHaveBeenOnlyCalledWith(lines.join('')) + const expectedOutput = ['00:00:00.000Z INFO', '{', ' "test": "Promise"', '}'].join('\n') + expect(transport.log).toHaveBeenOnlyCalledWith(expectedOutput) }) describe(PrettyLogger.prototype.for.name, () => { diff --git a/packages/common-universal/src/logger/utils.ts b/packages/common-universal/src/logger/utils.ts index 6dc960d75..4c7f7c519 100644 --- a/packages/common-universal/src/logger/utils.ts +++ b/packages/common-universal/src/logger/utils.ts @@ -1,19 +1,23 @@ -export function toJSON(parameters: object): string { - return JSON.stringify(parameters, (_k, v: unknown) => { - if (typeof v === 'bigint') { - return v.toString() - } +export function toJSON(parameters: object, space?: number): string { + return JSON.stringify( + parameters, + (_k, v: unknown) => { + if (typeof v === 'bigint') { + return v.toString() + } - // uses a generic check to catch any promise-like objects (e.g. Promise, Bluebird, etc.) - const isPromise = - typeof v === 'object' && - v !== null && - typeof (v as any).then === 'function' && - typeof (v as any).catch === 'function' - if (isPromise) { - return 'Promise' - } + // uses a generic check to catch any promise-like objects (e.g. Promise, Bluebird, etc.) + const isPromise = + typeof v === 'object' && + v !== null && + typeof (v as any).then === 'function' && + typeof (v as any).catch === 'function' + if (isPromise) { + return 'Promise' + } - return v - }) + return v + }, + space, + ) } From 9b52393b308d6b347ee59bb0cd0ff48d585b6eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 15:40:38 +0100 Subject: [PATCH 19/22] Remove cwd from logger --- packages/common-universal/src/logger/Logger.ts | 3 +-- .../common-universal/src/logger/resolveError.ts | 16 ++++------------ packages/common-universal/src/logger/types.ts | 1 - 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/common-universal/src/logger/Logger.ts b/packages/common-universal/src/logger/Logger.ts index 1ae85195a..070af26f0 100644 --- a/packages/common-universal/src/logger/Logger.ts +++ b/packages/common-universal/src/logger/Logger.ts @@ -34,7 +34,6 @@ export class Logger implements ILogger { service: options.service, tag: options.tag, utc: options.utc ?? false, - cwd: options.cwd, getTime: options.getTime ?? (() => new Date()), reportError: options.reportError ?? (() => {}), transports: options.transports ?? [ @@ -188,7 +187,7 @@ export class Logger implements ILogger { const parsed = parseLogArguments(args) return { ...parsed, - resolvedError: parsed.error ? resolveError(parsed.error, this.options.cwd) : undefined, + resolvedError: parsed.error ? resolveError(parsed.error) : undefined, level, time: this.options.getTime(), service: tagService(this.options.service, this.options.tag), diff --git a/packages/common-universal/src/logger/resolveError.ts b/packages/common-universal/src/logger/resolveError.ts index f37702bf2..71fdd80c3 100644 --- a/packages/common-universal/src/logger/resolveError.ts +++ b/packages/common-universal/src/logger/resolveError.ts @@ -6,30 +6,22 @@ export interface ResolvedError { stack: string[] } -export function resolveError(error: Error, cwd: string | undefined): ResolvedError { +export function resolveError(error: Error): ResolvedError { return { name: error.name, error: error.message, - stack: ErrorStackParser.parse(error).map((frame) => formatFrame(frame, cwd)), + stack: ErrorStackParser.parse(error).map((frame) => formatFrame(frame)), } } -function formatFrame(frame: StackFrame, cwd: string | undefined): string { - const file = getFile(frame, cwd) +function formatFrame(frame: StackFrame): string { const functionName = frame.functionName ? `${frame.functionName} ` : '' const fileLocation = frame.lineNumber !== undefined && frame.columnNumber !== undefined ? `:${frame.lineNumber}:${frame.columnNumber}` : '' - const location = file !== undefined ? `(${file}${fileLocation})` : '' + const location = frame.fileName !== undefined ? `(${frame.fileName}${fileLocation})` : '' return `${functionName}${location}` } - -function getFile(frame: StackFrame, cwd: string | undefined): string | undefined { - if (!cwd) { - return frame.fileName - } - return frame.fileName?.startsWith(cwd) ? frame.fileName.slice(cwd.length) : frame.fileName -} diff --git a/packages/common-universal/src/logger/types.ts b/packages/common-universal/src/logger/types.ts index cb698ed89..09934ed3e 100644 --- a/packages/common-universal/src/logger/types.ts +++ b/packages/common-universal/src/logger/types.ts @@ -22,7 +22,6 @@ export interface LoggerOptions { service?: string tag?: string utc: boolean - cwd?: string getTime: () => Date reportError: (entry: LogEntry) => void transports: LoggerTransportOptions[] From e81b14d0b41e73003725e1e818d471f36011c7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 16:01:48 +0100 Subject: [PATCH 20/22] Move LogFormatterPretty --- packages/common-nodejs/package.json | 16 +- .../src/logger/PrettyLogger.test.ts | 137 ------------------ .../common-nodejs/src/logger/PrettyLogger.ts | 76 ---------- packages/common-nodejs/src/logger/index.ts | 2 - .../src/logger/LogFormatterPretty.ts | 10 +- .../src/logger/Logger.test.ts | 107 +++++++++++++- pnpm-lock.yaml | 3 - 7 files changed, 110 insertions(+), 241 deletions(-) delete mode 100644 packages/common-nodejs/src/logger/PrettyLogger.test.ts delete mode 100644 packages/common-nodejs/src/logger/PrettyLogger.ts delete mode 100644 packages/common-nodejs/src/logger/index.ts rename packages/{common-nodejs => common-universal}/src/logger/LogFormatterPretty.ts (89%) diff --git a/packages/common-nodejs/package.json b/packages/common-nodejs/package.json index 9f0881c1e..7b3ae67a5 100644 --- a/packages/common-nodejs/package.json +++ b/packages/common-nodejs/package.json @@ -22,24 +22,11 @@ "types": "./dist/types/env/index.d.ts", "default": "./dist/cjs/env/index.js" } - }, - "./logger": { - "import": { - "@marsfoundation/local-spark-monorepo": "./src/logger/index.ts", - "types": "./dist/types/logger/index.d.ts", - "default": "./dist/esm/logger/index.js" - }, - "require": { - "@marsfoundation/local-spark-monorepo": "./src/logger/index.ts", - "types": "./dist/types/logger/index.d.ts", - "default": "./dist/cjs/logger/index.js" - } } }, "typesVersions": { "*": { - "env": ["dist/types/env/index.d.ts"], - "logger": ["dist/types/logger/index.d.ts"] + "env": ["dist/types/env/index.d.ts"] } }, "files": ["dist"], @@ -67,7 +54,6 @@ "tinyspy": "^3.0.2" }, "dependencies": { - "chalk": "^5.3.0", "dotenv": "^16.3.1", "uuid": "^11.0.3" }, diff --git a/packages/common-nodejs/src/logger/PrettyLogger.test.ts b/packages/common-nodejs/src/logger/PrettyLogger.test.ts deleted file mode 100644 index 7bd6a529b..000000000 --- a/packages/common-nodejs/src/logger/PrettyLogger.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { Logger, LoggerTransport } from '@marsfoundation/common-universal/logger' -import { MockObject, expect, mockFn } from 'earl' -import { LogFormatterPretty } from './LogFormatterPretty.js' -import { PrettyLogger } from './PrettyLogger.js' - -describe(PrettyLogger.name, () => { - it('supports bigint values in pretty output', () => { - const transport = createTestTransport() - const logger = new Logger({ - transports: [ - { - transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - - logger.info({ foo: 123n, bar: [4n, 56n] }) - const expectedOutput = [ - '00:00:00.000Z INFO', - '{', - ' "foo": "123",', - ' "bar": [', - ' "4",', - ' "56"', - ' ]', - '}', - ].join('\n') - expect(transport.log).toHaveBeenOnlyCalledWith(expectedOutput) - }) - - it('marks promised values in pretty output', () => { - const transport = createTestTransport() - const logger = new Logger({ - transports: [ - { - transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - - logger.info({ test: Promise.resolve(1234) }) - const expectedOutput = ['00:00:00.000Z INFO', '{', ' "test": "Promise"', '}'].join('\n') - expect(transport.log).toHaveBeenOnlyCalledWith(expectedOutput) - }) - - describe(PrettyLogger.prototype.for.name, () => { - function setup(): { transport: TestTransport; baseLogger: Logger } { - const transport = createTestTransport() - const baseLogger = new Logger({ - transports: [ - { - transport, - formatter: new LogFormatterPretty({ colors: false, utc: true }), - }, - ], - logLevel: 'TRACE', - getTime: () => new Date(0), - utc: true, - }) - return { transport, baseLogger } - } - - it('single service (string)', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.for('FooService') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') - }) - - it('single service (object)', () => { - const { transport, baseLogger } = setup() - - class FooService {} - const instance = new FooService() - const logger = baseLogger.for(instance) - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') - }) - - it('service with member', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.for('FooService').for('queue') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue ] hello') - }) - - it('service with tag', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.tag('Red').for('FooService') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService:Red ] hello') - }) - - it('service with tag and member', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.tag('Red').for('FooService').for('queue') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue:Red ] hello') - }) - - it('lone tag', () => { - const { transport, baseLogger } = setup() - - const logger = baseLogger.tag('Red') - logger.info('hello') - - expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ :Red ] hello') - }) - }) -}) - -type TestTransport = MockObject -function createTestTransport(): TestTransport { - return { - debug: mockFn((_: string): void => {}), - log: mockFn((_: string): void => {}), - warn: mockFn((_: string): void => {}), - error: mockFn((_: string): void => {}), - } -} diff --git a/packages/common-nodejs/src/logger/PrettyLogger.ts b/packages/common-nodejs/src/logger/PrettyLogger.ts deleted file mode 100644 index ed7666b44..000000000 --- a/packages/common-nodejs/src/logger/PrettyLogger.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { join } from 'node:path' -import { Logger, LoggerOptions } from '@marsfoundation/common-universal/logger' -import { LogFormatterPretty } from './LogFormatterPretty.js' - -export class PrettyLogger extends Logger { - constructor(options: Partial) { - options.transports = options.transports ?? [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ] - options.cwd = join(options.cwd ?? process.cwd(), '/') - super(options) - } - - static CRITICAL = new Logger({ - logLevel: 'CRITICAL', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static ERROR = new Logger({ - logLevel: 'ERROR', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static WARN = new Logger({ - logLevel: 'WARN', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static INFO = new Logger({ - logLevel: 'INFO', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static DEBUG = new Logger({ - logLevel: 'DEBUG', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) - - static TRACE = new Logger({ - logLevel: 'TRACE', - transports: [ - { - transport: console, - formatter: new LogFormatterPretty(), - }, - ], - }) -} diff --git a/packages/common-nodejs/src/logger/index.ts b/packages/common-nodejs/src/logger/index.ts deleted file mode 100644 index 324bdf8b6..000000000 --- a/packages/common-nodejs/src/logger/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './LogFormatterPretty.js' -export * from './PrettyLogger.js' diff --git a/packages/common-nodejs/src/logger/LogFormatterPretty.ts b/packages/common-universal/src/logger/LogFormatterPretty.ts similarity index 89% rename from packages/common-nodejs/src/logger/LogFormatterPretty.ts rename to packages/common-universal/src/logger/LogFormatterPretty.ts index 7a1128ad3..67faee66b 100644 --- a/packages/common-nodejs/src/logger/LogFormatterPretty.ts +++ b/packages/common-universal/src/logger/LogFormatterPretty.ts @@ -1,5 +1,7 @@ -import { LogEntry, LogFormatter, LogLevel, toJSON } from '@marsfoundation/common-universal/logger' import chalk from 'chalk' +import { LogLevel } from './LogLevel.js' +import { LogEntry, LogFormatter } from './types.js' +import { toJSON } from './utils.js' const INDENT_SIZE = 4 @@ -25,7 +27,7 @@ export class LogFormatterPretty implements LogFormatter { const serviceOut = this.formatServicePretty(entry.service, this.options.colors) const messageOut = entry.message ? ` ${entry.message}` : '' const paramsOut = this.formatParametersPretty( - this.sanitize(entry.resolvedError ? { ...entry.resolvedError, ...entry.parameters } : (entry.parameters ?? {})), + entry.resolvedError ? { ...entry.resolvedError, ...entry.parameters } : (entry.parameters ?? {}), this.options.colors, ) @@ -83,8 +85,4 @@ export class LogFormatterPretty implements LogFormatter { } return colors ? ` ${chalk.gray('[')} ${chalk.yellow(service)} ${chalk.gray(']')}` : ` [ ${service} ]` } - - protected sanitize(parameters: object): object { - return JSON.parse(toJSON(parameters)) - } } diff --git a/packages/common-universal/src/logger/Logger.test.ts b/packages/common-universal/src/logger/Logger.test.ts index 69ad6fc47..21df439f3 100644 --- a/packages/common-universal/src/logger/Logger.test.ts +++ b/packages/common-universal/src/logger/Logger.test.ts @@ -1,9 +1,10 @@ /* eslint-disable */ -import { expect, formatCompact, mockFn } from 'earl' +import { MockObject, expect, formatCompact, mockFn } from 'earl' import { LogFormatterJson } from './LogFormatterJson.js' +import { LogFormatterPretty } from './LogFormatterPretty.js' import { Logger } from './Logger.js' -import { LogEntry } from './types.js' +import { LogEntry, LoggerTransport } from './types.js' describe(Logger.name, () => { it('calls correct transport', () => { @@ -146,6 +147,91 @@ describe(Logger.name, () => { }) }) + describe(`with ${LogFormatterPretty.name}`, () => { + it('supports bigint values', () => { + const { transport, baseLogger } = setup() + + baseLogger.info({ foo: 123n, bar: [4n, 56n] }) + const expectedOutput = [ + '00:00:00.000Z INFO', + '{', + ' "foo": "123",', + ' "bar": [', + ' "4",', + ' "56"', + ' ]', + '}', + ].join('\n') + expect(transport.log).toHaveBeenOnlyCalledWith(expectedOutput) + }) + + it('marks promised values', () => { + const { transport, baseLogger } = setup() + + baseLogger.info({ test: Promise.resolve(1234) }) + const expectedOutput = ['00:00:00.000Z INFO', '{', ' "test": "Promise"', '}'].join('\n') + expect(transport.log).toHaveBeenOnlyCalledWith(expectedOutput) + }) + + describe(Logger.prototype.for.name, () => { + it('single service (string)', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.for('FooService') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') + }) + + it('single service (object)', () => { + const { transport, baseLogger } = setup() + + class FooService {} + const instance = new FooService() + const logger = baseLogger.for(instance) + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService ] hello') + }) + + it('service with member', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.for('FooService').for('queue') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue ] hello') + }) + + it('service with tag', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red').for('FooService') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService:Red ] hello') + }) + + it('service with tag and member', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red').for('FooService').for('queue') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ FooService.queue:Red ] hello') + }) + + it('lone tag', () => { + const { transport, baseLogger } = setup() + + const logger = baseLogger.tag('Red') + logger.info('hello') + + expect(transport.log).toHaveBeenOnlyCalledWith('00:00:00.000Z INFO [ :Red ] hello') + }) + }) + }) + describe('error reporting', () => { const oldConsoleError = console.error beforeEach(() => { @@ -309,6 +395,23 @@ describe(Logger.name, () => { }) }) +function setup(): { transport: TestTransport; baseLogger: Logger } { + const transport = createTestTransport() + const baseLogger = new Logger({ + transports: [ + { + transport, + formatter: new LogFormatterPretty({ colors: false, utc: true }), + }, + ], + logLevel: 'TRACE', + getTime: () => new Date(0), + utc: true, + }) + return { transport, baseLogger } +} + +type TestTransport = MockObject function createTestTransport() { return { debug: mockFn((_: string): void => {}), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d2c4b444..d02bb91ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -377,9 +377,6 @@ importers: '@marsfoundation/common-universal': specifier: workspace:^ version: link:../common-universal - chalk: - specifier: ^5.3.0 - version: 5.3.0 dotenv: specifier: ^16.3.1 version: 16.3.1 From 1f226bcd7145600a54b1da6a2951f60effe9267c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 16:24:06 +0100 Subject: [PATCH 21/22] Add BROWSER logger --- .../src/logger/LogFormatterObject.ts | 15 ++++++++++++ .../common-universal/src/logger/Logger.ts | 24 ++++++++++++++----- packages/common-universal/src/logger/index.ts | 2 ++ packages/common-universal/src/logger/types.ts | 10 ++++---- 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 packages/common-universal/src/logger/LogFormatterObject.ts diff --git a/packages/common-universal/src/logger/LogFormatterObject.ts b/packages/common-universal/src/logger/LogFormatterObject.ts new file mode 100644 index 000000000..7602d6693 --- /dev/null +++ b/packages/common-universal/src/logger/LogFormatterObject.ts @@ -0,0 +1,15 @@ +import { LogEntry, LogFormatter } from './types.js' + +export class LogFormatterObject implements LogFormatter { + public format(entry: LogEntry): object { + const core = { + time: entry.time.toISOString(), + level: entry.level, + service: entry.service, + message: entry.message, + error: entry.resolvedError, + } + + return { ...core, parameters: entry.parameters } + } +} diff --git a/packages/common-universal/src/logger/Logger.ts b/packages/common-universal/src/logger/Logger.ts index 070af26f0..11d89dc9c 100644 --- a/packages/common-universal/src/logger/Logger.ts +++ b/packages/common-universal/src/logger/Logger.ts @@ -1,5 +1,7 @@ import { assertNever } from '../assert/assertNever.js' import { LogFormatterJson } from './LogFormatterJson.js' +import { LogFormatterObject } from './LogFormatterObject.js' +import { LogFormatterPretty } from './LogFormatterPretty.js' import { LEVEL, LogLevel } from './LogLevel.js' import { LogThrottle, LogThrottleOptions } from './LogThrottle.js' import { parseLogArguments } from './parseLogArguments.js' @@ -53,7 +55,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterJson(), + formatter: new LogFormatterPretty(), }, ], }) @@ -63,7 +65,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterJson(), + formatter: new LogFormatterPretty(), }, ], }) @@ -73,7 +75,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterJson(), + formatter: new LogFormatterPretty(), }, ], }) @@ -83,7 +85,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterJson(), + formatter: new LogFormatterPretty(), }, ], }) @@ -93,7 +95,7 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterJson(), + formatter: new LogFormatterPretty(), }, ], }) @@ -103,7 +105,17 @@ export class Logger implements ILogger { transports: [ { transport: console, - formatter: new LogFormatterJson(), + formatter: new LogFormatterPretty(), + }, + ], + }) + + static BROWSER = new Logger({ + logLevel: 'INFO', + transports: [ + { + transport: console, + formatter: new LogFormatterObject(), }, ], }) diff --git a/packages/common-universal/src/logger/index.ts b/packages/common-universal/src/logger/index.ts index 7c2dd2360..792d41a06 100644 --- a/packages/common-universal/src/logger/index.ts +++ b/packages/common-universal/src/logger/index.ts @@ -2,5 +2,7 @@ export * from './Logger.js' export * from './LogLevel.js' export * from './types.js' export * from './LogFormatterJson.js' +export * from './LogFormatterObject.js' +export * from './LogFormatterPretty.js' export * from './LogThrottle.js' export * from './utils.js' diff --git a/packages/common-universal/src/logger/types.ts b/packages/common-universal/src/logger/types.ts index 09934ed3e..a1e812e12 100644 --- a/packages/common-universal/src/logger/types.ts +++ b/packages/common-universal/src/logger/types.ts @@ -2,14 +2,14 @@ import { LogLevel } from './LogLevel.js' import { ResolvedError } from './resolveError.js' export interface LoggerTransport { - debug(message: string): void - log(message: string): void - warn(message: string): void - error(message: string): void + debug(message: string | object): void + log(message: string | object): void + warn(message: string | object): void + error(message: string | object): void } export interface LogFormatter { - format(entry: LogEntry): string + format(entry: LogEntry): string | object } export interface LoggerTransportOptions { From ce9fc9b69f67afdcaea263fbd2071286ffaba549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tarczy=C5=84ski?= Date: Thu, 6 Mar 2025 16:29:37 +0100 Subject: [PATCH 22/22] Lint --- packages/common-universal/src/logger/Logger.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/common-universal/src/logger/Logger.test.ts b/packages/common-universal/src/logger/Logger.test.ts index 21df439f3..6348fdc20 100644 --- a/packages/common-universal/src/logger/Logger.test.ts +++ b/packages/common-universal/src/logger/Logger.test.ts @@ -412,11 +412,11 @@ function setup(): { transport: TestTransport; baseLogger: Logger } { } type TestTransport = MockObject -function createTestTransport() { +function createTestTransport(): TestTransport { return { - debug: mockFn((_: string): void => {}), - log: mockFn((_: string): void => {}), - warn: mockFn((_: string): void => {}), - error: mockFn((_: string): void => {}), + debug: mockFn((_: string | object) => {}), + log: mockFn((_: string | object) => {}), + warn: mockFn((_: string | object) => {}), + error: mockFn((_: string | object) => {}), } }