From 3002ca97d52b73fcdaaa8bebcf73dde291c25b0e Mon Sep 17 00:00:00 2001 From: DrDreo Date: Wed, 17 Dec 2025 22:27:43 +0100 Subject: [PATCH 01/17] feat: add `@opentelemetry/instrumentation-console` package for capturing console method calls as OpenTelemetry logs --- OPEN_QUESTIONS.md | 7 + package-lock.json | 13 + packages/instrumentation-console/README.md | 113 ++++++++ packages/instrumentation-console/package.json | 47 +++ packages/instrumentation-console/src/index.ts | 21 ++ .../src/instrumentation.test.ts | 269 ++++++++++++++++++ .../src/instrumentation.ts | 128 +++++++++ .../instrumentation-console/src/semconv.ts | 32 +++ packages/instrumentation-console/src/types.ts | 39 +++ packages/instrumentation-console/src/utils.ts | 25 ++ .../instrumentation-console/tsconfig.json | 9 + .../instrumentation-console/tsdown.config.ts | 6 + 12 files changed, 709 insertions(+) create mode 100644 OPEN_QUESTIONS.md create mode 100644 packages/instrumentation-console/README.md create mode 100644 packages/instrumentation-console/package.json create mode 100644 packages/instrumentation-console/src/index.ts create mode 100644 packages/instrumentation-console/src/instrumentation.test.ts create mode 100644 packages/instrumentation-console/src/instrumentation.ts create mode 100644 packages/instrumentation-console/src/semconv.ts create mode 100644 packages/instrumentation-console/src/types.ts create mode 100644 packages/instrumentation-console/src/utils.ts create mode 100644 packages/instrumentation-console/tsconfig.json create mode 100644 packages/instrumentation-console/tsdown.config.ts diff --git a/OPEN_QUESTIONS.md b/OPEN_QUESTIONS.md new file mode 100644 index 00000000..0b2e5e68 --- /dev/null +++ b/OPEN_QUESTIONS.md @@ -0,0 +1,7 @@ +what semantic attributes? + - browser.console for event and browser.console.method for type + +secerity for log vs info? +- INFO = 9? +is the traceparent of active span enough? +- from my testing that can get disconnected when the frontend was served by the backend and we could use the traceparent from meta tags \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index def685bd..6fe91be3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1393,6 +1393,10 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-console": { + "resolved": "packages/instrumentation-console", + "link": true + }, "node_modules/@opentelemetry/instrumentation-user-action": { "resolved": "packages/instrumentation-user-action", "link": true @@ -7185,6 +7189,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/instrumentation-console": { + "name": "@opentelemetry/instrumentation-console", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "*", + "@opentelemetry/instrumentation": "*" + } + }, "packages/instrumentation-user-action": { "name": "@opentelemetry/instrumentation-user-action", "version": "0.1.0", diff --git a/packages/instrumentation-console/README.md b/packages/instrumentation-console/README.md new file mode 100644 index 00000000..3d6f66ef --- /dev/null +++ b/packages/instrumentation-console/README.md @@ -0,0 +1,113 @@ +# OpenTelemetry Console Instrumentation for Web + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + +This module provides automatic instrumentation for *console* methods (log, warn, error, info, debug) for Web applications, emitting them as OpenTelemetry logs which may be collected using the [`@opentelemetry/sdk-logs`](https://www.npmjs.com/package/@opentelemetry/sdk-logs) package. + +Compatible with OpenTelemetry JS API and SDK `1.0+`. + +## Installation + +```bash +npm install @opentelemetry/instrumentation-console +``` + +## Usage + +### Initialize + +```typescript +import { logs } from '@opentelemetry/api-logs'; +import { + ConsoleLogRecordExporter, + LoggerProvider, + SimpleLogRecordProcessor, +} from '@opentelemetry/sdk-logs'; +import { ConsoleInstrumentation } from '@opentelemetry/instrumentation-console'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; + +const logProvider = new LoggerProvider({ + processors: [ + new SimpleLogRecordProcessor(new ConsoleLogRecordExporter()), + ], +}); +logs.setGlobalLoggerProvider(logProvider); + +registerInstrumentations({ + instrumentations: [ + new ConsoleInstrumentation(), + ], +}); + +// Now all console calls will be captured as OpenTelemetry logs +console.log('Hello, World!'); +console.error('Something went wrong!'); +``` + +## Configuration + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `logMethods` | `ConsoleMethod[]` | `['log', 'warn', 'error', 'info', 'debug']` | Console methods to instrument | +| `messageSerializer` | `(args: unknown[]) => string` | See below | Custom serializer for console arguments | + +### logMethods + +Configure which console methods to instrument: + +```typescript +new ConsoleInstrumentation({ + logMethods: ['error', 'warn'], // Only capture errors and warnings +}); +``` + +### messageSerializer + +Provide a custom serializer for console arguments: + +```typescript +new ConsoleInstrumentation({ + messageSerializer: (args) => args.map(arg => String(arg)).join(' | '), +}); +``` + +The default serializer joins arguments as strings, with objects serialized via `JSON.stringify`. Circular references are handled gracefully by falling back to `String(arg)`. + +## Severity Mapping + +Console methods are mapped to OpenTelemetry severity levels: + +| Console Method | SeverityNumber | SeverityText | +|----------------|----------------|--------------| +| `debug` | DEBUG (5) | 'debug' | +| `log` | INFO (9) | 'log' | +| `info` | INFO (9) | 'info' | +| `warn` | WARN (13) | 'warn' | +| `error` | ERROR (17) | 'error' | + +## Semantic Conventions + +This package emits logs with the following attributes: + +| Attribute | Description | +|-----------|-------------| +| `browser.console.method` | The console method that was called (e.g., 'log', 'error') | + +Event name: `browser.console` + +## Useful links + +- For more information on OpenTelemetry, visit: +- For more about OpenTelemetry Browser: +- For help or feedback on this project, join us in [GitHub Discussions][discussions-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[discussions-url]: https://github.com/open-telemetry/opentelemetry-browser/discussions/landing +[license-url]: https://github.com/open-telemetry/opentelemetry-browser/blob/main/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[npm-url]: https://www.npmjs.com/package/@opentelemetry/instrumentation-console +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Finstrumentation-console.svg diff --git a/packages/instrumentation-console/package.json b/packages/instrumentation-console/package.json new file mode 100644 index 00000000..fd04dd1e --- /dev/null +++ b/packages/instrumentation-console/package.json @@ -0,0 +1,47 @@ +{ + "name": "@opentelemetry/instrumentation-console", + "version": "0.1.0", + "description": "OpenTelemetry instrumentation that captures console log/warn/error/info/debug calls and emits them as OpenTelemetry logs.", + "keywords": [ + "opentelemetry", + "browser", + "web", + "instrumentation", + "console" + ], + "homepage": "https://github.com/open-telemetry/opentelemetry-browser", + "bugs": "https://github.com/open-telemetry/opentelemetry-browser/issues", + "license": "Apache-2.0", + "author": "OpenTelemetry Authors", + "repository": { + "type": "git", + "url": "git+https://github.com/open-telemetry/opentelemetry-browser.git", + "directory": "packages/instrumentation-console" + }, + "type": "module", + "types": "./dist/index.d.ts", + "exports": { + ".": "./dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsdown", + "check-types": "tsc", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest --coverage" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "dependencies": { + "@opentelemetry/api-logs": "*", + "@opentelemetry/core": "*", + "@opentelemetry/instrumentation": "*" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/instrumentation-console/src/index.ts b/packages/instrumentation-console/src/index.ts new file mode 100644 index 00000000..c716edf5 --- /dev/null +++ b/packages/instrumentation-console/src/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ConsoleInstrumentation } from './instrumentation.ts'; +export type { + ConsoleInstrumentationConfig, + ConsoleMethod, +} from './types.ts'; diff --git a/packages/instrumentation-console/src/instrumentation.test.ts b/packages/instrumentation-console/src/instrumentation.test.ts new file mode 100644 index 00000000..e481ca15 --- /dev/null +++ b/packages/instrumentation-console/src/instrumentation.test.ts @@ -0,0 +1,269 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SeverityNumber } from '@opentelemetry/api-logs'; +import type { InMemoryLogRecordExporter } from '@opentelemetry/sdk-logs'; +import { setupTestLogExporter } from '@opentelemetry/test-utils'; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { ConsoleInstrumentation } from './instrumentation.ts'; +import { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts'; + +describe('ConsoleInstrumentation', () => { + let inMemoryExporter: InMemoryLogRecordExporter; + let instrumentation: ConsoleInstrumentation; + + beforeAll(() => { + inMemoryExporter = setupTestLogExporter(); + }); + + beforeEach(() => { + inMemoryExporter.reset(); + instrumentation = new ConsoleInstrumentation(); + }); + + afterEach(() => { + instrumentation.disable(); + }); + + describe('severity mapping', () => { + it('should emit a log with DEBUG severity for console.debug', () => { + console.debug('debug message'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + + const log = logs[0]; + expect(log?.severityNumber).toBe(SeverityNumber.DEBUG); + expect(log?.severityText).toBe('debug'); + expect(log?.body).toBe('debug message'); + expect(log?.eventName).toBe(CONSOLE_LOG_EVENT_NAME); + expect(log?.attributes[ATTR_CONSOLE_METHOD]).toBe('debug'); + }); + + it('should emit a log with INFO severity for console.log', () => { + console.log('log message'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + + const log = logs[0]; + expect(log?.severityNumber).toBe(SeverityNumber.INFO); + expect(log?.severityText).toBe('log'); + expect(log?.body).toBe('log message'); + expect(log?.attributes[ATTR_CONSOLE_METHOD]).toBe('log'); + }); + + it('should emit a log with INFO severity for console.info', () => { + console.info('info message'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + + const log = logs[0]; + expect(log?.severityNumber).toBe(SeverityNumber.INFO); + expect(log?.severityText).toBe('info'); + expect(log?.body).toBe('info message'); + expect(log?.attributes[ATTR_CONSOLE_METHOD]).toBe('info'); + }); + + it('should emit a log with WARN severity for console.warn', () => { + console.warn('warn message'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + + const log = logs[0]; + expect(log?.severityNumber).toBe(SeverityNumber.WARN); + expect(log?.severityText).toBe('warn'); + expect(log?.body).toBe('warn message'); + expect(log?.attributes[ATTR_CONSOLE_METHOD]).toBe('warn'); + }); + + it('should emit a log with ERROR severity for console.error', () => { + console.error('error message'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + + const log = logs[0]; + expect(log?.severityNumber).toBe(SeverityNumber.ERROR); + expect(log?.severityText).toBe('error'); + expect(log?.body).toBe('error message'); + expect(log?.attributes[ATTR_CONSOLE_METHOD]).toBe('error'); + }); + }); + + describe('logMethods config', () => { + it('should only instrument configured methods', () => { + instrumentation.disable(); + inMemoryExporter.reset(); + instrumentation = new ConsoleInstrumentation({ + enabled: false, + logMethods: ['error', 'warn'], + }); + instrumentation.enable(); + + console.log('log message'); + console.info('info message'); + console.debug('debug message'); + console.warn('warn message'); + console.error('error message'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(2); + + expect(logs[0]?.severityText).toBe('warn'); + expect(logs[1]?.severityText).toBe('error'); + }); + + it('should not emit any logs when logMethods is empty', () => { + instrumentation.disable(); + inMemoryExporter.reset(); + instrumentation = new ConsoleInstrumentation({ + enabled: false, + logMethods: [], + }); + instrumentation.enable(); + + console.log('log message'); + console.warn('warn message'); + console.error('error message'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(0); + }); + }); + + describe('default serialization', () => { + it('should serialize primitive values', () => { + console.log('string', 123, true, null, undefined); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + expect(logs[0]?.body).toBe('string 123 true null undefined'); + }); + + it('should serialize objects as JSON', () => { + console.log({ name: 'test', value: 42 }); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + expect(logs[0]?.body).toBe('{"name":"test","value":42}'); + }); + + it('should serialize multiple arguments', () => { + console.log('User:', { id: 1 }, 'logged in'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + expect(logs[0]?.body).toBe('User: {"id":1} logged in'); + }); + + it('should serialize arrays as JSON', () => { + console.log([1, 2, 3]); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + expect(logs[0]?.body).toBe('[1,2,3]'); + }); + }); + + describe('custom messageSerializer', () => { + it('should use custom serializer when provided', () => { + instrumentation.disable(); + instrumentation = new ConsoleInstrumentation({ + enabled: false, + messageSerializer: (args) => + args.map((arg) => `[${typeof arg}]`).join('-'), + }); + instrumentation.enable(); + + console.log('hello', 123, { test: true }); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + expect(logs[0]?.body).toBe('[string]-[number]-[object]'); + }); + }); + + describe('circular reference handling', () => { + it('should handle circular references by falling back to String()', () => { + const circularObj: Record = { name: 'circular' }; + circularObj['self'] = circularObj; + + console.log(circularObj); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + // Should fall back to String() which returns [object Object] + expect(logs[0]?.body).toBe('[object Object]'); + }); + }); + + describe('original console behavior', () => { + it('should still call the original console method', () => { + let called = false; + const originalLog = console.log; + + console.log = (...args: unknown[]) => { + called = true; + originalLog.apply(console, args); + }; + + instrumentation = new ConsoleInstrumentation(); + + console.log('test'); + + expect(called).toBe(true); + + instrumentation.disable(); + console.log = originalLog; + }); + }); + + describe('enable/disable lifecycle', () => { + it('should not emit logs when disabled', () => { + instrumentation.disable(); + inMemoryExporter.reset(); + + console.log('should not be captured'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(0); + }); + + it('should emit logs when re-enabled', () => { + instrumentation.disable(); + inMemoryExporter.reset(); + instrumentation.enable(); + + console.log('should be captured'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + expect(logs[0]?.body).toBe('should be captured'); + }); + + it('should restore original console method behavior after disable', () => { + const wrappedLog = console.log; + + instrumentation.disable(); + + // After disable, console.log should be different (unwrapped) + expect(console.log).not.toBe(wrappedLog); + }); + }); +}); diff --git a/packages/instrumentation-console/src/instrumentation.ts b/packages/instrumentation-console/src/instrumentation.ts new file mode 100644 index 00000000..823fa84a --- /dev/null +++ b/packages/instrumentation-console/src/instrumentation.ts @@ -0,0 +1,128 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { trace, context } from '@opentelemetry/api'; +import { SeverityNumber } from '@opentelemetry/api-logs'; +import { InstrumentationBase } from '@opentelemetry/instrumentation'; +import { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts'; +import type { ConsoleInstrumentationConfig, ConsoleMethod } from './types.ts'; +import { getTraceParent } from './utils.ts'; + +const DEFAULT_LOG_METHODS: ConsoleMethod[] = [ + 'log', + 'warn', + 'error', + 'info', + 'debug', +]; + +const SEVERITY_MAP: Record = { + debug: SeverityNumber.DEBUG, + log: SeverityNumber.INFO, + info: SeverityNumber.INFO, + warn: SeverityNumber.WARN, + error: SeverityNumber.ERROR, +}; + +/** + * Default serializer for console arguments. + * Joins arguments as strings. + */ +function defaultMessageSerializer(args: unknown[]): string { + return args + .map((arg) => { + if (typeof arg === 'object' && arg !== null) { + try { + return JSON.stringify(arg); + } catch { + // Circular reference or other error, fallback to String + return String(arg); + } + } + return String(arg); + }) + .join(' '); +} + +/** + * OpenTelemetry instrumentation that captures console calls and emits them as OpenTelemetry logs. + */ +export class ConsoleInstrumentation extends InstrumentationBase { + constructor(config: ConsoleInstrumentationConfig = {}) { + super('@opentelemetry/instrumentation-console', '0.1.0', config); + } + + protected override init() { + return []; + } + + private _getMessageSerializer(): (args: unknown[]) => string { + return this._config.messageSerializer ?? defaultMessageSerializer; + } + + private _getLogMethods(): ConsoleMethod[] { + return this._config.logMethods ?? DEFAULT_LOG_METHODS; + } + + private _patchConsoleMethod( + method: ConsoleMethod, + ): (original: Console[ConsoleMethod]) => Console[ConsoleMethod] { + const instrumentation = this; + // Get the page-level traceparent for fallback + const pageTraceParent = getTraceParent(); + const serializer = instrumentation._getMessageSerializer(); + + return function patchConsoleMethod(original: Console[ConsoleMethod]) { + return function (this: Console, ...args: unknown[]) { + // Get the active span context at call time, fallback to page traceparent + const activeSpan = trace.getSpan(context.active()); + const spanContext = activeSpan?.spanContext() ?? pageTraceParent; + const body = serializer(args); + + instrumentation.logger.emit({ + body, + eventName: CONSOLE_LOG_EVENT_NAME, + severityNumber: SEVERITY_MAP[method], + severityText: method, + attributes: { + [ATTR_CONSOLE_METHOD]: method, + ...(spanContext && { + 'trace.id': spanContext.traceId, + 'span.id': spanContext.spanId, + }), + }, + }); + + return original.apply(this, args); + } as Console[ConsoleMethod]; + }; + } + + override enable(): void { + const methods = this._getLogMethods(); + for (const method of methods) { + if (typeof console[method] === 'function') { + this._wrap(console, method, this._patchConsoleMethod(method)); + } + } + } + + override disable(): void { + const methods = this._getLogMethods(); + for (const method of methods) { + this._unwrap(console, method); + } + } +} diff --git a/packages/instrumentation-console/src/semconv.ts b/packages/instrumentation-console/src/semconv.ts new file mode 100644 index 00000000..25a8371d --- /dev/null +++ b/packages/instrumentation-console/src/semconv.ts @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This file contains a copy of unstable semantic convention definitions + * used by this package. + * @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv + */ + +/** + * Event name for console log events. + */ +export const CONSOLE_LOG_EVENT_NAME = 'browser.console'; + +/** + * The console method that was called (e.g., 'log', 'warn', 'error', 'info', 'debug'). + * @example 'error' + */ +export const ATTR_CONSOLE_METHOD = 'browser.console.method'; diff --git a/packages/instrumentation-console/src/types.ts b/packages/instrumentation-console/src/types.ts new file mode 100644 index 00000000..bf21d1f9 --- /dev/null +++ b/packages/instrumentation-console/src/types.ts @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; + +/** + * Console methods that can be instrumented. + */ +export type ConsoleMethod = 'log' | 'warn' | 'error' | 'info' | 'debug'; + +/** + * ConsoleInstrumentation Configuration + */ +export interface ConsoleInstrumentationConfig extends InstrumentationConfig { + /** + * Console methods to instrument. + * @default ['log', 'warn', 'error', 'info', 'debug'] + */ + logMethods?: ConsoleMethod[]; + + /** + * Custom serializer for console arguments. + * @default Joins args as strings + */ + messageSerializer?: (args: unknown[]) => string; +} diff --git a/packages/instrumentation-console/src/utils.ts b/packages/instrumentation-console/src/utils.ts new file mode 100644 index 00000000..faee8130 --- /dev/null +++ b/packages/instrumentation-console/src/utils.ts @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { SpanContext } from '@opentelemetry/api'; +import { parseTraceParent, TRACE_PARENT_HEADER } from '@opentelemetry/core'; + +export function getTraceParent(): SpanContext | null { + const metaElement = Array.from(document.getElementsByTagName('meta')).find( + e => e.getAttribute('name') === TRACE_PARENT_HEADER + ); + return parseTraceParent((metaElement && metaElement.content) || ''); +} diff --git a/packages/instrumentation-console/tsconfig.json b/packages/instrumentation-console/tsconfig.json new file mode 100644 index 00000000..485bbd71 --- /dev/null +++ b/packages/instrumentation-console/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "skipLibCheck": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/instrumentation-console/tsdown.config.ts b/packages/instrumentation-console/tsdown.config.ts new file mode 100644 index 00000000..b769b79e --- /dev/null +++ b/packages/instrumentation-console/tsdown.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'tsdown'; +import baseConfig from '../../tsdown.config.ts'; + +export default defineConfig({ + ...baseConfig, +}); From e92f1f32e326b717d5321c0abfbd50a3d5dcedcd Mon Sep 17 00:00:00 2001 From: DrDreo Date: Wed, 17 Dec 2025 22:28:18 +0100 Subject: [PATCH 02/17] chore: format --- packages/instrumentation-console/src/instrumentation.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/instrumentation-console/src/instrumentation.ts b/packages/instrumentation-console/src/instrumentation.ts index 823fa84a..416a8c16 100644 --- a/packages/instrumentation-console/src/instrumentation.ts +++ b/packages/instrumentation-console/src/instrumentation.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { trace, context } from '@opentelemetry/api'; import { SeverityNumber } from '@opentelemetry/api-logs'; import { InstrumentationBase } from '@opentelemetry/instrumentation'; From eab5e16c06030686433339cc87ba0b2e12d9df5d Mon Sep 17 00:00:00 2001 From: DrDreo Date: Wed, 17 Dec 2025 22:43:58 +0100 Subject: [PATCH 03/17] chore: remove open questions --- OPEN_QUESTIONS.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 OPEN_QUESTIONS.md diff --git a/OPEN_QUESTIONS.md b/OPEN_QUESTIONS.md deleted file mode 100644 index 0b2e5e68..00000000 --- a/OPEN_QUESTIONS.md +++ /dev/null @@ -1,7 +0,0 @@ -what semantic attributes? - - browser.console for event and browser.console.method for type - -secerity for log vs info? -- INFO = 9? -is the traceparent of active span enough? -- from my testing that can get disconnected when the frontend was served by the backend and we could use the traceparent from meta tags \ No newline at end of file From 5ce4f9aab5be33185da9ad6f8e77256808a79f8c Mon Sep 17 00:00:00 2001 From: DrDreo Date: Wed, 17 Dec 2025 22:48:32 +0100 Subject: [PATCH 04/17] chore: trigger CLA verification --- packages/instrumentation-console/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/instrumentation-console/README.md b/packages/instrumentation-console/README.md index 3d6f66ef..58192b07 100644 --- a/packages/instrumentation-console/README.md +++ b/packages/instrumentation-console/README.md @@ -9,6 +9,7 @@ Compatible with OpenTelemetry JS API and SDK `1.0+`. ## Installation + ```bash npm install @opentelemetry/instrumentation-console ``` From 43402e84ccbaa2aa62d9e6124b5304109b981d7f Mon Sep 17 00:00:00 2001 From: DrDreo Date: Wed, 17 Dec 2025 22:55:56 +0100 Subject: [PATCH 05/17] chore: trigger CLA verification --- packages/instrumentation-console/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/instrumentation-console/README.md b/packages/instrumentation-console/README.md index 58192b07..3d6f66ef 100644 --- a/packages/instrumentation-console/README.md +++ b/packages/instrumentation-console/README.md @@ -9,7 +9,6 @@ Compatible with OpenTelemetry JS API and SDK `1.0+`. ## Installation - ```bash npm install @opentelemetry/instrumentation-console ``` From 1b992d6b9fbe850adf2cd485acbd6594b87f69ba Mon Sep 17 00:00:00 2001 From: Dr Dreo Date: Thu, 18 Dec 2025 12:59:26 +0100 Subject: [PATCH 06/17] Update packages/instrumentation-console/package.json Co-authored-by: Jared Freeze --- packages/instrumentation-console/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/instrumentation-console/package.json b/packages/instrumentation-console/package.json index fd04dd1e..80843f72 100644 --- a/packages/instrumentation-console/package.json +++ b/packages/instrumentation-console/package.json @@ -37,9 +37,9 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/api-logs": "*", - "@opentelemetry/core": "*", - "@opentelemetry/instrumentation": "*" + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/instrumentation": "0.208.0" }, "publishConfig": { "access": "public" From cf2687d6e4c8b9fbfc971eb10b63d46884e89d2f Mon Sep 17 00:00:00 2001 From: DrDreo Date: Fri, 2 Jan 2026 12:21:15 +0100 Subject: [PATCH 07/17] chore: clean up imports and fix linting --- packages/instrumentation-console/src/instrumentation.ts | 2 +- packages/instrumentation-console/src/utils.ts | 8 ++++---- packages/instrumentation-console/tsconfig.json | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/instrumentation-console/src/instrumentation.ts b/packages/instrumentation-console/src/instrumentation.ts index 416a8c16..acd6a30b 100644 --- a/packages/instrumentation-console/src/instrumentation.ts +++ b/packages/instrumentation-console/src/instrumentation.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { trace, context } from '@opentelemetry/api'; +import { context, trace } from '@opentelemetry/api'; import { SeverityNumber } from '@opentelemetry/api-logs'; import { InstrumentationBase } from '@opentelemetry/instrumentation'; import { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts'; diff --git a/packages/instrumentation-console/src/utils.ts b/packages/instrumentation-console/src/utils.ts index faee8130..6d280794 100644 --- a/packages/instrumentation-console/src/utils.ts +++ b/packages/instrumentation-console/src/utils.ts @@ -18,8 +18,8 @@ import type { SpanContext } from '@opentelemetry/api'; import { parseTraceParent, TRACE_PARENT_HEADER } from '@opentelemetry/core'; export function getTraceParent(): SpanContext | null { - const metaElement = Array.from(document.getElementsByTagName('meta')).find( - e => e.getAttribute('name') === TRACE_PARENT_HEADER - ); - return parseTraceParent((metaElement && metaElement.content) || ''); + const metaElement = Array.from(document.getElementsByTagName('meta')).find( + (e) => e.getAttribute('name') === TRACE_PARENT_HEADER, + ); + return parseTraceParent(metaElement?.content || ''); } diff --git a/packages/instrumentation-console/tsconfig.json b/packages/instrumentation-console/tsconfig.json index 485bbd71..0ff2110a 100644 --- a/packages/instrumentation-console/tsconfig.json +++ b/packages/instrumentation-console/tsconfig.json @@ -1,8 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": "./src", - "skipLibCheck": false + "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] From e0c794187d0d828671b1c5f3c136180c63670d63 Mon Sep 17 00:00:00 2001 From: DrDreo Date: Tue, 6 Jan 2026 15:33:35 +0100 Subject: [PATCH 08/17] chore: update linting from main --- package-lock.json | 8 ++++++-- packages/instrumentation-console/src/index.ts | 13 +------------ .../src/instrumentation.test.ts | 13 +------------ .../instrumentation-console/src/instrumentation.ts | 13 +------------ packages/instrumentation-console/src/semconv.ts | 13 +------------ packages/instrumentation-console/src/types.ts | 13 +------------ packages/instrumentation-console/src/utils.ts | 13 +------------ 7 files changed, 12 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00a90f3c..73cc3659 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8365,8 +8365,12 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "*", - "@opentelemetry/instrumentation": "*" + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/instrumentation": "0.208.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, "packages/instrumentation-user-action": { diff --git a/packages/instrumentation-console/src/index.ts b/packages/instrumentation-console/src/index.ts index c716edf5..b0218324 100644 --- a/packages/instrumentation-console/src/index.ts +++ b/packages/instrumentation-console/src/index.ts @@ -1,17 +1,6 @@ /* * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ export { ConsoleInstrumentation } from './instrumentation.ts'; diff --git a/packages/instrumentation-console/src/instrumentation.test.ts b/packages/instrumentation-console/src/instrumentation.test.ts index e481ca15..55569733 100644 --- a/packages/instrumentation-console/src/instrumentation.test.ts +++ b/packages/instrumentation-console/src/instrumentation.test.ts @@ -1,17 +1,6 @@ /* * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ import { SeverityNumber } from '@opentelemetry/api-logs'; diff --git a/packages/instrumentation-console/src/instrumentation.ts b/packages/instrumentation-console/src/instrumentation.ts index acd6a30b..944a5432 100644 --- a/packages/instrumentation-console/src/instrumentation.ts +++ b/packages/instrumentation-console/src/instrumentation.ts @@ -1,17 +1,6 @@ /* * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ import { context, trace } from '@opentelemetry/api'; diff --git a/packages/instrumentation-console/src/semconv.ts b/packages/instrumentation-console/src/semconv.ts index 25a8371d..600ef419 100644 --- a/packages/instrumentation-console/src/semconv.ts +++ b/packages/instrumentation-console/src/semconv.ts @@ -1,17 +1,6 @@ /* * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ /* diff --git a/packages/instrumentation-console/src/types.ts b/packages/instrumentation-console/src/types.ts index bf21d1f9..0da52def 100644 --- a/packages/instrumentation-console/src/types.ts +++ b/packages/instrumentation-console/src/types.ts @@ -1,17 +1,6 @@ /* * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; diff --git a/packages/instrumentation-console/src/utils.ts b/packages/instrumentation-console/src/utils.ts index 6d280794..8a29949b 100644 --- a/packages/instrumentation-console/src/utils.ts +++ b/packages/instrumentation-console/src/utils.ts @@ -1,17 +1,6 @@ /* * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: Apache-2.0 */ import type { SpanContext } from '@opentelemetry/api'; From 3dede67b542653ea3c3b78cec5da4d92e510305d Mon Sep 17 00:00:00 2001 From: DrDreo Date: Wed, 7 Jan 2026 09:46:53 +0100 Subject: [PATCH 09/17] refactor: PR feedback --- packages/instrumentation-console/README.md | 2 ++ packages/instrumentation-console/src/instrumentation.ts | 1 + packages/instrumentation-console/src/utils.ts | 6 +++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/instrumentation-console/README.md b/packages/instrumentation-console/README.md index 3d6f66ef..3358c694 100644 --- a/packages/instrumentation-console/README.md +++ b/packages/instrumentation-console/README.md @@ -45,6 +45,8 @@ console.log('Hello, World!'); console.error('Something went wrong!'); ``` +> **Warning:** Do not use this instrumentation with the `ConsoleSpanExporter`. This creates an infinite loop where console logs from the exporter itself are captured as new logs, creating more console output. See [OpenTelemetry Browser Exporters](https://opentelemetry.io/docs/languages/js/getting-started/browser/#creating-an-exporter) for available options. + ## Configuration | Option | Type | Default | Description | diff --git a/packages/instrumentation-console/src/instrumentation.ts b/packages/instrumentation-console/src/instrumentation.ts index 944a5432..b1e181c3 100644 --- a/packages/instrumentation-console/src/instrumentation.ts +++ b/packages/instrumentation-console/src/instrumentation.ts @@ -86,6 +86,7 @@ export class ConsoleInstrumentation extends InstrumentationBase e.getAttribute('name') === TRACE_PARENT_HEADER, + const metaElement = document.querySelector( + `meta[name="${TRACE_PARENT_HEADER}"]`, ); - return parseTraceParent(metaElement?.content || ''); + return parseTraceParent(metaElement?.content ?? ''); } From 9bbc0e728d600c04cb5da2a7da3d89a0c3a2f459 Mon Sep 17 00:00:00 2001 From: DrDreo Date: Wed, 7 Jan 2026 10:51:05 +0100 Subject: [PATCH 10/17] chore: add instrumentation test app --- .../examples/index.html | 12 ++ .../examples/package.json | 17 +++ .../examples/src/console.ts | 73 +++++++++ .../examples/src/main.ts | 18 +++ .../examples/src/styles.css | 139 ++++++++++++++++++ 5 files changed, 259 insertions(+) create mode 100644 packages/instrumentation-console/examples/index.html create mode 100644 packages/instrumentation-console/examples/package.json create mode 100644 packages/instrumentation-console/examples/src/console.ts create mode 100644 packages/instrumentation-console/examples/src/main.ts create mode 100644 packages/instrumentation-console/examples/src/styles.css diff --git a/packages/instrumentation-console/examples/index.html b/packages/instrumentation-console/examples/index.html new file mode 100644 index 00000000..f14956f7 --- /dev/null +++ b/packages/instrumentation-console/examples/index.html @@ -0,0 +1,12 @@ + + + + + + OpenTelemetry Browser Console Test App + + +
+ + + diff --git a/packages/instrumentation-console/examples/package.json b/packages/instrumentation-console/examples/package.json new file mode 100644 index 00000000..7ff869b4 --- /dev/null +++ b/packages/instrumentation-console/examples/package.json @@ -0,0 +1,17 @@ +{ + "name": "ot-test-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@opentelemetry/sdk-trace-base": "2.2.0", + "@opentelemetry/sdk-trace-web": "2.2.0", + "typescript": "~5.9.3", + "vite": "^7.2.6" + } +} diff --git a/packages/instrumentation-console/examples/src/console.ts b/packages/instrumentation-console/examples/src/console.ts new file mode 100644 index 00000000..9785dae4 --- /dev/null +++ b/packages/instrumentation-console/examples/src/console.ts @@ -0,0 +1,73 @@ +import { logs } from '@opentelemetry/api-logs'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { resourceFromAttributes } from '@opentelemetry/resources'; +import { + ConsoleLogRecordExporter, + LoggerProvider, + SimpleLogRecordProcessor, +} from '@opentelemetry/sdk-logs'; +import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; +import { ConsoleInstrumentation } from '../../src/index.ts'; + +export function setupConsoleDemo(element: HTMLDivElement) { + const resourceSettings = resourceFromAttributes({ + [ATTR_SERVICE_NAME]: 'OpenTelemetry Browser Test', + }); + + const loggerProvider = new LoggerProvider({ + resource: resourceSettings, + processors: [new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())], + }); + logs.setGlobalLoggerProvider(loggerProvider); + + registerInstrumentations({ + loggerProvider, + instrumentations: [new ConsoleInstrumentation()], + }); + + element.innerHTML = ` +
+ + + + + +
+

Open DevTools Console to see the output and traces

+ `; + + const btnLog = element.querySelector('#btn-log'); + if (btnLog) { + btnLog.addEventListener('click', () => { + console.log('Test log message', { foo: 'bar' }); + }); + } + + const btnInfo = element.querySelector('#btn-info'); + if (btnInfo) { + btnInfo.addEventListener('click', () => { + console.info('Test info message'); + }); + } + + const btnWarn = element.querySelector('#btn-warn'); + if (btnWarn) { + btnWarn.addEventListener('click', () => { + console.warn('Test warning message'); + }); + } + + const btnError = element.querySelector('#btn-error'); + if (btnError) { + btnError.addEventListener('click', () => { + console.error('Test error message'); + }); + } + + const btnDebug = element.querySelector('#btn-debug'); + if (btnDebug) { + btnDebug.addEventListener('click', () => { + console.debug('Test debug message'); + }); + } +} diff --git a/packages/instrumentation-console/examples/src/main.ts b/packages/instrumentation-console/examples/src/main.ts new file mode 100644 index 00000000..49d6def7 --- /dev/null +++ b/packages/instrumentation-console/examples/src/main.ts @@ -0,0 +1,18 @@ +import './styles.css'; + +import { setupConsoleDemo } from './console.ts'; + +const appElement = document.querySelector('#app'); +if (appElement) { + appElement.innerHTML = ` +
+

Console Instrumentation Demo

+
+
+ `; + + const demosElement = document.querySelector('#console-demo'); + if (demosElement) { + setupConsoleDemo(demosElement); + } +} diff --git a/packages/instrumentation-console/examples/src/styles.css b/packages/instrumentation-console/examples/src/styles.css new file mode 100644 index 00000000..92b909bb --- /dev/null +++ b/packages/instrumentation-console/examples/src/styles.css @@ -0,0 +1,139 @@ +:root { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --border: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --radius: 0.5rem; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, sans-serif; + line-height: 1.5; + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#app { + width: 100%; + max-width: 600px; + padding: 2rem; +} + +h1 { + font-size: 1.875rem; + font-weight: 600; + letter-spacing: -0.025em; + margin-bottom: 0.5rem; +} + +.description { + color: hsl(var(--muted-foreground)); + margin-bottom: 2rem; +} + +.button-group { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +button { + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + border-radius: var(--radius); + font-size: 0.875rem; + font-weight: 500; + font-family: inherit; + height: 2.5rem; + padding: 0 1rem; + cursor: pointer; + transition: + background-color 0.15s, + border-color 0.15s, + opacity 0.15s; + border: 1px solid hsl(var(--border)); + background-color: transparent; + color: hsl(var(--foreground)); +} + +button:hover { + background-color: hsl(var(--muted)); +} + +button:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; +} + +button:active { + opacity: 0.9; +} + +#btn-log { + background-color: hsl(var(--foreground)); + color: hsl(var(--background)); + border-color: hsl(var(--foreground)); +} + +#btn-log:hover { + background-color: hsl(var(--foreground)); + opacity: 0.9; +} + +#btn-error { + background-color: hsl(0 84.2% 60.2%); + color: hsl(0 0% 98%); + border-color: hsl(0 84.2% 60.2%); +} + +#btn-error:hover { + background-color: hsl(0 84.2% 60.2%); + opacity: 0.9; +} + +#btn-warn { + background-color: hsl(47.9 95.8% 53.1%); + color: hsl(0 0% 9%); + border-color: hsl(47.9 95.8% 53.1%); +} + +#btn-warn:hover { + background-color: hsl(47.9 95.8% 53.1%); + opacity: 0.9; +} + +.hint { + margin-top: 1.5rem; + padding: 1rem; + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); + color: hsl(var(--muted-foreground)); + font-size: 0.875rem; +} + +.hint code { + background-color: hsl(var(--muted)); + padding: 0.125rem 0.375rem; + border-radius: calc(var(--radius) - 2px); + font-size: 0.8125rem; + font-family: + ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; +} From f8a6bd79ed43a06e2c7fb6a3b984af4d9b7480c2 Mon Sep 17 00:00:00 2001 From: Dr Dreo Date: Fri, 9 Jan 2026 10:26:55 +0100 Subject: [PATCH 11/17] Update packages/instrumentation-console/examples/package.json Co-authored-by: Jared Freeze --- packages/instrumentation-console/examples/package.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/instrumentation-console/examples/package.json b/packages/instrumentation-console/examples/package.json index 7ff869b4..8d165600 100644 --- a/packages/instrumentation-console/examples/package.json +++ b/packages/instrumentation-console/examples/package.json @@ -5,13 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", "preview": "vite preview" - }, - "devDependencies": { - "@opentelemetry/sdk-trace-base": "2.2.0", - "@opentelemetry/sdk-trace-web": "2.2.0", - "typescript": "~5.9.3", - "vite": "^7.2.6" } } From caf96def77885cedb811cd034c43eecc0c76238f Mon Sep 17 00:00:00 2001 From: DrDreo Date: Thu, 15 Jan 2026 13:22:43 +0100 Subject: [PATCH 12/17] refactor: log context improvements --- package-lock.json | 33 +++++++- .../examples/index.html | 16 ++-- packages/instrumentation-console/package.json | 4 +- .../src/instrumentation.ts | 27 ++++--- packages/instrumentation-console/src/utils.ts | 14 ---- packages/web-utils/src/getTraceParent.test.ts | 77 +++++++++++++++++++ packages/web-utils/src/getTraceParent.ts | 31 ++++++++ packages/web-utils/src/index.ts | 8 +- 8 files changed, 171 insertions(+), 39 deletions(-) delete mode 100644 packages/instrumentation-console/src/utils.ts create mode 100644 packages/web-utils/src/getTraceParent.test.ts create mode 100644 packages/web-utils/src/getTraceParent.ts diff --git a/package-lock.json b/package-lock.json index 5e8afb84..05e0fad7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8420,13 +8420,42 @@ "license": "Apache-2.0", "dependencies": { "@opentelemetry/api-logs": "0.208.0", - "@opentelemetry/core": "2.2.0", - "@opentelemetry/instrumentation": "0.208.0" + "@opentelemetry/instrumentation": "0.208.0", + "@opentelemetry/web-utils": "*" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, + "packages/instrumentation-console/node_modules/@opentelemetry/api-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", + "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "packages/instrumentation-console/node_modules/@opentelemetry/instrumentation": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", + "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, "packages/instrumentation-user-action": { "name": "@opentelemetry/instrumentation-user-action", "version": "0.1.0", diff --git a/packages/instrumentation-console/examples/index.html b/packages/instrumentation-console/examples/index.html index f14956f7..c814c50b 100644 --- a/packages/instrumentation-console/examples/index.html +++ b/packages/instrumentation-console/examples/index.html @@ -1,12 +1,12 @@ - - - + + + OpenTelemetry Browser Console Test App - - -
- - + + +
+ + diff --git a/packages/instrumentation-console/package.json b/packages/instrumentation-console/package.json index 80843f72..053bc9e4 100644 --- a/packages/instrumentation-console/package.json +++ b/packages/instrumentation-console/package.json @@ -38,8 +38,8 @@ }, "dependencies": { "@opentelemetry/api-logs": "0.208.0", - "@opentelemetry/core": "2.2.0", - "@opentelemetry/instrumentation": "0.208.0" + "@opentelemetry/instrumentation": "0.208.0", + "@opentelemetry/web-utils": "*" }, "publishConfig": { "access": "public" diff --git a/packages/instrumentation-console/src/instrumentation.ts b/packages/instrumentation-console/src/instrumentation.ts index b1e181c3..646e61ab 100644 --- a/packages/instrumentation-console/src/instrumentation.ts +++ b/packages/instrumentation-console/src/instrumentation.ts @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { context, trace } from '@opentelemetry/api'; +import { context, propagation, ROOT_CONTEXT, trace } from '@opentelemetry/api'; import { SeverityNumber } from '@opentelemetry/api-logs'; import { InstrumentationBase } from '@opentelemetry/instrumentation'; +import { getTraceParentString } from '@opentelemetry/web-utils'; import { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts'; import type { ConsoleInstrumentationConfig, ConsoleMethod } from './types.ts'; -import { getTraceParent } from './utils.ts'; const DEFAULT_LOG_METHODS: ConsoleMethod[] = [ 'log', @@ -70,15 +70,22 @@ export class ConsoleInstrumentation extends InstrumentationBase Console[ConsoleMethod] { const instrumentation = this; - // Get the page-level traceparent for fallback - const pageTraceParent = getTraceParent(); const serializer = instrumentation._getMessageSerializer(); return function patchConsoleMethod(original: Console[ConsoleMethod]) { return function (this: Console, ...args: unknown[]) { - // Get the active span context at call time, fallback to page traceparent - const activeSpan = trace.getSpan(context.active()); - const spanContext = activeSpan?.spanContext() ?? pageTraceParent; + // Get the active context, or extract from meta tag traceparent if no active span + let logContext = context.active(); + const activeSpan = trace.getSpan(logContext); + + if (!activeSpan) { + // No active span, try to extract context from meta tag traceparent + const traceparent = getTraceParentString(); + if (traceparent) { + logContext = propagation.extract(ROOT_CONTEXT, { traceparent }); + } + } + const body = serializer(args); instrumentation.logger.emit({ @@ -86,13 +93,9 @@ export class ConsoleInstrumentation extends InstrumentationBase( - `meta[name="${TRACE_PARENT_HEADER}"]`, - ); - return parseTraceParent(metaElement?.content ?? ''); -} diff --git a/packages/web-utils/src/getTraceParent.test.ts b/packages/web-utils/src/getTraceParent.test.ts new file mode 100644 index 00000000..9cab7bd3 --- /dev/null +++ b/packages/web-utils/src/getTraceParent.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { afterEach, describe, expect, it } from 'vitest'; +import { getTraceParent, getTraceParentString } from './getTraceParent.ts'; + +describe('getTraceParent utilities', () => { + let metaElement: HTMLMetaElement | null = null; + + afterEach(() => { + if (metaElement) { + metaElement.remove(); + metaElement = null; + } + }); + + function addMetaTag(content: string): void { + metaElement = document.createElement('meta'); + metaElement.setAttribute('name', 'traceparent'); + metaElement.content = content; + document.head.appendChild(metaElement); + } + + describe('getTraceParentString', () => { + it('should return empty string when no traceparent meta tag exists', () => { + const result = getTraceParentString(); + expect(result).toBe(''); + }); + + it('should return the traceparent content when meta tag exists', () => { + const traceparent = + '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01'; + addMetaTag(traceparent); + + const result = getTraceParentString(); + expect(result).toBe(traceparent); + }); + }); + + describe('getTraceParent', () => { + it('should return null when no traceparent meta tag exists', () => { + const result = getTraceParent(); + expect(result).toBeNull(); + }); + + it('should return null for invalid traceparent', () => { + addMetaTag('invalid-traceparent'); + + const result = getTraceParent(); + expect(result).toBeNull(); + }); + + it('should return parsed SpanContext for valid traceparent', () => { + const traceId = '0af7651916cd43dd8448eb211c80319c'; + const spanId = 'b7ad6b7169203331'; + addMetaTag(`00-${traceId}-${spanId}-01`); + + const result = getTraceParent(); + expect(result).not.toBeNull(); + expect(result?.traceId).toBe(traceId); + expect(result?.spanId).toBe(spanId); + expect(result?.traceFlags).toBe(1); + }); + + it('should handle traceparent with traceFlags 00', () => { + const traceId = '0af7651916cd43dd8448eb211c80319c'; + const spanId = 'b7ad6b7169203331'; + addMetaTag(`00-${traceId}-${spanId}-00`); + + const result = getTraceParent(); + expect(result).not.toBeNull(); + expect(result?.traceFlags).toBe(0); + }); + }); +}); diff --git a/packages/web-utils/src/getTraceParent.ts b/packages/web-utils/src/getTraceParent.ts new file mode 100644 index 00000000..0bf200ce --- /dev/null +++ b/packages/web-utils/src/getTraceParent.ts @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { SpanContext } from '@opentelemetry/api'; +import { parseTraceParent, TRACE_PARENT_HEADER } from '@opentelemetry/core'; + +/** + * Gets the raw traceparent string from the meta tag in the document. + * This is useful for use with propagation.extract() to create a proper context. + * + * @returns The raw traceparent string if a meta tag is found, empty string otherwise + */ +export function getTraceParentString(): string { + const metaElement = document.querySelector( + `meta[name="${TRACE_PARENT_HEADER}"]`, + ); + return metaElement?.content ?? ''; +} + +/** + * Extracts the traceparent from a meta tag in the document and parses it. + * This is useful for correlating browser logs/spans with server-side traces + * when the server injects the traceparent into the HTML. + * + * @returns The parsed SpanContext if a valid traceparent meta tag is found, null otherwise + */ +export function getTraceParent(): SpanContext | null { + return parseTraceParent(getTraceParentString()); +} diff --git a/packages/web-utils/src/index.ts b/packages/web-utils/src/index.ts index c3922478..60d3cb46 100644 --- a/packages/web-utils/src/index.ts +++ b/packages/web-utils/src/index.ts @@ -5,5 +5,11 @@ import { getElementCSSSelector } from './getElementCSSSelector.ts'; import { getElementXPath } from './getElementXPath.ts'; +import { getTraceParent, getTraceParentString } from './getTraceParent.ts'; -export { getElementCSSSelector, getElementXPath }; +export { + getElementCSSSelector, + getElementXPath, + getTraceParent, + getTraceParentString, +}; From 0c7b2e50da8a4296d2efe351ac561d765f65256a Mon Sep 17 00:00:00 2001 From: DrDreo Date: Thu, 15 Jan 2026 13:53:18 +0100 Subject: [PATCH 13/17] test: remove log spam in tests --- .../src/instrumentation.test.ts | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/instrumentation-console/src/instrumentation.test.ts b/packages/instrumentation-console/src/instrumentation.test.ts index 55569733..f1cf5644 100644 --- a/packages/instrumentation-console/src/instrumentation.test.ts +++ b/packages/instrumentation-console/src/instrumentation.test.ts @@ -6,18 +6,40 @@ import { SeverityNumber } from '@opentelemetry/api-logs'; import type { InMemoryLogRecordExporter } from '@opentelemetry/sdk-logs'; import { setupTestLogExporter } from '@opentelemetry/test-utils'; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from 'vitest'; import { ConsoleInstrumentation } from './instrumentation.ts'; import { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts'; describe('ConsoleInstrumentation', () => { let inMemoryExporter: InMemoryLogRecordExporter; let instrumentation: ConsoleInstrumentation; + let originalConsole: Console; beforeAll(() => { + originalConsole = globalThis.console; + globalThis.console = { + error: () => {}, + log: () => {}, + info: () => {}, + warn: () => {}, + trace: () => {}, + debug: () => {}, + } as unknown as Console; inMemoryExporter = setupTestLogExporter(); }); + afterAll(() => { + globalThis.console = originalConsole; + }); + beforeEach(() => { inMemoryExporter.reset(); instrumentation = new ConsoleInstrumentation(); @@ -100,10 +122,9 @@ describe('ConsoleInstrumentation', () => { instrumentation.disable(); inMemoryExporter.reset(); instrumentation = new ConsoleInstrumentation({ - enabled: false, + enabled: true, logMethods: ['error', 'warn'], }); - instrumentation.enable(); console.log('log message'); console.info('info message'); @@ -122,10 +143,9 @@ describe('ConsoleInstrumentation', () => { instrumentation.disable(); inMemoryExporter.reset(); instrumentation = new ConsoleInstrumentation({ - enabled: false, + enabled: true, logMethods: [], }); - instrumentation.enable(); console.log('log message'); console.warn('warn message'); @@ -174,11 +194,10 @@ describe('ConsoleInstrumentation', () => { it('should use custom serializer when provided', () => { instrumentation.disable(); instrumentation = new ConsoleInstrumentation({ - enabled: false, + enabled: true, messageSerializer: (args) => args.map((arg) => `[${typeof arg}]`).join('-'), }); - instrumentation.enable(); console.log('hello', 123, { test: true }); @@ -232,6 +251,9 @@ describe('ConsoleInstrumentation', () => { const logs = inMemoryExporter.getFinishedLogRecords(); expect(logs.length).toBe(0); + + // enable instrumentation again + instrumentation.enable(); }); it('should emit logs when re-enabled', () => { @@ -253,6 +275,9 @@ describe('ConsoleInstrumentation', () => { // After disable, console.log should be different (unwrapped) expect(console.log).not.toBe(wrappedLog); + + // enable instrumentation again + instrumentation.enable(); }); }); }); From d455a1d71bd35ab8504808d52fd3987ed5ce9d8b Mon Sep 17 00:00:00 2001 From: DrDreo Date: Thu, 22 Jan 2026 11:16:45 +0100 Subject: [PATCH 14/17] refactor: remove trace parent context --- .../src/instrumentation.ts | 16 +--- packages/web-utils/src/getTraceParent.test.ts | 77 ------------------- packages/web-utils/src/getTraceParent.ts | 31 -------- packages/web-utils/src/index.ts | 8 +- 4 files changed, 3 insertions(+), 129 deletions(-) delete mode 100644 packages/web-utils/src/getTraceParent.test.ts delete mode 100644 packages/web-utils/src/getTraceParent.ts diff --git a/packages/instrumentation-console/src/instrumentation.ts b/packages/instrumentation-console/src/instrumentation.ts index 646e61ab..5668a63c 100644 --- a/packages/instrumentation-console/src/instrumentation.ts +++ b/packages/instrumentation-console/src/instrumentation.ts @@ -3,10 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { context, propagation, ROOT_CONTEXT, trace } from '@opentelemetry/api'; +import { context } from '@opentelemetry/api'; import { SeverityNumber } from '@opentelemetry/api-logs'; import { InstrumentationBase } from '@opentelemetry/instrumentation'; -import { getTraceParentString } from '@opentelemetry/web-utils'; import { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts'; import type { ConsoleInstrumentationConfig, ConsoleMethod } from './types.ts'; @@ -74,18 +73,7 @@ export class ConsoleInstrumentation extends InstrumentationBase { - let metaElement: HTMLMetaElement | null = null; - - afterEach(() => { - if (metaElement) { - metaElement.remove(); - metaElement = null; - } - }); - - function addMetaTag(content: string): void { - metaElement = document.createElement('meta'); - metaElement.setAttribute('name', 'traceparent'); - metaElement.content = content; - document.head.appendChild(metaElement); - } - - describe('getTraceParentString', () => { - it('should return empty string when no traceparent meta tag exists', () => { - const result = getTraceParentString(); - expect(result).toBe(''); - }); - - it('should return the traceparent content when meta tag exists', () => { - const traceparent = - '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01'; - addMetaTag(traceparent); - - const result = getTraceParentString(); - expect(result).toBe(traceparent); - }); - }); - - describe('getTraceParent', () => { - it('should return null when no traceparent meta tag exists', () => { - const result = getTraceParent(); - expect(result).toBeNull(); - }); - - it('should return null for invalid traceparent', () => { - addMetaTag('invalid-traceparent'); - - const result = getTraceParent(); - expect(result).toBeNull(); - }); - - it('should return parsed SpanContext for valid traceparent', () => { - const traceId = '0af7651916cd43dd8448eb211c80319c'; - const spanId = 'b7ad6b7169203331'; - addMetaTag(`00-${traceId}-${spanId}-01`); - - const result = getTraceParent(); - expect(result).not.toBeNull(); - expect(result?.traceId).toBe(traceId); - expect(result?.spanId).toBe(spanId); - expect(result?.traceFlags).toBe(1); - }); - - it('should handle traceparent with traceFlags 00', () => { - const traceId = '0af7651916cd43dd8448eb211c80319c'; - const spanId = 'b7ad6b7169203331'; - addMetaTag(`00-${traceId}-${spanId}-00`); - - const result = getTraceParent(); - expect(result).not.toBeNull(); - expect(result?.traceFlags).toBe(0); - }); - }); -}); diff --git a/packages/web-utils/src/getTraceParent.ts b/packages/web-utils/src/getTraceParent.ts deleted file mode 100644 index 0bf200ce..00000000 --- a/packages/web-utils/src/getTraceParent.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import type { SpanContext } from '@opentelemetry/api'; -import { parseTraceParent, TRACE_PARENT_HEADER } from '@opentelemetry/core'; - -/** - * Gets the raw traceparent string from the meta tag in the document. - * This is useful for use with propagation.extract() to create a proper context. - * - * @returns The raw traceparent string if a meta tag is found, empty string otherwise - */ -export function getTraceParentString(): string { - const metaElement = document.querySelector( - `meta[name="${TRACE_PARENT_HEADER}"]`, - ); - return metaElement?.content ?? ''; -} - -/** - * Extracts the traceparent from a meta tag in the document and parses it. - * This is useful for correlating browser logs/spans with server-side traces - * when the server injects the traceparent into the HTML. - * - * @returns The parsed SpanContext if a valid traceparent meta tag is found, null otherwise - */ -export function getTraceParent(): SpanContext | null { - return parseTraceParent(getTraceParentString()); -} diff --git a/packages/web-utils/src/index.ts b/packages/web-utils/src/index.ts index 60d3cb46..c3922478 100644 --- a/packages/web-utils/src/index.ts +++ b/packages/web-utils/src/index.ts @@ -5,11 +5,5 @@ import { getElementCSSSelector } from './getElementCSSSelector.ts'; import { getElementXPath } from './getElementXPath.ts'; -import { getTraceParent, getTraceParentString } from './getTraceParent.ts'; -export { - getElementCSSSelector, - getElementXPath, - getTraceParent, - getTraceParentString, -}; +export { getElementCSSSelector, getElementXPath }; From 5e9cb98803320e8762c9d6187328f6c064000805 Mon Sep 17 00:00:00 2001 From: DrDreo Date: Sun, 25 Jan 2026 16:46:02 +0100 Subject: [PATCH 15/17] chore: update readme --- packages/instrumentation-console/README.md | 13 ++++++------- packages/instrumentation-console/package.json | 3 +-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/instrumentation-console/README.md b/packages/instrumentation-console/README.md index 3358c694..8c3bba2d 100644 --- a/packages/instrumentation-console/README.md +++ b/packages/instrumentation-console/README.md @@ -19,17 +19,16 @@ npm install @opentelemetry/instrumentation-console ```typescript import { logs } from '@opentelemetry/api-logs'; -import { - ConsoleLogRecordExporter, - LoggerProvider, - SimpleLogRecordProcessor, -} from '@opentelemetry/sdk-logs'; +import { LoggerProvider, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; import { ConsoleInstrumentation } from '@opentelemetry/instrumentation-console'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; const logProvider = new LoggerProvider({ processors: [ - new SimpleLogRecordProcessor(new ConsoleLogRecordExporter()), + new SimpleLogRecordProcessor(new OTLPLogExporter({ + url: 'http://localhost:8000/v1/logs', + })), ], }); logs.setGlobalLoggerProvider(logProvider); @@ -45,7 +44,7 @@ console.log('Hello, World!'); console.error('Something went wrong!'); ``` -> **Warning:** Do not use this instrumentation with the `ConsoleSpanExporter`. This creates an infinite loop where console logs from the exporter itself are captured as new logs, creating more console output. See [OpenTelemetry Browser Exporters](https://opentelemetry.io/docs/languages/js/getting-started/browser/#creating-an-exporter) for available options. +> **Warning:** Do not use this instrumentation with the `ConsoleSpanExporter` or `ConsoleLogRecordExporter`. This creates an infinite loop where console logs from the exporter itself are captured as new logs, creating more console output. Use exporters that send data over the network, such as `OTLPLogExporter`, `OTLPTraceExporter`, or other non-console exporters. See [OpenTelemetry Browser Exporters](https://opentelemetry.io/docs/languages/js/getting-started/browser/#creating-an-exporter) for available options. ## Configuration diff --git a/packages/instrumentation-console/package.json b/packages/instrumentation-console/package.json index 053bc9e4..419395b6 100644 --- a/packages/instrumentation-console/package.json +++ b/packages/instrumentation-console/package.json @@ -38,8 +38,7 @@ }, "dependencies": { "@opentelemetry/api-logs": "0.208.0", - "@opentelemetry/instrumentation": "0.208.0", - "@opentelemetry/web-utils": "*" + "@opentelemetry/instrumentation": "0.208.0" }, "publishConfig": { "access": "public" From 331e74b907629b208a7ec92fbe5ae6a1963e3334 Mon Sep 17 00:00:00 2001 From: DrDreo Date: Mon, 13 Apr 2026 11:29:43 +0200 Subject: [PATCH 16/17] chore: move console instrumentation to new folder structure --- package-lock.json | 30 ---- packages/instrumentation-console/README.md | 114 -------------- .../examples/index.html | 12 -- .../examples/package.json | 11 -- .../examples/src/console.ts | 73 --------- .../examples/src/main.ts | 18 --- .../examples/src/styles.css | 139 ------------------ packages/instrumentation-console/package.json | 46 ------ .../instrumentation-console/tsconfig.json | 8 - .../instrumentation-console/tsdown.config.ts | 6 - packages/instrumentation/package.json | 2 + .../src/console}/index.ts | 0 .../src/console}/instrumentation.test.ts | 23 ++- .../src/console}/instrumentation.ts | 43 +++--- .../src/console}/semconv.ts | 0 .../src/console}/types.ts | 0 16 files changed, 46 insertions(+), 479 deletions(-) delete mode 100644 packages/instrumentation-console/README.md delete mode 100644 packages/instrumentation-console/examples/index.html delete mode 100644 packages/instrumentation-console/examples/package.json delete mode 100644 packages/instrumentation-console/examples/src/console.ts delete mode 100644 packages/instrumentation-console/examples/src/main.ts delete mode 100644 packages/instrumentation-console/examples/src/styles.css delete mode 100644 packages/instrumentation-console/package.json delete mode 100644 packages/instrumentation-console/tsconfig.json delete mode 100644 packages/instrumentation-console/tsdown.config.ts rename packages/{instrumentation-console/src => instrumentation/src/console}/index.ts (100%) rename packages/{instrumentation-console/src => instrumentation/src/console}/instrumentation.test.ts (91%) rename packages/{instrumentation-console/src => instrumentation/src/console}/instrumentation.ts (74%) rename packages/{instrumentation-console/src => instrumentation/src/console}/semconv.ts (100%) rename packages/{instrumentation-console/src => instrumentation/src/console}/types.ts (100%) diff --git a/package-lock.json b/package-lock.json index cf17598c..0484b549 100644 --- a/package-lock.json +++ b/package-lock.json @@ -345,9 +345,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -365,9 +362,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -385,9 +379,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -405,9 +396,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -6431,9 +6419,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6451,9 +6436,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6471,9 +6453,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6491,9 +6470,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6511,9 +6487,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6531,9 +6504,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/packages/instrumentation-console/README.md b/packages/instrumentation-console/README.md deleted file mode 100644 index 8c3bba2d..00000000 --- a/packages/instrumentation-console/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# OpenTelemetry Console Instrumentation for Web - -[![NPM Published Version][npm-img]][npm-url] -[![Apache License][license-image]][license-image] - -This module provides automatic instrumentation for *console* methods (log, warn, error, info, debug) for Web applications, emitting them as OpenTelemetry logs which may be collected using the [`@opentelemetry/sdk-logs`](https://www.npmjs.com/package/@opentelemetry/sdk-logs) package. - -Compatible with OpenTelemetry JS API and SDK `1.0+`. - -## Installation - -```bash -npm install @opentelemetry/instrumentation-console -``` - -## Usage - -### Initialize - -```typescript -import { logs } from '@opentelemetry/api-logs'; -import { LoggerProvider, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs'; -import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; -import { ConsoleInstrumentation } from '@opentelemetry/instrumentation-console'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; - -const logProvider = new LoggerProvider({ - processors: [ - new SimpleLogRecordProcessor(new OTLPLogExporter({ - url: 'http://localhost:8000/v1/logs', - })), - ], -}); -logs.setGlobalLoggerProvider(logProvider); - -registerInstrumentations({ - instrumentations: [ - new ConsoleInstrumentation(), - ], -}); - -// Now all console calls will be captured as OpenTelemetry logs -console.log('Hello, World!'); -console.error('Something went wrong!'); -``` - -> **Warning:** Do not use this instrumentation with the `ConsoleSpanExporter` or `ConsoleLogRecordExporter`. This creates an infinite loop where console logs from the exporter itself are captured as new logs, creating more console output. Use exporters that send data over the network, such as `OTLPLogExporter`, `OTLPTraceExporter`, or other non-console exporters. See [OpenTelemetry Browser Exporters](https://opentelemetry.io/docs/languages/js/getting-started/browser/#creating-an-exporter) for available options. - -## Configuration - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `logMethods` | `ConsoleMethod[]` | `['log', 'warn', 'error', 'info', 'debug']` | Console methods to instrument | -| `messageSerializer` | `(args: unknown[]) => string` | See below | Custom serializer for console arguments | - -### logMethods - -Configure which console methods to instrument: - -```typescript -new ConsoleInstrumentation({ - logMethods: ['error', 'warn'], // Only capture errors and warnings -}); -``` - -### messageSerializer - -Provide a custom serializer for console arguments: - -```typescript -new ConsoleInstrumentation({ - messageSerializer: (args) => args.map(arg => String(arg)).join(' | '), -}); -``` - -The default serializer joins arguments as strings, with objects serialized via `JSON.stringify`. Circular references are handled gracefully by falling back to `String(arg)`. - -## Severity Mapping - -Console methods are mapped to OpenTelemetry severity levels: - -| Console Method | SeverityNumber | SeverityText | -|----------------|----------------|--------------| -| `debug` | DEBUG (5) | 'debug' | -| `log` | INFO (9) | 'log' | -| `info` | INFO (9) | 'info' | -| `warn` | WARN (13) | 'warn' | -| `error` | ERROR (17) | 'error' | - -## Semantic Conventions - -This package emits logs with the following attributes: - -| Attribute | Description | -|-----------|-------------| -| `browser.console.method` | The console method that was called (e.g., 'log', 'error') | - -Event name: `browser.console` - -## Useful links - -- For more information on OpenTelemetry, visit: -- For more about OpenTelemetry Browser: -- For help or feedback on this project, join us in [GitHub Discussions][discussions-url] - -## License - -Apache 2.0 - See [LICENSE][license-url] for more information. - -[discussions-url]: https://github.com/open-telemetry/opentelemetry-browser/discussions/landing -[license-url]: https://github.com/open-telemetry/opentelemetry-browser/blob/main/LICENSE -[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat -[npm-url]: https://www.npmjs.com/package/@opentelemetry/instrumentation-console -[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Finstrumentation-console.svg diff --git a/packages/instrumentation-console/examples/index.html b/packages/instrumentation-console/examples/index.html deleted file mode 100644 index c814c50b..00000000 --- a/packages/instrumentation-console/examples/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - OpenTelemetry Browser Console Test App - - -
- - - diff --git a/packages/instrumentation-console/examples/package.json b/packages/instrumentation-console/examples/package.json deleted file mode 100644 index 8d165600..00000000 --- a/packages/instrumentation-console/examples/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "ot-test-app", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - } -} diff --git a/packages/instrumentation-console/examples/src/console.ts b/packages/instrumentation-console/examples/src/console.ts deleted file mode 100644 index 9785dae4..00000000 --- a/packages/instrumentation-console/examples/src/console.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { logs } from '@opentelemetry/api-logs'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { resourceFromAttributes } from '@opentelemetry/resources'; -import { - ConsoleLogRecordExporter, - LoggerProvider, - SimpleLogRecordProcessor, -} from '@opentelemetry/sdk-logs'; -import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; -import { ConsoleInstrumentation } from '../../src/index.ts'; - -export function setupConsoleDemo(element: HTMLDivElement) { - const resourceSettings = resourceFromAttributes({ - [ATTR_SERVICE_NAME]: 'OpenTelemetry Browser Test', - }); - - const loggerProvider = new LoggerProvider({ - resource: resourceSettings, - processors: [new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())], - }); - logs.setGlobalLoggerProvider(loggerProvider); - - registerInstrumentations({ - loggerProvider, - instrumentations: [new ConsoleInstrumentation()], - }); - - element.innerHTML = ` -
- - - - - -
-

Open DevTools Console to see the output and traces

- `; - - const btnLog = element.querySelector('#btn-log'); - if (btnLog) { - btnLog.addEventListener('click', () => { - console.log('Test log message', { foo: 'bar' }); - }); - } - - const btnInfo = element.querySelector('#btn-info'); - if (btnInfo) { - btnInfo.addEventListener('click', () => { - console.info('Test info message'); - }); - } - - const btnWarn = element.querySelector('#btn-warn'); - if (btnWarn) { - btnWarn.addEventListener('click', () => { - console.warn('Test warning message'); - }); - } - - const btnError = element.querySelector('#btn-error'); - if (btnError) { - btnError.addEventListener('click', () => { - console.error('Test error message'); - }); - } - - const btnDebug = element.querySelector('#btn-debug'); - if (btnDebug) { - btnDebug.addEventListener('click', () => { - console.debug('Test debug message'); - }); - } -} diff --git a/packages/instrumentation-console/examples/src/main.ts b/packages/instrumentation-console/examples/src/main.ts deleted file mode 100644 index 49d6def7..00000000 --- a/packages/instrumentation-console/examples/src/main.ts +++ /dev/null @@ -1,18 +0,0 @@ -import './styles.css'; - -import { setupConsoleDemo } from './console.ts'; - -const appElement = document.querySelector('#app'); -if (appElement) { - appElement.innerHTML = ` -
-

Console Instrumentation Demo

-
-
- `; - - const demosElement = document.querySelector('#console-demo'); - if (demosElement) { - setupConsoleDemo(demosElement); - } -} diff --git a/packages/instrumentation-console/examples/src/styles.css b/packages/instrumentation-console/examples/src/styles.css deleted file mode 100644 index 92b909bb..00000000 --- a/packages/instrumentation-console/examples/src/styles.css +++ /dev/null @@ -1,139 +0,0 @@ -:root { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - --border: 0 0% 14.9%; - --ring: 0 0% 83.1%; - --radius: 0.5rem; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", - Arial, sans-serif; - line-height: 1.5; - background-color: hsl(var(--background)); - color: hsl(var(--foreground)); - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -#app { - width: 100%; - max-width: 600px; - padding: 2rem; -} - -h1 { - font-size: 1.875rem; - font-weight: 600; - letter-spacing: -0.025em; - margin-bottom: 0.5rem; -} - -.description { - color: hsl(var(--muted-foreground)); - margin-bottom: 2rem; -} - -.button-group { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; -} - -button { - display: inline-flex; - align-items: center; - justify-content: center; - white-space: nowrap; - border-radius: var(--radius); - font-size: 0.875rem; - font-weight: 500; - font-family: inherit; - height: 2.5rem; - padding: 0 1rem; - cursor: pointer; - transition: - background-color 0.15s, - border-color 0.15s, - opacity 0.15s; - border: 1px solid hsl(var(--border)); - background-color: transparent; - color: hsl(var(--foreground)); -} - -button:hover { - background-color: hsl(var(--muted)); -} - -button:focus-visible { - outline: 2px solid hsl(var(--ring)); - outline-offset: 2px; -} - -button:active { - opacity: 0.9; -} - -#btn-log { - background-color: hsl(var(--foreground)); - color: hsl(var(--background)); - border-color: hsl(var(--foreground)); -} - -#btn-log:hover { - background-color: hsl(var(--foreground)); - opacity: 0.9; -} - -#btn-error { - background-color: hsl(0 84.2% 60.2%); - color: hsl(0 0% 98%); - border-color: hsl(0 84.2% 60.2%); -} - -#btn-error:hover { - background-color: hsl(0 84.2% 60.2%); - opacity: 0.9; -} - -#btn-warn { - background-color: hsl(47.9 95.8% 53.1%); - color: hsl(0 0% 9%); - border-color: hsl(47.9 95.8% 53.1%); -} - -#btn-warn:hover { - background-color: hsl(47.9 95.8% 53.1%); - opacity: 0.9; -} - -.hint { - margin-top: 1.5rem; - padding: 1rem; - border: 1px solid hsl(var(--border)); - border-radius: var(--radius); - color: hsl(var(--muted-foreground)); - font-size: 0.875rem; -} - -.hint code { - background-color: hsl(var(--muted)); - padding: 0.125rem 0.375rem; - border-radius: calc(var(--radius) - 2px); - font-size: 0.8125rem; - font-family: - ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; -} diff --git a/packages/instrumentation-console/package.json b/packages/instrumentation-console/package.json deleted file mode 100644 index 419395b6..00000000 --- a/packages/instrumentation-console/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@opentelemetry/instrumentation-console", - "version": "0.1.0", - "description": "OpenTelemetry instrumentation that captures console log/warn/error/info/debug calls and emits them as OpenTelemetry logs.", - "keywords": [ - "opentelemetry", - "browser", - "web", - "instrumentation", - "console" - ], - "homepage": "https://github.com/open-telemetry/opentelemetry-browser", - "bugs": "https://github.com/open-telemetry/opentelemetry-browser/issues", - "license": "Apache-2.0", - "author": "OpenTelemetry Authors", - "repository": { - "type": "git", - "url": "git+https://github.com/open-telemetry/opentelemetry-browser.git", - "directory": "packages/instrumentation-console" - }, - "type": "module", - "types": "./dist/index.d.ts", - "exports": { - ".": "./dist/index.js" - }, - "files": [ - "dist" - ], - "scripts": { - "build": "tsdown", - "check-types": "tsc", - "test": "vitest run", - "test:watch": "vitest", - "test:coverage": "vitest --coverage" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - }, - "dependencies": { - "@opentelemetry/api-logs": "0.208.0", - "@opentelemetry/instrumentation": "0.208.0" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/instrumentation-console/tsconfig.json b/packages/instrumentation-console/tsconfig.json deleted file mode 100644 index 0ff2110a..00000000 --- a/packages/instrumentation-console/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/instrumentation-console/tsdown.config.ts b/packages/instrumentation-console/tsdown.config.ts deleted file mode 100644 index b769b79e..00000000 --- a/packages/instrumentation-console/tsdown.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from 'tsdown'; -import baseConfig from '../../tsdown.config.ts'; - -export default defineConfig({ - ...baseConfig, -}); diff --git a/packages/instrumentation/package.json b/packages/instrumentation/package.json index 49721a6c..66430a5c 100644 --- a/packages/instrumentation/package.json +++ b/packages/instrumentation/package.json @@ -7,6 +7,7 @@ "browser", "web", "instrumentation", + "console", "navigation-timing", "user-action", "web-vitals", @@ -27,6 +28,7 @@ "#instrumentation-test-utils": "./src/test-utils/index.ts" }, "exports": { + "./experimental/console": "./dist/console/index.js", "./experimental/navigation-timing": "./dist/navigation-timing/index.js", "./experimental/user-action": "./dist/user-action/index.js", "./experimental/web-vitals": "./dist/web-vitals/index.js", diff --git a/packages/instrumentation-console/src/index.ts b/packages/instrumentation/src/console/index.ts similarity index 100% rename from packages/instrumentation-console/src/index.ts rename to packages/instrumentation/src/console/index.ts diff --git a/packages/instrumentation-console/src/instrumentation.test.ts b/packages/instrumentation/src/console/instrumentation.test.ts similarity index 91% rename from packages/instrumentation-console/src/instrumentation.test.ts rename to packages/instrumentation/src/console/instrumentation.test.ts index f1cf5644..bd3bd06d 100644 --- a/packages/instrumentation-console/src/instrumentation.test.ts +++ b/packages/instrumentation/src/console/instrumentation.test.ts @@ -5,7 +5,6 @@ import { SeverityNumber } from '@opentelemetry/api-logs'; import type { InMemoryLogRecordExporter } from '@opentelemetry/sdk-logs'; -import { setupTestLogExporter } from '@opentelemetry/test-utils'; import { afterAll, afterEach, @@ -15,6 +14,7 @@ import { expect, it, } from 'vitest'; +import { setupTestLogExporter } from '#instrumentation-test-utils'; import { ConsoleInstrumentation } from './instrumentation.ts'; import { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts'; @@ -268,16 +268,31 @@ describe('ConsoleInstrumentation', () => { expect(logs[0]?.body).toBe('should be captured'); }); - it('should restore original console method behavior after disable', () => { + it('should keep console methods patched after disable (patch-once pattern)', () => { const wrappedLog = console.log; instrumentation.disable(); - // After disable, console.log should be different (unwrapped) - expect(console.log).not.toBe(wrappedLog); + // After disable, console.log remains wrapped but is a no-op for emitting logs. + // This avoids unpatch-order issues when multiple instrumentations wrap the same API. + expect(console.log).toBe(wrappedLog); // enable instrumentation again instrumentation.enable(); }); + + it('should not wrap console methods multiple times when enable() is called repeatedly', () => { + inMemoryExporter.reset(); + + instrumentation.enable(); + instrumentation.enable(); + instrumentation.enable(); + + console.log('single log message'); + + const logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + expect(logs[0]?.body).toBe('single log message'); + }); }); }); diff --git a/packages/instrumentation-console/src/instrumentation.ts b/packages/instrumentation/src/console/instrumentation.ts similarity index 74% rename from packages/instrumentation-console/src/instrumentation.ts rename to packages/instrumentation/src/console/instrumentation.ts index 5668a63c..951ad807 100644 --- a/packages/instrumentation-console/src/instrumentation.ts +++ b/packages/instrumentation/src/console/instrumentation.ts @@ -6,6 +6,7 @@ import { context } from '@opentelemetry/api'; import { SeverityNumber } from '@opentelemetry/api-logs'; import { InstrumentationBase } from '@opentelemetry/instrumentation'; +import { version } from '../../package.json' with { type: 'json' }; import { ATTR_CONSOLE_METHOD, CONSOLE_LOG_EVENT_NAME } from './semconv.ts'; import type { ConsoleInstrumentationConfig, ConsoleMethod } from './types.ts'; @@ -49,8 +50,11 @@ function defaultMessageSerializer(args: unknown[]): string { * OpenTelemetry instrumentation that captures console calls and emits them as OpenTelemetry logs. */ export class ConsoleInstrumentation extends InstrumentationBase { + private declare _isPatched: boolean; + private declare _active: boolean; + constructor(config: ConsoleInstrumentationConfig = {}) { - super('@opentelemetry/instrumentation-console', '0.1.0', config); + super('@opentelemetry/browser-instrumentation/console', version, config); } protected override init() { @@ -69,23 +73,24 @@ export class ConsoleInstrumentation extends InstrumentationBase Console[ConsoleMethod] { const instrumentation = this; - const serializer = instrumentation._getMessageSerializer(); return function patchConsoleMethod(original: Console[ConsoleMethod]) { return function (this: Console, ...args: unknown[]) { - const logContext = context.active(); - const body = serializer(args); + if (instrumentation._active) { + const logContext = context.active(); + const body = instrumentation._getMessageSerializer()(args); - instrumentation.logger.emit({ - body, - eventName: CONSOLE_LOG_EVENT_NAME, - severityNumber: SEVERITY_MAP[method], - severityText: method, - context: logContext, - attributes: { - [ATTR_CONSOLE_METHOD]: method, - }, - }); + instrumentation.logger.emit({ + body, + eventName: CONSOLE_LOG_EVENT_NAME, + severityNumber: SEVERITY_MAP[method], + severityText: method, + context: logContext, + attributes: { + [ATTR_CONSOLE_METHOD]: method, + }, + }); + } return original.apply(this, args); } as Console[ConsoleMethod]; @@ -93,6 +98,11 @@ export class ConsoleInstrumentation extends InstrumentationBase Date: Sat, 25 Apr 2026 14:38:03 +0200 Subject: [PATCH 17/17] feat: make log methods dynamically configurable --- .../src/console/instrumentation.test.ts | 24 +++++++++++++++++++ .../src/console/instrumentation.ts | 8 ++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/instrumentation/src/console/instrumentation.test.ts b/packages/instrumentation/src/console/instrumentation.test.ts index bd3bd06d..8df63c12 100644 --- a/packages/instrumentation/src/console/instrumentation.test.ts +++ b/packages/instrumentation/src/console/instrumentation.test.ts @@ -154,6 +154,30 @@ describe('ConsoleInstrumentation', () => { const logs = inMemoryExporter.getFinishedLogRecords(); expect(logs.length).toBe(0); }); + + it('should respect logMethods updates via setConfig at runtime', () => { + instrumentation.setConfig({ logMethods: ['error'] }); + + console.log('log message'); + console.warn('warn message'); + console.error('error message'); + + let logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(1); + expect(logs[0]?.severityText).toBe('error'); + + inMemoryExporter.reset(); + instrumentation.setConfig({ logMethods: ['log', 'warn'] }); + + console.log('log message'); + console.warn('warn message'); + console.error('error message'); + + logs = inMemoryExporter.getFinishedLogRecords(); + expect(logs.length).toBe(2); + expect(logs[0]?.severityText).toBe('log'); + expect(logs[1]?.severityText).toBe('warn'); + }); }); describe('default serialization', () => { diff --git a/packages/instrumentation/src/console/instrumentation.ts b/packages/instrumentation/src/console/instrumentation.ts index 951ad807..06e4fa3b 100644 --- a/packages/instrumentation/src/console/instrumentation.ts +++ b/packages/instrumentation/src/console/instrumentation.ts @@ -76,7 +76,10 @@ export class ConsoleInstrumentation extends InstrumentationBase