diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/init.js b/dev-packages/browser-integration-tests/suites/public-api/logger/init.js new file mode 100644 index 000000000000..27397e0f90ce --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + _experiments: { + enableLogs: true, + }, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/subject.js b/dev-packages/browser-integration-tests/suites/public-api/logger/subject.js new file mode 100644 index 000000000000..e175ee4c9e27 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/subject.js @@ -0,0 +1,19 @@ +Sentry.logger.trace('test trace'); +Sentry.logger.debug('test debug'); +Sentry.logger.info('test info'); +Sentry.logger.warn('test warn'); +Sentry.logger.error('test error'); +Sentry.logger.fatal('test fatal'); + +const formattedMessage = (message, stringArg, boolArg, numberArg) => { + return Sentry.logger.fmt`test ${message} ${stringArg} ${boolArg} ${numberArg}`; +}; + +Sentry.logger.trace(formattedMessage('trace', 'stringArg', false, 123)); +Sentry.logger.debug(formattedMessage('debug', 'stringArg', false, 123)); +Sentry.logger.info(formattedMessage('info', 'stringArg', false, 123)); +Sentry.logger.warn(formattedMessage('warn', 'stringArg', false, 123)); +Sentry.logger.error(formattedMessage('error', 'stringArg', false, 123)); +Sentry.logger.fatal(formattedMessage('fatal', 'stringArg', false, 123)); + +Sentry.flush(); diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/test.ts new file mode 100644 index 000000000000..53a5f31ffcbb --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/test.ts @@ -0,0 +1,372 @@ +import { expect } from '@playwright/test'; +import type { OtelLogEnvelope } from '@sentry/core'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, properFullEnvelopeRequestParser } from '../../../utils/helpers'; + +sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page }) => { + const bundle = process.env.PW_BUNDLE || ''; + // Only run this for npm package exports + if (bundle.startsWith('bundle') || bundle.startsWith('loader')) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const event = await getFirstSentryEnvelopeRequest(page, url, properFullEnvelopeRequestParser); + const envelopeItems = event[1]; + + expect(envelopeItems[0]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'trace', + body: { stringValue: 'test trace' }, + attributes: [], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 1, + }, + ]); + + expect(envelopeItems[1]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'debug', + body: { stringValue: 'test debug' }, + attributes: [], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 5, + }, + ]); + + expect(envelopeItems[2]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'info', + body: { stringValue: 'test info' }, + attributes: [], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 9, + }, + ]); + + expect(envelopeItems[3]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'warn', + body: { stringValue: 'test warn' }, + attributes: [], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 13, + }, + ]); + + expect(envelopeItems[4]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'error', + body: { stringValue: 'test error' }, + attributes: [], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 17, + }, + ]); + + expect(envelopeItems[5]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'fatal', + body: { stringValue: 'test fatal' }, + attributes: [], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 21, + }, + ]); + + expect(envelopeItems[6]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'trace', + body: { stringValue: 'test trace stringArg false 123' }, + attributes: [ + { + key: 'sentry.message.template', + value: { + stringValue: 'test %s %s %s %s', + }, + }, + { + key: 'sentry.message.param.0', + value: { + stringValue: 'trace', + }, + }, + { + key: 'sentry.message.param.1', + value: { + stringValue: 'stringArg', + }, + }, + { + key: 'sentry.message.param.2', + value: { + boolValue: false, + }, + }, + { + key: 'sentry.message.param.3', + value: { + doubleValue: 123, + }, + }, + ], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 1, + }, + ]); + + expect(envelopeItems[7]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'debug', + body: { stringValue: 'test debug stringArg false 123' }, + attributes: [ + { + key: 'sentry.message.template', + value: { + stringValue: 'test %s %s %s %s', + }, + }, + { + key: 'sentry.message.param.0', + value: { + stringValue: 'debug', + }, + }, + { + key: 'sentry.message.param.1', + value: { + stringValue: 'stringArg', + }, + }, + { + key: 'sentry.message.param.2', + value: { + boolValue: false, + }, + }, + { + key: 'sentry.message.param.3', + value: { + doubleValue: 123, + }, + }, + ], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 5, + }, + ]); + + expect(envelopeItems[8]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'info', + body: { stringValue: 'test info stringArg false 123' }, + attributes: [ + { + key: 'sentry.message.template', + value: { + stringValue: 'test %s %s %s %s', + }, + }, + { + key: 'sentry.message.param.0', + value: { + stringValue: 'info', + }, + }, + { + key: 'sentry.message.param.1', + value: { + stringValue: 'stringArg', + }, + }, + { + key: 'sentry.message.param.2', + value: { + boolValue: false, + }, + }, + { + key: 'sentry.message.param.3', + value: { + doubleValue: 123, + }, + }, + ], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 9, + }, + ]); + + expect(envelopeItems[9]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'warn', + body: { stringValue: 'test warn stringArg false 123' }, + attributes: [ + { + key: 'sentry.message.template', + value: { + stringValue: 'test %s %s %s %s', + }, + }, + { + key: 'sentry.message.param.0', + value: { + stringValue: 'warn', + }, + }, + { + key: 'sentry.message.param.1', + value: { + stringValue: 'stringArg', + }, + }, + { + key: 'sentry.message.param.2', + value: { + boolValue: false, + }, + }, + { + key: 'sentry.message.param.3', + value: { + doubleValue: 123, + }, + }, + ], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 13, + }, + ]); + + expect(envelopeItems[10]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'error', + body: { stringValue: 'test error stringArg false 123' }, + attributes: [ + { + key: 'sentry.message.template', + value: { + stringValue: 'test %s %s %s %s', + }, + }, + { + key: 'sentry.message.param.0', + value: { + stringValue: 'error', + }, + }, + { + key: 'sentry.message.param.1', + value: { + stringValue: 'stringArg', + }, + }, + { + key: 'sentry.message.param.2', + value: { + boolValue: false, + }, + }, + { + key: 'sentry.message.param.3', + value: { + doubleValue: 123, + }, + }, + ], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 17, + }, + ]); + + expect(envelopeItems[11]).toEqual([ + { + type: 'otel_log', + }, + { + severityText: 'fatal', + body: { stringValue: 'test fatal stringArg false 123' }, + attributes: [ + { + key: 'sentry.message.template', + value: { + stringValue: 'test %s %s %s %s', + }, + }, + { + key: 'sentry.message.param.0', + value: { + stringValue: 'fatal', + }, + }, + { + key: 'sentry.message.param.1', + value: { + stringValue: 'stringArg', + }, + }, + { + key: 'sentry.message.param.2', + value: { + boolValue: false, + }, + }, + { + key: 'sentry.message.param.3', + value: { + doubleValue: 123, + }, + }, + ], + timeUnixNano: expect.any(String), + traceId: expect.any(String), + severityNumber: 21, + }, + ]); +}); diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts index 0d2b17cc0db9..d08ffccd7831 100644 --- a/dev-packages/browser-integration-tests/utils/helpers.ts +++ b/dev-packages/browser-integration-tests/utils/helpers.ts @@ -135,10 +135,13 @@ export const countEnvelopes = async ( page.on('request', requestHandler); - setTimeout(() => { - page.off('request', requestHandler); - resolve(reqCount); - }, options?.timeout || 1000); + setTimeout( + () => { + page.off('request', requestHandler); + resolve(reqCount); + }, + options?.timeout || 1000, + ); }); if (options?.url) { diff --git a/dev-packages/node-integration-tests/utils/assertions.ts b/dev-packages/node-integration-tests/utils/assertions.ts index 4197c4a70844..f3925dfd493e 100644 --- a/dev-packages/node-integration-tests/utils/assertions.ts +++ b/dev-packages/node-integration-tests/utils/assertions.ts @@ -4,6 +4,7 @@ import type { Envelope, Event, SerializedCheckIn, + SerializedOtelLog, SerializedSession, SessionAggregates, TransactionEvent, @@ -66,6 +67,12 @@ export function assertSentryClientReport(actual: ClientReport, expected: Partial }); } +export function assertSentryOtelLog(actual: SerializedOtelLog, expected: Partial): void { + expect(actual).toMatchObject({ + ...expected, + }); +} + export function assertEnvelopeHeader(actual: Envelope[0], expected: Partial): void { expect(actual).toEqual({ event_id: expect.any(String), diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts index 1d77e80bf55c..436e03a4c330 100644 --- a/dev-packages/node-integration-tests/utils/runner.ts +++ b/dev-packages/node-integration-tests/utils/runner.ts @@ -10,6 +10,7 @@ import type { Event, EventEnvelope, SerializedCheckIn, + SerializedOtelLog, SerializedSession, SessionAggregates, TransactionEvent, @@ -20,6 +21,7 @@ import { assertSentryCheckIn, assertSentryClientReport, assertSentryEvent, + assertSentryOtelLog, assertSentrySession, assertSentrySessions, assertSentryTransaction, @@ -119,6 +121,7 @@ type ExpectedSession = Partial | ((event: SerializedSession) type ExpectedSessions = Partial | ((event: SessionAggregates) => void); type ExpectedCheckIn = Partial | ((event: SerializedCheckIn) => void); type ExpectedClientReport = Partial | ((event: ClientReport) => void); +type ExpectedOtelLog = Partial | ((event: SerializedOtelLog) => void); type Expected = | { @@ -138,13 +141,17 @@ type Expected = } | { client_report: ExpectedClientReport; + } + | { + otel_log: ExpectedOtelLog; }; type ExpectedEnvelopeHeader = | { event: Partial } | { transaction: Partial } | { session: Partial } - | { sessions: Partial }; + | { sessions: Partial } + | { otel_log: Partial }; type StartResult = { completed(): Promise; @@ -325,6 +332,9 @@ export function createRunner(...paths: string[]) { } else if ('client_report' in expected) { expectClientReport(item[1] as ClientReport, expected.client_report); expectCallbackCalled(); + } else if ('otel_log' in expected) { + expectOtelLog(item[1] as SerializedOtelLog, expected.otel_log); + expectCallbackCalled(); } else { throw new Error( `Unhandled expected envelope item type: ${JSON.stringify(expected)}\nItem: ${JSON.stringify(item)}`, @@ -547,3 +557,11 @@ function expectClientReport(item: ClientReport, expected: ExpectedClientReport): assertSentryClientReport(item, expected); } } + +function expectOtelLog(item: SerializedOtelLog, expected: ExpectedOtelLog): void { + if (typeof expected === 'function') { + expected(item); + } else { + assertSentryOtelLog(item, expected); + } +} diff --git a/packages/core/src/types-hoist/index.ts b/packages/core/src/types-hoist/index.ts index 1ca827f225a5..1659aeda9bc0 100644 --- a/packages/core/src/types-hoist/index.ts +++ b/packages/core/src/types-hoist/index.ts @@ -49,6 +49,8 @@ export type { ProfileChunkItem, SpanEnvelope, SpanItem, + OtelLogEnvelope, + OtelLogItem, } from './envelope'; export type { ExtendedError } from './error'; export type { Event, EventHint, EventType, ErrorEvent, TransactionEvent } from './event';