Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { expect } from '@playwright/test';
import type { LogEnvelope } from '@sentry/core';
import { sentryTest } from '../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest, properFullEnvelopeRequestParser } from '../../../../utils/helpers';
import {
getFirstSentryEnvelopeRequest,
properFullEnvelopeRequestParser,
testingCdnBundle,
} from '../../../../utils/helpers';

sentryTest('should capture console object calls', 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();
}
sentryTest.skip(testingCdnBundle());

const url = await getLocalTestUrl({ testDir: __dirname });

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// only log attribute
Sentry.logger.info('log_before_any_scope', { log_attr: 'log_attr_1' });

Sentry.getGlobalScope().setAttributes({ global_scope_attr: true });

// this attribute will not be sent for now
Sentry.getGlobalScope().setAttribute('array_attr', [1, 2, 3]);

// global scope, log attribute
Sentry.logger.info('log_after_global_scope', { log_attr: 'log_attr_2' });

Sentry.withIsolationScope(isolationScope => {
isolationScope.setAttribute('isolation_scope_1_attr', { value: 100, unit: 'millisecond' });

// global scope, isolation scope, log attribute
Sentry.logger.info('log_with_isolation_scope', { log_attr: 'log_attr_3' });

Sentry.withScope(scope => {
scope.setAttributes({ scope_attr: { value: 200, unit: 'millisecond' } });

// global scope, isolation scope, current scope attribute, log attribute
Sentry.logger.info('log_with_scope', { log_attr: 'log_attr_4' });
});

Sentry.withScope(scope2 => {
scope2.setAttribute('scope_2_attr', { value: 300, unit: 'millisecond' });

// global scope, isolation scope, current scope attribute, log attribute
Sentry.logger.info('log_with_scope_2', { log_attr: 'log_attr_5' });
});
});

Sentry.flush();
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { expect } from '@playwright/test';
import type { LogEnvelope } from '@sentry/core';
import { sentryTest } from '../../../../utils/fixtures';
import {
getFirstSentryEnvelopeRequest,
properFullEnvelopeRequestParser,
testingCdnBundle,
} from '../../../../utils/helpers';

sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page }) => {
sentryTest.skip(testingCdnBundle());

const url = await getLocalTestUrl({ testDir: __dirname });

const event = await getFirstSentryEnvelopeRequest<LogEnvelope>(page, url, properFullEnvelopeRequestParser);
const envelopeItems = event[1];

expect(envelopeItems[0]).toEqual([
{
type: 'log',
item_count: 5,
content_type: 'application/vnd.sentry.items.log+json',
},
{
items: [
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_before_any_scope',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
log_attr: { value: 'log_attr_1', type: 'string' },
},
},
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_after_global_scope',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
global_scope_attr: { value: true, type: 'boolean' },
log_attr: { value: 'log_attr_2', type: 'string' },
},
},
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_with_isolation_scope',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
global_scope_attr: { value: true, type: 'boolean' },
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
log_attr: { value: 'log_attr_3', type: 'string' },
},
},
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_with_scope',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
global_scope_attr: { value: true, type: 'boolean' },
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
scope_attr: { value: 200, unit: 'millisecond', type: 'integer' },
log_attr: { value: 'log_attr_4', type: 'string' },
},
},
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_with_scope_2',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
global_scope_attr: { value: true, type: 'boolean' },
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
scope_2_attr: { value: 300, unit: 'millisecond', type: 'integer' },
log_attr: { value: 'log_attr_5', type: 'string' },
},
},
],
},
]);
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { expect } from '@playwright/test';
import type { LogEnvelope } from '@sentry/core';
import { sentryTest } from '../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest, properFullEnvelopeRequestParser } from '../../../../utils/helpers';
import {
getFirstSentryEnvelopeRequest,
properFullEnvelopeRequestParser,
testingCdnBundle,
} 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();
}
sentryTest.skip(testingCdnBundle());

const url = await getLocalTestUrl({ testDir: __dirname });

Expand Down
8 changes: 8 additions & 0 deletions dev-packages/browser-integration-tests/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@ export function shouldSkipTracingTest(): boolean {
return bundle != null && !bundle.includes('tracing') && !bundle.includes('esm') && !bundle.includes('cjs');
}

/**
* @returns `true` if we are testing a CDN bundle
*/
export function testingCdnBundle(): boolean {
const bundle = process.env.PW_BUNDLE;
return bundle != null && (bundle.startsWith('bundle') || bundle.startsWith('loader'));
}

/**
* Today we always run feedback tests, but this can be used to guard this if we ever need to.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0.0',
environment: 'test',
enableLogs: true,
transport: loggingTransport,
});

async function run(): Promise<void> {
// only log attribute
Sentry.logger.info('log_before_any_scope', { log_attr: 'log_attr_1' });

Sentry.getGlobalScope().setAttribute('global_scope_attr', true);

// this attribute will not be sent for now
Sentry.getGlobalScope().setAttributes({ array_attr: [1, 2, 3] });

// global scope, log attribute
Sentry.logger.info('log_after_global_scope', { log_attr: 'log_attr_2' });

Sentry.withIsolationScope(isolationScope => {
isolationScope.setAttribute('isolation_scope_1_attr', { value: 100, unit: 'millisecond' });

// global scope, isolation scope, log attribute
Sentry.logger.info('log_with_isolation_scope', { log_attr: 'log_attr_3' });

Sentry.withScope(scope => {
scope.setAttribute('scope_attr', { value: 200, unit: 'millisecond' });

// global scope, isolation scope, current scope attribute, log attribute
Sentry.logger.info('log_with_scope', { log_attr: 'log_attr_4' });
});

Sentry.withScope(scope2 => {
scope2.setAttribute('scope_2_attr', { value: 300, unit: 'millisecond' });

// global scope, isolation scope, current scope attribute, log attribute
Sentry.logger.info('log_with_scope_2', { log_attr: 'log_attr_5' });
});
});

await Sentry.flush();
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
run();
109 changes: 109 additions & 0 deletions dev-packages/node-integration-tests/suites/public-api/logger/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type { SerializedLog } from '@sentry/core';
import { afterAll, describe, expect, test } from 'vitest';
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';

const commonAttributes: SerializedLog['attributes'] = {
'sentry.environment': {
type: 'string',
value: 'test',
},
'sentry.release': {
type: 'string',
value: '1.0.0',
},
'sentry.sdk.name': {
type: 'string',
value: 'sentry.javascript.node',
},
'sentry.sdk.version': {
type: 'string',
value: expect.any(String),
},
'server.address': {
type: 'string',
value: expect.any(String),
},
};

describe('logs', () => {
afterAll(() => {
cleanupChildProcesses();
});

test('captures logs with scope and log attributes', async () => {
const runner = createRunner(__dirname, 'scenario.ts')
.expect({
log: {
items: [
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_before_any_scope',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
...commonAttributes,
log_attr: { value: 'log_attr_1', type: 'string' },
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Node integration test uses partial matching for attributes (Bugbot Rules)

Flagging per review rules: The test uses toMatchObject (via assertSentryLogContainer) which is a relaxed assertion. The test scenario sets array_attr: [1, 2, 3] on the global scope, which should NOT appear in logs (since non-primitive scope attributes are discarded). However, the test only verifies that expected attributes are present, not that array_attr is absent. If a regression caused non-primitive scope attributes to leak into logs, this test would not catch it. The browser integration test correctly uses toEqual for exact matching.

Fix in Cursor Fix in Web

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair point if true

},
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_after_global_scope',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
...commonAttributes,
global_scope_attr: { value: true, type: 'boolean' },
log_attr: { value: 'log_attr_2', type: 'string' },
},
},
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_with_isolation_scope',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
...commonAttributes,
global_scope_attr: { value: true, type: 'boolean' },
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
log_attr: { value: 'log_attr_3', type: 'string' },
},
},
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_with_scope',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
...commonAttributes,
global_scope_attr: { value: true, type: 'boolean' },
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
scope_attr: { value: 200, unit: 'millisecond', type: 'integer' },
log_attr: { value: 'log_attr_4', type: 'string' },
},
},
{
timestamp: expect.any(Number),
level: 'info',
body: 'log_with_scope_2',
severity_number: 9,
trace_id: expect.any(String),
attributes: {
...commonAttributes,
global_scope_attr: { value: true, type: 'boolean' },
isolation_scope_1_attr: { value: 100, unit: 'millisecond', type: 'integer' },
scope_2_attr: { value: 300, unit: 'millisecond', type: 'integer' },
log_attr: { value: 'log_attr_5', type: 'string' },
},
},
],
},
})
.start();

await runner.completed();
});
});
Loading
Loading