From 265ff3179a59ff5b881c3ec8cbc2450ed01b87e2 Mon Sep 17 00:00:00 2001 From: Jamie King Date: Mon, 4 Mar 2024 05:07:59 -0700 Subject: [PATCH] feat(sdk-logs): added colors option to ConsoleLogRecordExporter --- experimental/packages/sdk-logs/package.json | 1 + .../src/export/ConsoleLogRecordExporter.ts | 12 +- experimental/packages/sdk-logs/src/index.ts | 1 + experimental/packages/sdk-logs/src/types.ts | 5 + .../export/ConsoleLogRecordExporter.test.ts | 126 ++++++++++++------ package-lock.json | 9 +- 6 files changed, 107 insertions(+), 47 deletions(-) diff --git a/experimental/packages/sdk-logs/package.json b/experimental/packages/sdk-logs/package.json index 9dcc7c3d904..3a6b3ae6042 100644 --- a/experimental/packages/sdk-logs/package.json +++ b/experimental/packages/sdk-logs/package.json @@ -80,6 +80,7 @@ "@types/mocha": "10.0.6", "@types/node": "18.6.5", "@types/sinon": "10.0.20", + "ansi-regex": "^5.0.1", "babel-plugin-istanbul": "6.1.1", "codecov": "3.8.3", "cross-var": "1.1.0", diff --git a/experimental/packages/sdk-logs/src/export/ConsoleLogRecordExporter.ts b/experimental/packages/sdk-logs/src/export/ConsoleLogRecordExporter.ts index edf7c0bf8c0..7bb614e9e79 100644 --- a/experimental/packages/sdk-logs/src/export/ConsoleLogRecordExporter.ts +++ b/experimental/packages/sdk-logs/src/export/ConsoleLogRecordExporter.ts @@ -17,6 +17,7 @@ import { ExportResult, hrTimeToMicroseconds } from '@opentelemetry/core'; import { ExportResultCode } from '@opentelemetry/core'; +import type { ConsoleLogRecordExporterConfig } from '../types'; import type { ReadableLogRecord } from './ReadableLogRecord'; import type { LogRecordExporter } from './LogRecordExporter'; @@ -27,6 +28,15 @@ import type { LogRecordExporter } from './LogRecordExporter'; /* eslint-disable no-console */ export class ConsoleLogRecordExporter implements LogRecordExporter { + private _dirOpts: { + depth: number; + colors?: boolean; + } = { depth: 3 }; + + constructor(config: ConsoleLogRecordExporterConfig = {}) { + if (typeof config.colors !== 'undefined') + this._dirOpts.colors = config.colors; + } /** * Export logs. * @param logs @@ -73,7 +83,7 @@ export class ConsoleLogRecordExporter implements LogRecordExporter { done?: (result: ExportResult) => void ): void { for (const logRecord of logRecords) { - console.dir(this._exportInfo(logRecord), { depth: 3 }); + console.dir(this._exportInfo(logRecord), this._dirOpts); } done?.({ code: ExportResultCode.SUCCESS }); } diff --git a/experimental/packages/sdk-logs/src/index.ts b/experimental/packages/sdk-logs/src/index.ts index b7347a28451..b8cc804f453 100644 --- a/experimental/packages/sdk-logs/src/index.ts +++ b/experimental/packages/sdk-logs/src/index.ts @@ -19,6 +19,7 @@ export { LogRecordLimits, BufferConfig, BatchLogRecordProcessorBrowserConfig, + ConsoleLogRecordExporterConfig, } from './types'; export { LoggerProvider } from './LoggerProvider'; export { LogRecord } from './LogRecord'; diff --git a/experimental/packages/sdk-logs/src/types.ts b/experimental/packages/sdk-logs/src/types.ts index 27aefa540fe..d80d65b790d 100644 --- a/experimental/packages/sdk-logs/src/types.ts +++ b/experimental/packages/sdk-logs/src/types.ts @@ -62,3 +62,8 @@ export interface BatchLogRecordProcessorBrowserConfig extends BufferConfig { * on mobile, switches to a different app. Auto flush is enabled by default. */ disableAutoFlushOnDocumentHide?: boolean; } + +export interface ConsoleLogRecordExporterConfig { + /** Force colorization of console logs instead of relying on detection */ + colors?: boolean; +} diff --git a/experimental/packages/sdk-logs/test/common/export/ConsoleLogRecordExporter.test.ts b/experimental/packages/sdk-logs/test/common/export/ConsoleLogRecordExporter.test.ts index bd28e8e12e9..1b9ce1f79ed 100644 --- a/experimental/packages/sdk-logs/test/common/export/ConsoleLogRecordExporter.test.ts +++ b/experimental/packages/sdk-logs/test/common/export/ConsoleLogRecordExporter.test.ts @@ -16,6 +16,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; +import * as ansiRegex from 'ansi-regex'; import { SeverityNumber } from '@opentelemetry/api-logs'; import { @@ -24,60 +25,97 @@ import { SimpleLogRecordProcessor, } from './../../../src'; -/* eslint-disable no-console */ +const isColorText = (text: string) => ansiRegex().test(text); + describe('ConsoleLogRecordExporter', () => { - let previousConsoleDir: typeof console.dir; + describe('export', () => { + it('should export information about log record', () => { + const consoleExporter = new ConsoleLogRecordExporter(); + const consoleSpy = sinon.spy(console, 'dir'); + const spyExport = sinon.spy(consoleExporter, 'export'); + const provider = new LoggerProvider(); + provider.addLogRecordProcessor( + new SimpleLogRecordProcessor(consoleExporter) + ); - beforeEach(() => { - previousConsoleDir = console.dir; - console.dir = () => {}; - }); + const stdoutStub = sinon.stub(process.stdout, 'write'); + provider.getLogger('default').emit({ + body: 'body1', + severityNumber: SeverityNumber.DEBUG, + severityText: 'DEBUG', + }); + const consoleOutput = stdoutStub.getCalls()[0].firstArg; + stdoutStub.restore(); - afterEach(() => { - console.dir = previousConsoleDir; - }); + const logRecords = spyExport.args[0]; + const firstLogRecord = logRecords[0][0]; + const consoleArgs = consoleSpy.args[0]; + const consoleLogRecord = consoleArgs[0]; + const keys = Object.keys(consoleLogRecord).sort().join(','); + consoleSpy.restore(); - describe('export', () => { - it('should export information about log record', () => { - assert.doesNotThrow(() => { - const consoleExporter = new ConsoleLogRecordExporter(); - const spyConsole = sinon.spy(console, 'dir'); - const spyExport = sinon.spy(consoleExporter, 'export'); - const provider = new LoggerProvider(); - provider.addLogRecordProcessor( - new SimpleLogRecordProcessor(consoleExporter) - ); + const expectedKeys = [ + 'attributes', + 'body', + 'severityNumber', + 'severityText', + 'spanId', + 'timestamp', + 'traceFlags', + 'traceId', + ].join(','); + + assert.equal(firstLogRecord.body, 'body1'); + assert.equal(firstLogRecord.severityNumber, SeverityNumber.DEBUG); + assert.equal(firstLogRecord.severityText, 'DEBUG'); + assert.equal(keys, expectedKeys); - provider.getLogger('default').emit({ - body: 'body1', - severityNumber: SeverityNumber.DEBUG, - severityText: 'DEBUG', - }); + assert.ok(spyExport.calledOnce); - const logRecords = spyExport.args[0]; - const firstLogRecord = logRecords[0][0]; - const consoleArgs = spyConsole.args[0]; - const consoleLogRecord = consoleArgs[0]; - const keys = Object.keys(consoleLogRecord).sort().join(','); + const containsColor = isColorText(consoleOutput); + assert.equal( + containsColor, + (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0') || + process.stdout.isTTY + ); + }); + it('should colorize log records', () => { + const consoleExporter = new ConsoleLogRecordExporter({ colors: true }); + const provider = new LoggerProvider(); + provider.addLogRecordProcessor( + new SimpleLogRecordProcessor(consoleExporter) + ); - const expectedKeys = [ - 'attributes', - 'body', - 'severityNumber', - 'severityText', - 'spanId', - 'timestamp', - 'traceFlags', - 'traceId', - ].join(','); + const stdoutStub = sinon.stub(process.stdout, 'write'); + provider.getLogger('default').emit({ + body: 'body2', + severityNumber: SeverityNumber.DEBUG, + severityText: 'DEBUG', + }); + const consoleOutput = stdoutStub.getCalls()[0].firstArg; + stdoutStub.restore(); - assert.ok(firstLogRecord.body === 'body1'); - assert.ok(firstLogRecord.severityNumber === SeverityNumber.DEBUG); - assert.ok(firstLogRecord.severityText === 'DEBUG'); - assert.ok(keys === expectedKeys, 'expectedKeys'); + const containsColor = isColorText(consoleOutput); + assert.ok(containsColor); + }); + it('should not colorize log records', () => { + const consoleExporter = new ConsoleLogRecordExporter({ colors: false }); + const provider = new LoggerProvider(); + provider.addLogRecordProcessor( + new SimpleLogRecordProcessor(consoleExporter) + ); - assert.ok(spyExport.calledOnce); + const stdoutStub = sinon.stub(process.stdout, 'write'); + provider.getLogger('default').emit({ + body: 'body3', + severityNumber: SeverityNumber.DEBUG, + severityText: 'DEBUG', }); + const consoleOutput = stdoutStub.getCalls()[0].firstArg; + stdoutStub.restore(); + + const doesNotContainColor = !isColorText(consoleOutput); + assert.ok(doesNotContainColor); }); }); }); diff --git a/package-lock.json b/package-lock.json index 8ad74075f89..7850ca22982 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4750,6 +4750,7 @@ "@types/mocha": "10.0.6", "@types/node": "18.6.5", "@types/sinon": "10.0.20", + "ansi-regex": "^5.0.1", "babel-plugin-istanbul": "6.1.1", "codecov": "3.8.3", "cross-var": "1.1.0", @@ -12238,7 +12239,8 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { "node": ">=8" } @@ -43335,6 +43337,7 @@ "@types/mocha": "10.0.6", "@types/node": "18.6.5", "@types/sinon": "10.0.20", + "ansi-regex": "^5.0.1", "babel-plugin-istanbul": "6.1.1", "codecov": "3.8.3", "cross-var": "1.1.0", @@ -45963,7 +45966,9 @@ "dev": true }, "ansi-regex": { - "version": "5.0.1" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0",