Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

- **feat(browser): Add `ingest_settings` to v2 log envelope payload ([#20453](https://github.com/getsentry/sentry-javascript/pull/20453))**

Inference of user data (e.g. IP address, browser name/version) on log events is now gated behind the `sendDefaultPii` option. Previously, this data was always inferred by default.

## 10.51.0

### Important Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page
content_type: 'application/vnd.sentry.items.log+json',
},
{
version: 2,
ingest_settings: { infer_ip: 'never', infer_user_agent: 'never' },
items: [
{
timestamp: expect.any(Number),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page
content_type: 'application/vnd.sentry.items.log+json',
},
{
version: 2,
ingest_settings: { infer_ip: 'never', infer_user_agent: 'never' },
items: [
{
timestamp: expect.any(Number),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page
content_type: 'application/vnd.sentry.items.log+json',
},
{
version: 2,
ingest_settings: { infer_ip: 'never', infer_user_agent: 'never' },
items: [
{
timestamp: expect.any(Number),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('light mode logs', () => {
.expect({
log: logsContainer => {
expect(logsContainer).toEqual({
version: 2,
items: [
{
attributes: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('logger public API', () => {
.expect({
log: logsContainer => {
expect(logsContainer).toEqual({
version: 2,
items: [
{
attributes: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ describe('logs', () => {
.expect({
log: logsContainer => {
expect(logsContainer).toEqual({
version: 2,
items: [
{
timestamp: expect.any(Number),
Expand Down
14 changes: 12 additions & 2 deletions packages/core/src/logs/envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@ import type { SerializedLog } from '../types-hoist/log';
import type { SdkMetadata } from '../types-hoist/sdkmetadata';
import { dsnToString } from '../utils/dsn';
import { createEnvelope } from '../utils/envelope';
import { isBrowser } from '../utils/isBrowser';

/**
* Creates a log container envelope item for a list of logs.
*
* @param items - The logs to include in the envelope.
* @param inferUserData - If true, tells Relay to infer the end-user IP and User-Agent from the incoming request.
* Only emitted as `ingest_settings` in browser environments.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: extra leading spaces, intended?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes

* @returns The created log container envelope item.
*/
export function createLogContainerEnvelopeItem(items: Array<SerializedLog>): LogContainerItem {
export function createLogContainerEnvelopeItem(items: Array<SerializedLog>, inferUserData?: boolean): LogContainerItem {
const inferSetting = inferUserData ? 'auto' : 'never';
return [
{
type: 'log',
item_count: items.length,
content_type: 'application/vnd.sentry.items.log+json',
},
{
version: 2,
...(isBrowser() && {
ingest_settings: { infer_ip: inferSetting, infer_user_agent: inferSetting },
}),
items,
},
];
Expand All @@ -33,13 +41,15 @@ export function createLogContainerEnvelopeItem(items: Array<SerializedLog>): Log
* @param metadata - The metadata to include in the envelope.
* @param tunnel - The tunnel to include in the envelope.
* @param dsn - The DSN to include in the envelope.
* @param inferUserData - If true, tells Relay to infer the end-user IP and User-Agent from the incoming request.
* @returns The created envelope.
*/
export function createLogEnvelope(
logs: Array<SerializedLog>,
metadata?: SdkMetadata,
tunnel?: string,
dsn?: DsnComponents,
inferUserData?: boolean,
): LogEnvelope {
const headers: LogEnvelope[0] = {};

Expand All @@ -54,5 +64,5 @@ export function createLogEnvelope(
headers.dsn = dsnToString(dsn);
}

return createEnvelope<LogEnvelope>(headers, [createLogContainerEnvelopeItem(logs)]);
return createEnvelope<LogEnvelope>(headers, [createLogContainerEnvelopeItem(logs, inferUserData)]);
}
8 changes: 7 additions & 1 deletion packages/core/src/logs/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,13 @@ export function _INTERNAL_flushLogsBuffer(client: Client, maybeLogBuffer?: Array
}

const clientOptions = client.getOptions();
const envelope = createLogEnvelope(logBuffer, clientOptions._metadata, clientOptions.tunnel, client.getDsn());
const envelope = createLogEnvelope(
logBuffer,
clientOptions._metadata,
clientOptions.tunnel,
client.getDsn(),
clientOptions.sendDefaultPii,
);

// Clear the log buffer after envelopes have been constructed.
_getBufferMap().set(client, []);
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/types-hoist/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,10 @@ export interface SerializedLog {
}

export type SerializedLogContainer = {
version?: number;
ingest_settings?: {
infer_ip?: 'auto' | 'never';
infer_user_agent?: 'auto' | 'never';
};
items: Array<SerializedLog>;
};
62 changes: 54 additions & 8 deletions packages/core/test/lib/logs/envelope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { SerializedLog } from '../../../src/types-hoist/log';
import type { SdkMetadata } from '../../../src/types-hoist/sdkmetadata';
import * as utilsDsn from '../../../src/utils/dsn';
import * as utilsEnvelope from '../../../src/utils/envelope';
import { isBrowser } from '../../../src/utils/isBrowser';

// Mock utils functions
vi.mock('../../../src/utils/dsn', () => ({
Expand All @@ -13,20 +14,65 @@ vi.mock('../../../src/utils/dsn', () => ({
vi.mock('../../../src/utils/envelope', () => ({
createEnvelope: vi.fn((_headers, items) => [_headers, items]),
}));
vi.mock('../../../src/utils/isBrowser', () => ({
isBrowser: vi.fn(() => false),
}));

afterEach(() => {
vi.mocked(isBrowser).mockReturnValue(false);
});

describe('createLogContainerEnvelopeItem', () => {
it('creates an envelope item with correct structure', () => {
it('emits version: 2 without ingest_settings when not in browser', () => {
const mockLog: SerializedLog = {
timestamp: 1713859200,
level: 'info',
body: 'Test log message',
};

const result = createLogContainerEnvelopeItem([mockLog], true);

expect(result[0]).toEqual({ type: 'log', item_count: 1, content_type: 'application/vnd.sentry.items.log+json' });
expect(result[1]).toEqual({
version: 2,
items: [mockLog],
});
});

it("includes ingest_settings with 'auto' values when in browser and inferUserData is true", () => {
vi.mocked(isBrowser).mockReturnValue(true);

const mockLog: SerializedLog = {
timestamp: 1713859200,
level: 'error',
body: 'Test error message',
level: 'info',
body: 'Test log message',
};

const result = createLogContainerEnvelopeItem([mockLog, mockLog]);
const result = createLogContainerEnvelopeItem([mockLog], true);

expect(result[1]).toEqual({
version: 2,
ingest_settings: { infer_ip: 'auto', infer_user_agent: 'auto' },
items: [mockLog],
});
});

it("includes ingest_settings with 'never' values when in browser and inferUserData is false", () => {
vi.mocked(isBrowser).mockReturnValue(true);

expect(result).toHaveLength(2);
expect(result[0]).toEqual({ type: 'log', item_count: 2, content_type: 'application/vnd.sentry.items.log+json' });
expect(result[1]).toEqual({ items: [mockLog, mockLog] });
const mockLog: SerializedLog = {
timestamp: 1713859200,
level: 'info',
body: 'Test log message',
};

const result = createLogContainerEnvelopeItem([mockLog], false);

expect(result[1]).toEqual({
version: 2,
ingest_settings: { infer_ip: 'never', infer_user_agent: 'never' },
items: [mockLog],
});
});
});

Expand Down Expand Up @@ -133,7 +179,7 @@ describe('createLogEnvelope', () => {
expect.arrayContaining([
expect.arrayContaining([
{ type: 'log', item_count: 2, content_type: 'application/vnd.sentry.items.log+json' },
{ items: mockLogs },
{ version: 2, items: mockLogs },
]),
]),
);
Expand Down
Loading