From 0af425c5a414da5b962f1b77075876e80b6501b7 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 19 Feb 2026 10:10:45 -0500 Subject: [PATCH 01/10] feat: add tie-breaking attribute to generated logs --- packages/core/src/logs/internal.ts | 11 ++ packages/core/test/lib/logs/internal.test.ts | 150 +++++++++++++++++-- 2 files changed, 148 insertions(+), 13 deletions(-) diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 3408b01a5f96..d7fc64e945bc 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -15,6 +15,9 @@ import { SEVERITY_TEXT_TO_SEVERITY_NUMBER } from './constants'; import { createLogEnvelope } from './envelope'; const MAX_LOG_BUFFER_SIZE = 100; +const LOG_SEQUENCE_ATTR_KEY = 'sentry.log.sequence'; + +let _logSequenceNumber = 0; /** * Sets a log attribute if the value exists and the attribute key is not already present. @@ -163,6 +166,7 @@ export function _INTERNAL_captureLog( attributes: { ...serializeAttributes(scopeAttributes), ...serializeAttributes(logAttributes, true), + [LOG_SEQUENCE_ATTR_KEY]: { value: _logSequenceNumber++, type: 'integer' }, }, }; @@ -215,3 +219,10 @@ function _getBufferMap(): WeakMap> { // The reference to the Client <> LogBuffer map is stored on the carrier to ensure it's always the same return getGlobalSingleton('clientToLogBufferMap', () => new WeakMap>()); } + +/** + * Resets the log sequence number. Only exported for testing purposes. + */ +export function _INTERNAL_resetLogSequenceNumber(): void { + _logSequenceNumber = 0; +} diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 2eec7c64dcbc..8cd9898b46e3 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -1,6 +1,11 @@ -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { fmt, Scope } from '../../../src'; -import { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_getLogBuffer } from '../../../src/logs/internal'; +import { + _INTERNAL_captureLog, + _INTERNAL_flushLogsBuffer, + _INTERNAL_getLogBuffer, + _INTERNAL_resetLogSequenceNumber, +} from '../../../src/logs/internal'; import type { Log } from '../../../src/types-hoist/log'; import * as loggerModule from '../../../src/utils/debug-logger'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; @@ -8,6 +13,9 @@ import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; describe('_INTERNAL_captureLog', () => { + beforeEach(() => { + _INTERNAL_resetLogSequenceNumber(); + }); it('captures and sends logs', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); @@ -23,7 +31,9 @@ describe('_INTERNAL_captureLog', () => { timestamp: expect.any(Number), trace_id: expect.any(String), severity_number: 9, - attributes: {}, + attributes: { + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }, }), ); }); @@ -86,6 +96,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -117,6 +128,7 @@ describe('_INTERNAL_captureLog', () => { value: '7.0.0', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -168,6 +180,7 @@ describe('_INTERNAL_captureLog', () => { value: 'auth', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -219,6 +232,7 @@ describe('_INTERNAL_captureLog', () => { type: 'boolean', value: true, }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -278,6 +292,7 @@ describe('_INTERNAL_captureLog', () => { value: 'Sentry', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -290,7 +305,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'debug', message: fmt`User logged in` }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }); }); it('processes logs through beforeSendLog when provided', () => { @@ -344,7 +361,6 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, - // during serialization, they're converted to the typed attribute format scope_1: { value: 'attribute_value', type: 'string', @@ -354,6 +370,7 @@ describe('_INTERNAL_captureLog', () => { unit: 'gigabytes', type: 'integer', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }, }), ); @@ -439,6 +456,7 @@ describe('_INTERNAL_captureLog', () => { value: 'sampled-replay-id', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -464,8 +482,9 @@ describe('_INTERNAL_captureLog', () => { expect(mockReplayIntegration.getReplayId).toHaveBeenCalledWith(true); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - // Should not include sentry.replay_id attribute - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }); }); it('includes replay ID for buffer mode sessions', () => { @@ -499,6 +518,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -514,8 +534,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log without replay' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - // Should not include sentry.replay_id attribute - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }); }); it('combines replay ID with other log attributes', () => { @@ -568,6 +589,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test-replay-id', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -592,7 +614,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: `test log with replay returning ${returnValue}` }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); }); }); @@ -626,6 +650,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -654,6 +679,7 @@ describe('_INTERNAL_captureLog', () => { value: 'session-replay-id', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -683,6 +709,7 @@ describe('_INTERNAL_captureLog', () => { value: 'stopped-replay-id', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -708,7 +735,9 @@ describe('_INTERNAL_captureLog', () => { expect(mockReplayIntegration.getRecordingMode).not.toHaveBeenCalled(); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); expect(logAttributes).not.toHaveProperty('sentry.internal.replay_is_buffering'); }); @@ -725,7 +754,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log without replay integration' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -784,6 +815,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -819,6 +851,7 @@ describe('_INTERNAL_captureLog', () => { value: 'testuser', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -844,6 +877,7 @@ describe('_INTERNAL_captureLog', () => { value: '123', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -874,6 +908,7 @@ describe('_INTERNAL_captureLog', () => { value: 'testuser', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -891,7 +926,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log with empty user' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }); }); it('combines user data with other log attributes', () => { @@ -945,6 +982,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -975,6 +1013,7 @@ describe('_INTERNAL_captureLog', () => { value: 'user@example.com', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1018,6 +1057,7 @@ describe('_INTERNAL_captureLog', () => { value: 'user@example.com', // Only added because user.email wasn't already present type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1066,6 +1106,7 @@ describe('_INTERNAL_captureLog', () => { value: 'scope-user', // Added from scope because not present type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -1126,6 +1167,89 @@ describe('_INTERNAL_captureLog', () => { value: '7.0.0', type: 'string', }, + 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + }); + }); + + describe('sentry.log.sequence', () => { + it('increments the sequence number across consecutive logs', () => { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); + + _INTERNAL_captureLog({ level: 'info', message: 'first' }, scope); + _INTERNAL_captureLog({ level: 'info', message: 'second' }, scope); + _INTERNAL_captureLog({ level: 'info', message: 'third' }, scope); + + const buffer = _INTERNAL_getLogBuffer(client); + expect(buffer?.[0]?.attributes?.['sentry.log.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer?.[1]?.attributes?.['sentry.log.sequence']).toEqual({ value: 1, type: 'integer' }); + expect(buffer?.[2]?.attributes?.['sentry.log.sequence']).toEqual({ value: 2, type: 'integer' }); + }); + + it('does not increment the sequence number for dropped logs', () => { + const beforeSendLog = vi.fn().mockImplementation(log => { + if (log.message === 'drop me') { + return null; + } + return log; + }); + + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true, beforeSendLog }); + const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); + + _INTERNAL_captureLog({ level: 'info', message: 'keep first' }, scope); + _INTERNAL_captureLog({ level: 'info', message: 'drop me' }, scope); + _INTERNAL_captureLog({ level: 'info', message: 'keep second' }, scope); + + const buffer = _INTERNAL_getLogBuffer(client); + expect(buffer).toHaveLength(2); + expect(buffer?.[0]?.attributes?.['sentry.log.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer?.[1]?.attributes?.['sentry.log.sequence']).toEqual({ value: 1, type: 'integer' }); + }); + + it('produces strictly monotonically increasing sequence numbers', () => { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); + + const count = 50; + for (let i = 0; i < count; i++) { + _INTERNAL_captureLog({ level: 'info', message: `log ${i}` }, scope); + } + + const buffer = _INTERNAL_getLogBuffer(client)!; + expect(buffer).toHaveLength(count); + + for (let i = 1; i < count; i++) { + const prev = (buffer[i - 1]?.attributes?.['sentry.log.sequence'] as { value: number }).value; + const curr = (buffer[i]?.attributes?.['sentry.log.sequence'] as { value: number }).value; + expect(curr).toBe(prev + 1); + } + }); + + it('resets the sequence number via _INTERNAL_resetLogSequenceNumber', () => { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); + + _INTERNAL_captureLog({ level: 'info', message: 'first' }, scope); + + _INTERNAL_resetLogSequenceNumber(); + + const client2 = new TestClient(options); + const scope2 = new Scope(); + scope2.setClient(client2); + + _INTERNAL_captureLog({ level: 'info', message: 'after reset' }, scope2); + + const buffer2 = _INTERNAL_getLogBuffer(client2); + expect(buffer2?.[0]?.attributes?.['sentry.log.sequence']).toEqual({ value: 0, type: 'integer' }); }); }); }); From bd3f97ffc5b698f091844c5d276201f2b908cfc6 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 10:56:47 -0500 Subject: [PATCH 02/10] feat(core): Align sequence attribute with spec and add to metrics Rename `sentry.log.sequence` to `sentry.timestamp.sequence` per the published spec. Extract sequence logic into a shared utility so both logs and metrics use a single counter, enabling cross-telemetry ordering. The counter resets to 0 when the integer millisecond timestamp changes. Co-Authored-By: Claude Opus 4.6 --- packages/core/src/logs/internal.ts | 17 ++-- packages/core/src/metrics/internal.ts | 7 +- packages/core/src/utils/sequence.ts | 41 +++++++++ packages/core/test/lib/logs/internal.test.ts | 91 ++++++++++--------- .../core/test/lib/metrics/internal.test.ts | 82 +++++++++++++++-- packages/core/test/lib/utils/sequence.test.ts | 80 ++++++++++++++++ 6 files changed, 255 insertions(+), 63 deletions(-) create mode 100644 packages/core/src/utils/sequence.ts create mode 100644 packages/core/test/lib/utils/sequence.test.ts diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index d7fc64e945bc..41d104f69fb1 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -8,6 +8,7 @@ import type { Log, SerializedLog } from '../types-hoist/log'; import { consoleSandbox, debug } from '../utils/debug-logger'; import { isParameterizedString } from '../utils/is'; import { getCombinedScopeData } from '../utils/scopeData'; +import { getSequenceAttribute } from '../utils/sequence'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; import { _getTraceInfoFromScope } from '../utils/trace-info'; @@ -15,9 +16,6 @@ import { SEVERITY_TEXT_TO_SEVERITY_NUMBER } from './constants'; import { createLogEnvelope } from './envelope'; const MAX_LOG_BUFFER_SIZE = 100; -const LOG_SEQUENCE_ATTR_KEY = 'sentry.log.sequence'; - -let _logSequenceNumber = 0; /** * Sets a log attribute if the value exists and the attribute key is not already present. @@ -157,8 +155,11 @@ export function _INTERNAL_captureLog( const { level, message, attributes: logAttributes = {}, severityNumber } = log; + const timestamp = timestampInSeconds(); + const sequenceAttr = getSequenceAttribute(timestamp); + const serializedLog: SerializedLog = { - timestamp: timestampInSeconds(), + timestamp, level, body: message, trace_id: traceContext?.trace_id, @@ -166,7 +167,7 @@ export function _INTERNAL_captureLog( attributes: { ...serializeAttributes(scopeAttributes), ...serializeAttributes(logAttributes, true), - [LOG_SEQUENCE_ATTR_KEY]: { value: _logSequenceNumber++, type: 'integer' }, + [sequenceAttr.key]: sequenceAttr.value, }, }; @@ -220,9 +221,3 @@ function _getBufferMap(): WeakMap> { return getGlobalSingleton('clientToLogBufferMap', () => new WeakMap>()); } -/** - * Resets the log sequence number. Only exported for testing purposes. - */ -export function _INTERNAL_resetLogSequenceNumber(): void { - _logSequenceNumber = 0; -} diff --git a/packages/core/src/metrics/internal.ts b/packages/core/src/metrics/internal.ts index bdd13d884967..6748ae760cb7 100644 --- a/packages/core/src/metrics/internal.ts +++ b/packages/core/src/metrics/internal.ts @@ -9,6 +9,7 @@ import type { Metric, SerializedMetric } from '../types-hoist/metric'; import type { User } from '../types-hoist/user'; import { debug } from '../utils/debug-logger'; import { getCombinedScopeData } from '../utils/scopeData'; +import { getSequenceAttribute } from '../utils/sequence'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; import { _getTraceInfoFromScope } from '../utils/trace-info'; @@ -135,8 +136,11 @@ function _buildSerializedMetric( const traceId = span ? span.spanContext().traceId : traceContext?.trace_id; const spanId = span ? span.spanContext().spanId : undefined; + const timestamp = timestampInSeconds(); + const sequenceAttr = getSequenceAttribute(timestamp); + return { - timestamp: timestampInSeconds(), + timestamp, trace_id: traceId ?? '', span_id: spanId, name: metric.name, @@ -146,6 +150,7 @@ function _buildSerializedMetric( attributes: { ...serializeAttributes(scopeAttributes), ...serializeAttributes(metric.attributes, 'skip-undefined'), + [sequenceAttr.key]: sequenceAttr.value, }, }; } diff --git a/packages/core/src/utils/sequence.ts b/packages/core/src/utils/sequence.ts new file mode 100644 index 000000000000..d2755d7a0724 --- /dev/null +++ b/packages/core/src/utils/sequence.ts @@ -0,0 +1,41 @@ +const SEQUENCE_ATTR_KEY = 'sentry.timestamp.sequence'; + +let _sequenceNumber = 0; +let _previousTimestampMs: number | undefined; + +/** + * Returns the `sentry.timestamp.sequence` attribute entry for a serialized telemetry item. + * + * The sequence number starts at 0 and increments by 1 for each item captured. + * It resets to 0 when the current item's integer millisecond timestamp differs + * from the previous item's integer millisecond timestamp. + * + * @param timestampInSeconds - The timestamp of the telemetry item in seconds. + */ +export function getSequenceAttribute(timestampInSeconds: number): { + key: string; + value: { value: number; type: 'integer' }; +} { + const nowMs = Math.floor(timestampInSeconds * 1000); + + if (_previousTimestampMs !== undefined && nowMs !== _previousTimestampMs) { + _sequenceNumber = 0; + } + + const value = _sequenceNumber; + _sequenceNumber++; + _previousTimestampMs = nowMs; + + return { + key: SEQUENCE_ATTR_KEY, + value: { value, type: 'integer' }, + }; +} + +/** + * Resets the sequence number state. Only exported for testing purposes. + */ +export function _INTERNAL_resetSequenceNumber(): void { + _sequenceNumber = 0; + _previousTimestampMs = undefined; +} diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 8cd9898b46e3..25e941ab6177 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -4,17 +4,18 @@ import { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_getLogBuffer, - _INTERNAL_resetLogSequenceNumber, } from '../../../src/logs/internal'; import type { Log } from '../../../src/types-hoist/log'; import * as loggerModule from '../../../src/utils/debug-logger'; +import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/sequence'; +import * as timeModule from '../../../src/utils/time'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; describe('_INTERNAL_captureLog', () => { beforeEach(() => { - _INTERNAL_resetLogSequenceNumber(); + _INTERNAL_resetSequenceNumber(); }); it('captures and sends logs', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); @@ -32,7 +33,7 @@ describe('_INTERNAL_captureLog', () => { trace_id: expect.any(String), severity_number: 9, attributes: { - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }), ); @@ -96,7 +97,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -128,7 +129,7 @@ describe('_INTERNAL_captureLog', () => { value: '7.0.0', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -180,7 +181,7 @@ describe('_INTERNAL_captureLog', () => { value: 'auth', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -232,7 +233,7 @@ describe('_INTERNAL_captureLog', () => { type: 'boolean', value: true, }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -292,7 +293,7 @@ describe('_INTERNAL_captureLog', () => { value: 'Sentry', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -306,7 +307,7 @@ describe('_INTERNAL_captureLog', () => { const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -370,7 +371,7 @@ describe('_INTERNAL_captureLog', () => { unit: 'gigabytes', type: 'integer', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }), ); @@ -456,7 +457,7 @@ describe('_INTERNAL_captureLog', () => { value: 'sampled-replay-id', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -483,7 +484,7 @@ describe('_INTERNAL_captureLog', () => { const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -518,7 +519,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -535,7 +536,7 @@ describe('_INTERNAL_captureLog', () => { const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -589,7 +590,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test-replay-id', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -615,7 +616,7 @@ describe('_INTERNAL_captureLog', () => { const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); }); @@ -650,7 +651,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -679,7 +680,7 @@ describe('_INTERNAL_captureLog', () => { value: 'session-replay-id', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -709,7 +710,7 @@ describe('_INTERNAL_captureLog', () => { value: 'stopped-replay-id', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -736,7 +737,7 @@ describe('_INTERNAL_captureLog', () => { const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); expect(logAttributes).not.toHaveProperty('sentry.internal.replay_is_buffering'); @@ -755,7 +756,7 @@ describe('_INTERNAL_captureLog', () => { const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); @@ -815,7 +816,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -851,7 +852,7 @@ describe('_INTERNAL_captureLog', () => { value: 'testuser', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -877,7 +878,7 @@ describe('_INTERNAL_captureLog', () => { value: '123', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -908,7 +909,7 @@ describe('_INTERNAL_captureLog', () => { value: 'testuser', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -927,7 +928,7 @@ describe('_INTERNAL_captureLog', () => { const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; expect(logAttributes).toEqual({ - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -982,7 +983,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1013,7 +1014,7 @@ describe('_INTERNAL_captureLog', () => { value: 'user@example.com', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1057,7 +1058,7 @@ describe('_INTERNAL_captureLog', () => { value: 'user@example.com', // Only added because user.email wasn't already present type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1106,7 +1107,7 @@ describe('_INTERNAL_captureLog', () => { value: 'scope-user', // Added from scope because not present type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -1167,11 +1168,11 @@ describe('_INTERNAL_captureLog', () => { value: '7.0.0', type: 'string', }, - 'sentry.log.sequence': { value: expect.any(Number), type: 'integer' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); - describe('sentry.log.sequence', () => { + describe('sentry.timestamp.sequence', () => { it('increments the sequence number across consecutive logs', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); @@ -1183,9 +1184,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'third' }, scope); const buffer = _INTERNAL_getLogBuffer(client); - expect(buffer?.[0]?.attributes?.['sentry.log.sequence']).toEqual({ value: 0, type: 'integer' }); - expect(buffer?.[1]?.attributes?.['sentry.log.sequence']).toEqual({ value: 1, type: 'integer' }); - expect(buffer?.[2]?.attributes?.['sentry.log.sequence']).toEqual({ value: 2, type: 'integer' }); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); + expect(buffer?.[2]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 2, type: 'integer' }); }); it('does not increment the sequence number for dropped logs', () => { @@ -1207,11 +1208,13 @@ describe('_INTERNAL_captureLog', () => { const buffer = _INTERNAL_getLogBuffer(client); expect(buffer).toHaveLength(2); - expect(buffer?.[0]?.attributes?.['sentry.log.sequence']).toEqual({ value: 0, type: 'integer' }); - expect(buffer?.[1]?.attributes?.['sentry.log.sequence']).toEqual({ value: 1, type: 'integer' }); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); }); - it('produces strictly monotonically increasing sequence numbers', () => { + it('produces monotonically increasing sequence numbers within the same millisecond', () => { + vi.spyOn(timeModule, 'timestampInSeconds').mockReturnValue(1000.001); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); const scope = new Scope(); @@ -1226,13 +1229,15 @@ describe('_INTERNAL_captureLog', () => { expect(buffer).toHaveLength(count); for (let i = 1; i < count; i++) { - const prev = (buffer[i - 1]?.attributes?.['sentry.log.sequence'] as { value: number }).value; - const curr = (buffer[i]?.attributes?.['sentry.log.sequence'] as { value: number }).value; + const prev = (buffer[i - 1]?.attributes?.['sentry.timestamp.sequence'] as { value: number }).value; + const curr = (buffer[i]?.attributes?.['sentry.timestamp.sequence'] as { value: number }).value; expect(curr).toBe(prev + 1); } + + vi.restoreAllMocks(); }); - it('resets the sequence number via _INTERNAL_resetLogSequenceNumber', () => { + it('resets the sequence number via _INTERNAL_resetSequenceNumber', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); const scope = new Scope(); @@ -1240,7 +1245,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'first' }, scope); - _INTERNAL_resetLogSequenceNumber(); + _INTERNAL_resetSequenceNumber(); const client2 = new TestClient(options); const scope2 = new Scope(); @@ -1249,7 +1254,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'after reset' }, scope2); const buffer2 = _INTERNAL_getLogBuffer(client2); - expect(buffer2?.[0]?.attributes?.['sentry.log.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); }); }); }); diff --git a/packages/core/test/lib/metrics/internal.test.ts b/packages/core/test/lib/metrics/internal.test.ts index 434f4b6c8289..0b70176a2785 100644 --- a/packages/core/test/lib/metrics/internal.test.ts +++ b/packages/core/test/lib/metrics/internal.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Scope } from '../../../src'; import { _INTERNAL_captureMetric, @@ -7,11 +7,18 @@ import { } from '../../../src/metrics/internal'; import type { Metric } from '../../../src/types-hoist/metric'; import * as loggerModule from '../../../src/utils/debug-logger'; +import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/sequence'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; +const SEQUENCE_ATTR = { 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' } }; + describe('_INTERNAL_captureMetric', () => { + beforeEach(() => { + _INTERNAL_resetSequenceNumber(); + }); + it('captures and sends metrics', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); @@ -27,7 +34,7 @@ describe('_INTERNAL_captureMetric', () => { value: 1, timestamp: expect.any(Number), trace_id: expect.any(String), - attributes: {}, + attributes: { ...SEQUENCE_ATTR }, }), ); }); @@ -80,6 +87,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'sentry.release': { value: '1.0.0', type: 'string', @@ -110,6 +118,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string', @@ -160,6 +169,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, endpoint: { value: '/api/users', type: 'string', @@ -183,6 +193,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, scope_attribute_1: { value: 1, type: 'integer', @@ -213,6 +224,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'my-attribute': { value: 43, type: 'integer' }, }); }); @@ -286,6 +298,7 @@ describe('_INTERNAL_captureMetric', () => { expect.objectContaining({ name: 'modified.original.metric', attributes: { + ...SEQUENCE_ATTR, processed: { value: true, type: 'boolean', @@ -370,6 +383,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'sentry.replay_id': { value: 'sampled-replay-id', type: 'string', @@ -397,7 +411,7 @@ describe('_INTERNAL_captureMetric', () => { expect(mockReplayIntegration.getReplayId).toHaveBeenCalledWith(true); const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual({}); + expect(metricAttributes).toEqual({ ...SEQUENCE_ATTR }); }); it('includes replay ID for buffer mode sessions', () => { @@ -422,6 +436,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'sentry.replay_id': { value: 'buffer-replay-id', type: 'string', @@ -445,7 +460,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope }); const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual({}); + expect(metricAttributes).toEqual({ ...SEQUENCE_ATTR }); }); it('combines replay ID with other metric attributes', () => { @@ -479,6 +494,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, endpoint: { value: '/api/users', type: 'string', @@ -523,7 +539,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope }); const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual({}); + expect(metricAttributes).toEqual({ ...SEQUENCE_ATTR }); expect(metricAttributes).not.toHaveProperty('sentry.replay_id'); }); }); @@ -549,6 +565,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'sentry.replay_id': { value: 'buffer-replay-id', type: 'string', @@ -581,6 +598,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'sentry.replay_id': { value: 'session-replay-id', type: 'string', @@ -610,6 +628,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'sentry.replay_id': { value: 'stopped-replay-id', type: 'string', @@ -639,7 +658,7 @@ describe('_INTERNAL_captureMetric', () => { expect(mockReplayIntegration.getRecordingMode).not.toHaveBeenCalled(); const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual({}); + expect(metricAttributes).toEqual({ ...SEQUENCE_ATTR }); expect(metricAttributes).not.toHaveProperty('sentry.replay_id'); expect(metricAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -656,7 +675,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope }); const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual({}); + expect(metricAttributes).toEqual({ ...SEQUENCE_ATTR }); expect(metricAttributes).not.toHaveProperty('sentry.replay_id'); expect(metricAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -692,6 +711,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, endpoint: { value: '/api/users', type: 'string', @@ -738,6 +758,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'user.id': { value: '123', type: 'string', @@ -769,6 +790,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'user.id': { value: '123', type: 'string', @@ -793,6 +815,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'user.email': { value: 'user@example.com', type: 'string', @@ -816,7 +839,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope }); const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual({}); + expect(metricAttributes).toEqual({ ...SEQUENCE_ATTR }); }); it('combines user data with other metric attributes', () => { @@ -846,6 +869,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, endpoint: { value: '/api/users', type: 'string', @@ -890,6 +914,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'user.id': { value: 123, type: 'integer', @@ -928,6 +953,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'user.custom': { value: 'custom-value', type: 'string', @@ -971,6 +997,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'other.attr': { value: 'value', type: 'string', @@ -1027,6 +1054,7 @@ describe('_INTERNAL_captureMetric', () => { const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; expect(metricAttributes).toEqual({ + ...SEQUENCE_ATTR, 'user.custom': { value: 'preserved-value', type: 'string', @@ -1049,4 +1077,42 @@ describe('_INTERNAL_captureMetric', () => { }, }); }); + + describe('sentry.timestamp.sequence', () => { + it('increments the sequence number across consecutive metrics', () => { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); + const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); + + _INTERNAL_captureMetric({ type: 'counter', name: 'first', value: 1 }, { scope }); + _INTERNAL_captureMetric({ type: 'counter', name: 'second', value: 2 }, { scope }); + _INTERNAL_captureMetric({ type: 'counter', name: 'third', value: 3 }, { scope }); + + const buffer = _INTERNAL_getMetricBuffer(client); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); + expect(buffer?.[2]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 2, type: 'integer' }); + }); + + it('resets the sequence number via _INTERNAL_resetSequenceNumber', () => { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); + const client = new TestClient(options); + const scope = new Scope(); + scope.setClient(client); + + _INTERNAL_captureMetric({ type: 'counter', name: 'first', value: 1 }, { scope }); + + _INTERNAL_resetSequenceNumber(); + + const client2 = new TestClient(options); + const scope2 = new Scope(); + scope2.setClient(client2); + + _INTERNAL_captureMetric({ type: 'counter', name: 'after reset', value: 2 }, { scope: scope2 }); + + const buffer2 = _INTERNAL_getMetricBuffer(client2); + expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + }); + }); }); diff --git a/packages/core/test/lib/utils/sequence.test.ts b/packages/core/test/lib/utils/sequence.test.ts new file mode 100644 index 000000000000..733a809a9791 --- /dev/null +++ b/packages/core/test/lib/utils/sequence.test.ts @@ -0,0 +1,80 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { _INTERNAL_resetSequenceNumber, getSequenceAttribute } from '../../../src/utils/sequence'; + +describe('getSequenceAttribute', () => { + beforeEach(() => { + _INTERNAL_resetSequenceNumber(); + }); + + it('returns the correct attribute key', () => { + const attr = getSequenceAttribute(1000.001); + expect(attr.key).toBe('sentry.timestamp.sequence'); + }); + + it('returns an integer type attribute', () => { + const attr = getSequenceAttribute(1000.001); + expect(attr.value.type).toBe('integer'); + }); + + it('starts at 0', () => { + const attr = getSequenceAttribute(1000.001); + expect(attr.value.value).toBe(0); + }); + + it('increments by 1 for each call within the same millisecond', () => { + const first = getSequenceAttribute(1000.001); + const second = getSequenceAttribute(1000.001); + const third = getSequenceAttribute(1000.001); + + expect(first.value.value).toBe(0); + expect(second.value.value).toBe(1); + expect(third.value.value).toBe(2); + }); + + it('resets to 0 when the integer millisecond changes', () => { + // Same millisecond (1000001ms) + expect(getSequenceAttribute(1000.001).value.value).toBe(0); + expect(getSequenceAttribute(1000.001).value.value).toBe(1); + + // Different millisecond (1000002ms) + expect(getSequenceAttribute(1000.002).value.value).toBe(0); + expect(getSequenceAttribute(1000.002).value.value).toBe(1); + }); + + it('does not reset when the fractional part changes but integer millisecond stays the same', () => { + // 1000001.0ms and 1000001.9ms both floor to 1000001ms + expect(getSequenceAttribute(1000.001).value.value).toBe(0); + expect(getSequenceAttribute(1000.0019).value.value).toBe(1); + }); + + it('resets via _INTERNAL_resetSequenceNumber', () => { + expect(getSequenceAttribute(1000.001).value.value).toBe(0); + expect(getSequenceAttribute(1000.001).value.value).toBe(1); + + _INTERNAL_resetSequenceNumber(); + + expect(getSequenceAttribute(1000.001).value.value).toBe(0); + }); + + it('resets to 0 after _INTERNAL_resetSequenceNumber even with same timestamp', () => { + getSequenceAttribute(1000.001); + getSequenceAttribute(1000.001); + + _INTERNAL_resetSequenceNumber(); + + // After reset, _previousTimestampMs is undefined, so it should start at 0 + const attr = getSequenceAttribute(1000.001); + expect(attr.value.value).toBe(0); + }); + + it('shares sequence across interleaved calls (monotonically increasing within same ms)', () => { + // Simulate interleaved log and metric captures at the same timestamp + const logSeq = getSequenceAttribute(1000.001); + const metricSeq = getSequenceAttribute(1000.001); + const logSeq2 = getSequenceAttribute(1000.001); + + expect(logSeq.value.value).toBe(0); + expect(metricSeq.value.value).toBe(1); + expect(logSeq2.value.value).toBe(2); + }); +}); From ffa2dc9b6d9edcc04492df3871ebd0d6c6bac61c Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 11:00:09 -0500 Subject: [PATCH 03/10] refactor(core): Rename sequence util to timestampSequence Co-Authored-By: Claude Opus 4.6 --- packages/core/src/logs/internal.ts | 2 +- packages/core/src/metrics/internal.ts | 2 +- packages/core/src/utils/{sequence.ts => timestampSequence.ts} | 0 packages/core/test/lib/logs/internal.test.ts | 2 +- packages/core/test/lib/metrics/internal.test.ts | 2 +- .../lib/utils/{sequence.test.ts => timestampSequence.test.ts} | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename packages/core/src/utils/{sequence.ts => timestampSequence.ts} (100%) rename packages/core/test/lib/utils/{sequence.test.ts => timestampSequence.test.ts} (98%) diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 41d104f69fb1..32c3e0d56340 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -8,7 +8,7 @@ import type { Log, SerializedLog } from '../types-hoist/log'; import { consoleSandbox, debug } from '../utils/debug-logger'; import { isParameterizedString } from '../utils/is'; import { getCombinedScopeData } from '../utils/scopeData'; -import { getSequenceAttribute } from '../utils/sequence'; +import { getSequenceAttribute } from '../utils/timestampSequence'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; import { _getTraceInfoFromScope } from '../utils/trace-info'; diff --git a/packages/core/src/metrics/internal.ts b/packages/core/src/metrics/internal.ts index 6748ae760cb7..c2dc9f04c645 100644 --- a/packages/core/src/metrics/internal.ts +++ b/packages/core/src/metrics/internal.ts @@ -9,7 +9,7 @@ import type { Metric, SerializedMetric } from '../types-hoist/metric'; import type { User } from '../types-hoist/user'; import { debug } from '../utils/debug-logger'; import { getCombinedScopeData } from '../utils/scopeData'; -import { getSequenceAttribute } from '../utils/sequence'; +import { getSequenceAttribute } from '../utils/timestampSequence'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; import { _getTraceInfoFromScope } from '../utils/trace-info'; diff --git a/packages/core/src/utils/sequence.ts b/packages/core/src/utils/timestampSequence.ts similarity index 100% rename from packages/core/src/utils/sequence.ts rename to packages/core/src/utils/timestampSequence.ts diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 25e941ab6177..743b42894962 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -7,7 +7,7 @@ import { } from '../../../src/logs/internal'; import type { Log } from '../../../src/types-hoist/log'; import * as loggerModule from '../../../src/utils/debug-logger'; -import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/sequence'; +import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/timestampSequence'; import * as timeModule from '../../../src/utils/time'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; diff --git a/packages/core/test/lib/metrics/internal.test.ts b/packages/core/test/lib/metrics/internal.test.ts index 0b70176a2785..a598f323067d 100644 --- a/packages/core/test/lib/metrics/internal.test.ts +++ b/packages/core/test/lib/metrics/internal.test.ts @@ -7,7 +7,7 @@ import { } from '../../../src/metrics/internal'; import type { Metric } from '../../../src/types-hoist/metric'; import * as loggerModule from '../../../src/utils/debug-logger'; -import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/sequence'; +import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/timestampSequence'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; diff --git a/packages/core/test/lib/utils/sequence.test.ts b/packages/core/test/lib/utils/timestampSequence.test.ts similarity index 98% rename from packages/core/test/lib/utils/sequence.test.ts rename to packages/core/test/lib/utils/timestampSequence.test.ts index 733a809a9791..0608bf296455 100644 --- a/packages/core/test/lib/utils/sequence.test.ts +++ b/packages/core/test/lib/utils/timestampSequence.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { _INTERNAL_resetSequenceNumber, getSequenceAttribute } from '../../../src/utils/sequence'; +import { _INTERNAL_resetSequenceNumber, getSequenceAttribute } from '../../../src/utils/timestampSequence'; describe('getSequenceAttribute', () => { beforeEach(() => { From 1bd3da31de3a7e89a08eb1b04af8e36e0ca9f640 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 11:03:51 -0500 Subject: [PATCH 04/10] chore: reformat --- packages/core/src/logs/internal.ts | 1 - packages/core/test/lib/logs/internal.test.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 32c3e0d56340..40648b8b6065 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -220,4 +220,3 @@ function _getBufferMap(): WeakMap> { // The reference to the Client <> LogBuffer map is stored on the carrier to ensure it's always the same return getGlobalSingleton('clientToLogBufferMap', () => new WeakMap>()); } - diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 743b42894962..0a16c3880b99 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -1,10 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { fmt, Scope } from '../../../src'; -import { - _INTERNAL_captureLog, - _INTERNAL_flushLogsBuffer, - _INTERNAL_getLogBuffer, -} from '../../../src/logs/internal'; +import { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_getLogBuffer } from '../../../src/logs/internal'; import type { Log } from '../../../src/types-hoist/log'; import * as loggerModule from '../../../src/utils/debug-logger'; import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/timestampSequence'; From edfee19e396fe59eb726e74055643468aba01e0b Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 11:25:35 -0500 Subject: [PATCH 05/10] chore: Bump size limits for affected bundles Co-Authored-By: Claude Opus 4.6 --- .size-limit.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index f4bf45b47b40..0b9cd2b00cb2 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -124,7 +124,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'logger'), gzip: true, - limit: '27 KB', + limit: '28 KB', }, { name: '@sentry/browser (incl. Metrics & Logs)', @@ -255,21 +255,21 @@ module.exports = [ path: createCDNPath('bundle.tracing.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '131 KB', + limit: '132 KB', }, { name: 'CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed', path: createCDNPath('bundle.replay.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '209 KB', + limit: '210 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay) - uncompressed', path: createCDNPath('bundle.tracing.replay.min.js'), gzip: false, brotli: false, - limit: '245 KB', + limit: '246 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed', From 05f3d2f3f9052164f737f7a7a908a99317290a78 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 12:07:01 -0500 Subject: [PATCH 06/10] fix: lint --- packages/core/src/logs/internal.ts | 2 +- packages/core/src/metrics/internal.ts | 2 +- packages/core/test/lib/logs/internal.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 40648b8b6065..097ffbb6906e 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -8,9 +8,9 @@ import type { Log, SerializedLog } from '../types-hoist/log'; import { consoleSandbox, debug } from '../utils/debug-logger'; import { isParameterizedString } from '../utils/is'; import { getCombinedScopeData } from '../utils/scopeData'; -import { getSequenceAttribute } from '../utils/timestampSequence'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; +import { getSequenceAttribute } from '../utils/timestampSequence'; import { _getTraceInfoFromScope } from '../utils/trace-info'; import { SEVERITY_TEXT_TO_SEVERITY_NUMBER } from './constants'; import { createLogEnvelope } from './envelope'; diff --git a/packages/core/src/metrics/internal.ts b/packages/core/src/metrics/internal.ts index c2dc9f04c645..0545414654ef 100644 --- a/packages/core/src/metrics/internal.ts +++ b/packages/core/src/metrics/internal.ts @@ -9,9 +9,9 @@ import type { Metric, SerializedMetric } from '../types-hoist/metric'; import type { User } from '../types-hoist/user'; import { debug } from '../utils/debug-logger'; import { getCombinedScopeData } from '../utils/scopeData'; -import { getSequenceAttribute } from '../utils/timestampSequence'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; +import { getSequenceAttribute } from '../utils/timestampSequence'; import { _getTraceInfoFromScope } from '../utils/trace-info'; import { createMetricEnvelope } from './envelope'; diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 0a16c3880b99..2f964eb09c20 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -3,8 +3,8 @@ import { fmt, Scope } from '../../../src'; import { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_getLogBuffer } from '../../../src/logs/internal'; import type { Log } from '../../../src/types-hoist/log'; import * as loggerModule from '../../../src/utils/debug-logger'; -import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/timestampSequence'; import * as timeModule from '../../../src/utils/time'; +import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/timestampSequence'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; From 8ba57b4b935caac07ad3f7a39cd70dde88bc1c5a Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 15:27:14 -0500 Subject: [PATCH 07/10] fix: Add sentry.timestamp.sequence attribute to test assertions Co-Authored-By: Claude Opus 4.6 --- .../suites/public-api/logger/integration/test.ts | 15 +++++++++++++++ .../public-api/logger/scopeAttributes/test.ts | 5 +++++ .../suites/public-api/logger/simple/test.ts | 12 ++++++++++++ .../suites/public-api/metrics/simple/test.ts | 9 +++++++++ .../public-api/metrics/server-address/test.ts | 4 ++++ .../suites/light-mode/logs/test.ts | 2 ++ .../suites/public-api/logs/test.ts | 12 ++++++++++++ .../public-api/metrics/server-address/test.ts | 1 + packages/core/test/lib/logs/internal.test.ts | 8 ++++++++ packages/core/test/lib/metrics/public-api.test.ts | 12 ++++++++++++ 10 files changed, 80 insertions(+) diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts index 40c2d18d29bd..7315e8cf4f36 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts @@ -34,6 +34,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.trace {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -49,6 +50,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.debug {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -64,6 +66,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.log {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -79,6 +82,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.info {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -94,6 +98,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.warn {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -109,6 +114,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.error {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -124,6 +130,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -136,6 +143,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Object: {}', type: 'string' }, 'sentry.message.parameter.0': { value: '{"key":"value","nested":{"prop":123}}', type: 'string' }, }, @@ -150,6 +158,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Array: {}', type: 'string' }, 'sentry.message.parameter.0': { value: '[1,2,3,"string"]', type: 'string' }, }, @@ -164,6 +173,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Mixed: {} {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 'prefix', type: 'string' }, 'sentry.message.parameter.1': { value: '{"obj":true}', type: 'string' }, @@ -181,6 +191,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -193,6 +204,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -205,6 +217,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -217,6 +230,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'first {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 0, type: 'integer' }, 'sentry.message.parameter.1': { value: 1, type: 'integer' }, @@ -233,6 +247,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'hello {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: true, type: 'boolean' }, 'sentry.message.parameter.1': { value: 'null', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts index c02a110046dd..4d7970945436 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts @@ -32,6 +32,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, log_attr: { value: 'log_attr_1', type: 'string' }, }, }, @@ -44,6 +45,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, global_scope_attr: { value: true, type: 'boolean' }, log_attr: { value: 'log_attr_2', type: 'string' }, }, @@ -57,6 +59,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, @@ -71,6 +74,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, @@ -86,6 +90,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts index aa2159d13bc1..db6d174820d7 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts @@ -33,6 +33,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -44,6 +45,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -55,6 +57,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -66,6 +69,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -77,6 +81,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -88,6 +93,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -99,6 +105,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'trace', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -115,6 +122,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'debug', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -131,6 +139,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'info', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -147,6 +156,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'warn', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -163,6 +173,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'error', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -179,6 +190,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'fatal', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts b/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts index f9722fc0bec8..66f44878ac86 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts @@ -40,6 +40,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -55,6 +56,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -70,6 +72,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -85,6 +88,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -102,6 +106,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -144,6 +149,10 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'user.email': { type: 'string', value: 'test@example.com', diff --git a/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts b/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts index 5ee5b0954e59..013bf552d772 100644 --- a/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts @@ -36,6 +36,10 @@ it('should add server.address attribute to metrics when serverName is set', asyn type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: 'mi-servidor.com', diff --git a/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts b/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts index f1dfde5ecdf8..25096f1be7e5 100644 --- a/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts +++ b/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts @@ -18,6 +18,7 @@ describe('light mode logs', () => { 'sentry.release': { type: 'string', value: '1.0.0' }, 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node-light' }, 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, 'server.address': { type: 'string', value: expect.any(String) }, }, body: 'test info log', @@ -31,6 +32,7 @@ describe('light mode logs', () => { 'sentry.release': { type: 'string', value: '1.0.0' }, 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node-light' }, 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, 'server.address': { type: 'string', value: expect.any(String) }, }, body: 'test error log', diff --git a/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts b/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts index 6f19a7152eae..53c80a6194c5 100644 --- a/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts +++ b/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts @@ -26,6 +26,10 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: expect.any(String), @@ -63,6 +67,10 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: expect.any(String), @@ -100,6 +108,10 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: expect.any(String), diff --git a/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts b/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts index 1ee4eda2de3e..048513da3c19 100644 --- a/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts @@ -24,6 +24,7 @@ describe('metrics server.address', () => { 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, ], diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 2f964eb09c20..360485f5ca84 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -1170,6 +1170,8 @@ describe('_INTERNAL_captureLog', () => { describe('sentry.timestamp.sequence', () => { it('increments the sequence number across consecutive logs', () => { + vi.spyOn(timeModule, 'timestampInSeconds').mockReturnValue(1000.001); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); const client = new TestClient(options); const scope = new Scope(); @@ -1183,9 +1185,13 @@ describe('_INTERNAL_captureLog', () => { expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); expect(buffer?.[2]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 2, type: 'integer' }); + + vi.restoreAllMocks(); }); it('does not increment the sequence number for dropped logs', () => { + vi.spyOn(timeModule, 'timestampInSeconds').mockReturnValue(1000.001); + const beforeSendLog = vi.fn().mockImplementation(log => { if (log.message === 'drop me') { return null; @@ -1206,6 +1212,8 @@ describe('_INTERNAL_captureLog', () => { expect(buffer).toHaveLength(2); expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); + + vi.restoreAllMocks(); }); it('produces monotonically increasing sequence numbers within the same millisecond', () => { diff --git a/packages/core/test/lib/metrics/public-api.test.ts b/packages/core/test/lib/metrics/public-api.test.ts index df8ff49c5553..cc8052cb0152 100644 --- a/packages/core/test/lib/metrics/public-api.test.ts +++ b/packages/core/test/lib/metrics/public-api.test.ts @@ -77,6 +77,10 @@ describe('Metrics Public API', () => { value: 'GET', type: 'string', }, + 'sentry.timestamp.sequence': { + value: expect.any(Number), + type: 'integer', + }, status: { value: 200, type: 'integer', @@ -194,6 +198,10 @@ describe('Metrics Public API', () => { value: 'websocket', type: 'string', }, + 'sentry.timestamp.sequence': { + value: expect.any(Number), + type: 'integer', + }, }, }), ); @@ -284,6 +292,10 @@ describe('Metrics Public API', () => { value: 'async', type: 'string', }, + 'sentry.timestamp.sequence': { + value: expect.any(Number), + type: 'integer', + }, }, }), ); From 1cc231b03a1a1f38137f7e9a7d2ead2f7c4b6b2d Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 15:33:00 -0500 Subject: [PATCH 08/10] feat(core): Skip sending sentry.timestamp.sequence when value is 0 Sequence 0 is the default and doesn't provide tie-breaking value, so omitting it saves bytes on the vast majority of telemetry items. Co-Authored-By: Claude Opus 4.6 --- .../public-api/logger/integration/test.ts | 15 ----- .../public-api/logger/scopeAttributes/test.ts | 5 -- .../suites/public-api/logger/simple/test.ts | 12 ---- .../suites/public-api/metrics/simple/test.ts | 9 --- .../public-api/metrics/server-address/test.ts | 4 -- .../suites/light-mode/logs/test.ts | 2 - .../suites/public-api/logs/test.ts | 12 ---- .../public-api/metrics/server-address/test.ts | 1 - packages/core/src/logs/internal.ts | 2 +- packages/core/src/metrics/internal.ts | 2 +- packages/core/src/utils/timestampSequence.ts | 16 +++-- packages/core/test/lib/logs/internal.test.ts | 64 ++++--------------- .../core/test/lib/metrics/internal.test.ts | 8 +-- .../core/test/lib/metrics/public-api.test.ts | 18 ++---- .../test/lib/utils/timestampSequence.test.ts | 54 ++++++++-------- 15 files changed, 63 insertions(+), 161 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts index 7315e8cf4f36..40c2d18d29bd 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts @@ -34,7 +34,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.trace {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -50,7 +49,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.debug {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -66,7 +64,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.log {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -82,7 +79,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.info {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -98,7 +94,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.warn {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -114,7 +109,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.error {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -130,7 +124,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -143,7 +136,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Object: {}', type: 'string' }, 'sentry.message.parameter.0': { value: '{"key":"value","nested":{"prop":123}}', type: 'string' }, }, @@ -158,7 +150,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Array: {}', type: 'string' }, 'sentry.message.parameter.0': { value: '[1,2,3,"string"]', type: 'string' }, }, @@ -173,7 +164,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Mixed: {} {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 'prefix', type: 'string' }, 'sentry.message.parameter.1': { value: '{"obj":true}', type: 'string' }, @@ -191,7 +181,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -204,7 +193,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -217,7 +205,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -230,7 +217,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'first {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 0, type: 'integer' }, 'sentry.message.parameter.1': { value: 1, type: 'integer' }, @@ -247,7 +233,6 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'hello {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: true, type: 'boolean' }, 'sentry.message.parameter.1': { value: 'null', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts index 4d7970945436..c02a110046dd 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts @@ -32,7 +32,6 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, log_attr: { value: 'log_attr_1', type: 'string' }, }, }, @@ -45,7 +44,6 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, global_scope_attr: { value: true, type: 'boolean' }, log_attr: { value: 'log_attr_2', type: 'string' }, }, @@ -59,7 +57,6 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, @@ -74,7 +71,6 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, @@ -90,7 +86,6 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts index db6d174820d7..aa2159d13bc1 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts @@ -33,7 +33,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -45,7 +44,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -57,7 +55,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -69,7 +66,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -81,7 +77,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -93,7 +88,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -105,7 +99,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'trace', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -122,7 +115,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'debug', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -139,7 +131,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'info', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -156,7 +147,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'warn', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -173,7 +163,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'error', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -190,7 +179,6 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'fatal', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts b/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts index 66f44878ac86..f9722fc0bec8 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts @@ -40,7 +40,6 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -56,7 +55,6 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -72,7 +70,6 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -88,7 +85,6 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -106,7 +102,6 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -149,10 +144,6 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) type: 'string', value: expect.any(String), }, - 'sentry.timestamp.sequence': { - type: 'integer', - value: expect.any(Number), - }, 'user.email': { type: 'string', value: 'test@example.com', diff --git a/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts b/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts index 013bf552d772..5ee5b0954e59 100644 --- a/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts @@ -36,10 +36,6 @@ it('should add server.address attribute to metrics when serverName is set', asyn type: 'string', value: expect.any(String), }, - 'sentry.timestamp.sequence': { - type: 'integer', - value: expect.any(Number), - }, 'server.address': { type: 'string', value: 'mi-servidor.com', diff --git a/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts b/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts index 25096f1be7e5..f1dfde5ecdf8 100644 --- a/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts +++ b/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts @@ -18,7 +18,6 @@ describe('light mode logs', () => { 'sentry.release': { type: 'string', value: '1.0.0' }, 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node-light' }, 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, - 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, 'server.address': { type: 'string', value: expect.any(String) }, }, body: 'test info log', @@ -32,7 +31,6 @@ describe('light mode logs', () => { 'sentry.release': { type: 'string', value: '1.0.0' }, 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node-light' }, 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, - 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, 'server.address': { type: 'string', value: expect.any(String) }, }, body: 'test error log', diff --git a/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts b/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts index 53c80a6194c5..6f19a7152eae 100644 --- a/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts +++ b/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts @@ -26,10 +26,6 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, - 'sentry.timestamp.sequence': { - type: 'integer', - value: expect.any(Number), - }, 'server.address': { type: 'string', value: expect.any(String), @@ -67,10 +63,6 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, - 'sentry.timestamp.sequence': { - type: 'integer', - value: expect.any(Number), - }, 'server.address': { type: 'string', value: expect.any(String), @@ -108,10 +100,6 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, - 'sentry.timestamp.sequence': { - type: 'integer', - value: expect.any(Number), - }, 'server.address': { type: 'string', value: expect.any(String), diff --git a/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts b/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts index 048513da3c19..1ee4eda2de3e 100644 --- a/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts @@ -24,7 +24,6 @@ describe('metrics server.address', () => { 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, ], diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 097ffbb6906e..6b40d3c3ab05 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -167,7 +167,7 @@ export function _INTERNAL_captureLog( attributes: { ...serializeAttributes(scopeAttributes), ...serializeAttributes(logAttributes, true), - [sequenceAttr.key]: sequenceAttr.value, + ...(sequenceAttr && { [sequenceAttr.key]: sequenceAttr.value }), }, }; diff --git a/packages/core/src/metrics/internal.ts b/packages/core/src/metrics/internal.ts index 0545414654ef..26712b401e76 100644 --- a/packages/core/src/metrics/internal.ts +++ b/packages/core/src/metrics/internal.ts @@ -150,7 +150,7 @@ function _buildSerializedMetric( attributes: { ...serializeAttributes(scopeAttributes), ...serializeAttributes(metric.attributes, 'skip-undefined'), - [sequenceAttr.key]: sequenceAttr.value, + ...(sequenceAttr && { [sequenceAttr.key]: sequenceAttr.value }), }, }; } diff --git a/packages/core/src/utils/timestampSequence.ts b/packages/core/src/utils/timestampSequence.ts index d2755d7a0724..d03e57d262cc 100644 --- a/packages/core/src/utils/timestampSequence.ts +++ b/packages/core/src/utils/timestampSequence.ts @@ -12,10 +12,12 @@ let _previousTimestampMs: number | undefined; * * @param timestampInSeconds - The timestamp of the telemetry item in seconds. */ -export function getSequenceAttribute(timestampInSeconds: number): { - key: string; - value: { value: number; type: 'integer' }; -} { +export function getSequenceAttribute(timestampInSeconds: number): + | { + key: string; + value: { value: number; type: 'integer' }; + } + | undefined { const nowMs = Math.floor(timestampInSeconds * 1000); if (_previousTimestampMs !== undefined && nowMs !== _previousTimestampMs) { @@ -26,6 +28,12 @@ export function getSequenceAttribute(timestampInSeconds: number): { _sequenceNumber++; _previousTimestampMs = nowMs; + // We skip sending 0 sequences to save some bytes + // sequence number is only needed for multiple equal timestamps in a row + if (value === 0) { + return undefined; + } + return { key: SEQUENCE_ATTR_KEY, value: { value, type: 'integer' }, diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 360485f5ca84..d0af56f682f6 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -28,9 +28,7 @@ describe('_INTERNAL_captureLog', () => { timestamp: expect.any(Number), trace_id: expect.any(String), severity_number: 9, - attributes: { - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, - }, + attributes: {}, }), ); }); @@ -93,7 +91,6 @@ describe('_INTERNAL_captureLog', () => { value: 'test', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -125,7 +122,6 @@ describe('_INTERNAL_captureLog', () => { value: '7.0.0', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -177,7 +173,6 @@ describe('_INTERNAL_captureLog', () => { value: 'auth', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -229,7 +224,6 @@ describe('_INTERNAL_captureLog', () => { type: 'boolean', value: true, }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -289,7 +283,6 @@ describe('_INTERNAL_captureLog', () => { value: 'Sentry', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -302,9 +295,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'debug', message: fmt`User logged in` }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({ - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, - }); + expect(logAttributes).toEqual({}); }); it('processes logs through beforeSendLog when provided', () => { @@ -367,7 +358,6 @@ describe('_INTERNAL_captureLog', () => { unit: 'gigabytes', type: 'integer', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }), ); @@ -453,7 +443,6 @@ describe('_INTERNAL_captureLog', () => { value: 'sampled-replay-id', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -479,9 +468,7 @@ describe('_INTERNAL_captureLog', () => { expect(mockReplayIntegration.getReplayId).toHaveBeenCalledWith(true); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({ - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, - }); + expect(logAttributes).toEqual({}); }); it('includes replay ID for buffer mode sessions', () => { @@ -515,7 +502,6 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -531,9 +517,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log without replay' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({ - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, - }); + expect(logAttributes).toEqual({}); }); it('combines replay ID with other log attributes', () => { @@ -586,7 +570,6 @@ describe('_INTERNAL_captureLog', () => { value: 'test-replay-id', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -611,9 +594,6 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: `test log with replay returning ${returnValue}` }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({ - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, - }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); }); }); @@ -647,7 +627,6 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -676,7 +655,6 @@ describe('_INTERNAL_captureLog', () => { value: 'session-replay-id', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -706,7 +684,6 @@ describe('_INTERNAL_captureLog', () => { value: 'stopped-replay-id', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -732,9 +709,7 @@ describe('_INTERNAL_captureLog', () => { expect(mockReplayIntegration.getRecordingMode).not.toHaveBeenCalled(); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({ - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, - }); + expect(logAttributes).toEqual({}); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); expect(logAttributes).not.toHaveProperty('sentry.internal.replay_is_buffering'); }); @@ -751,9 +726,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log without replay integration' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({ - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, - }); + expect(logAttributes).toEqual({}); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -812,7 +785,6 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -848,7 +820,6 @@ describe('_INTERNAL_captureLog', () => { value: 'testuser', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -874,7 +845,6 @@ describe('_INTERNAL_captureLog', () => { value: '123', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -905,7 +875,6 @@ describe('_INTERNAL_captureLog', () => { value: 'testuser', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -923,9 +892,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log with empty user' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({ - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, - }); + expect(logAttributes).toEqual({}); }); it('combines user data with other log attributes', () => { @@ -979,7 +946,6 @@ describe('_INTERNAL_captureLog', () => { value: 'test', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1010,7 +976,6 @@ describe('_INTERNAL_captureLog', () => { value: 'user@example.com', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1054,7 +1019,6 @@ describe('_INTERNAL_captureLog', () => { value: 'user@example.com', // Only added because user.email wasn't already present type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1103,7 +1067,6 @@ describe('_INTERNAL_captureLog', () => { value: 'scope-user', // Added from scope because not present type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -1164,7 +1127,6 @@ describe('_INTERNAL_captureLog', () => { value: '7.0.0', type: 'string', }, - 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1182,7 +1144,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'third' }, scope); const buffer = _INTERNAL_getLogBuffer(client); - expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); expect(buffer?.[2]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 2, type: 'integer' }); @@ -1210,7 +1172,7 @@ describe('_INTERNAL_captureLog', () => { const buffer = _INTERNAL_getLogBuffer(client); expect(buffer).toHaveLength(2); - expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); vi.restoreAllMocks(); @@ -1232,10 +1194,12 @@ describe('_INTERNAL_captureLog', () => { const buffer = _INTERNAL_getLogBuffer(client)!; expect(buffer).toHaveLength(count); + // First item has no sequence attribute (sequence 0 is omitted) + expect(buffer[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); + for (let i = 1; i < count; i++) { - const prev = (buffer[i - 1]?.attributes?.['sentry.timestamp.sequence'] as { value: number }).value; const curr = (buffer[i]?.attributes?.['sentry.timestamp.sequence'] as { value: number }).value; - expect(curr).toBe(prev + 1); + expect(curr).toBe(i); } vi.restoreAllMocks(); @@ -1258,7 +1222,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'after reset' }, scope2); const buffer2 = _INTERNAL_getLogBuffer(client2); - expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); }); }); }); diff --git a/packages/core/test/lib/metrics/internal.test.ts b/packages/core/test/lib/metrics/internal.test.ts index a598f323067d..e4ab9839f06a 100644 --- a/packages/core/test/lib/metrics/internal.test.ts +++ b/packages/core/test/lib/metrics/internal.test.ts @@ -12,7 +12,8 @@ import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; -const SEQUENCE_ATTR = { 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' } }; +// Sequence 0 is omitted to save bytes, so for single-metric tests the attribute is absent. +const SEQUENCE_ATTR = {}; describe('_INTERNAL_captureMetric', () => { beforeEach(() => { @@ -539,7 +540,6 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope }); const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual({ ...SEQUENCE_ATTR }); expect(metricAttributes).not.toHaveProperty('sentry.replay_id'); }); }); @@ -1090,7 +1090,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'third', value: 3 }, { scope }); const buffer = _INTERNAL_getMetricBuffer(client); - expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); expect(buffer?.[2]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 2, type: 'integer' }); }); @@ -1112,7 +1112,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'after reset', value: 2 }, { scope: scope2 }); const buffer2 = _INTERNAL_getMetricBuffer(client2); - expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); + expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); }); }); }); diff --git a/packages/core/test/lib/metrics/public-api.test.ts b/packages/core/test/lib/metrics/public-api.test.ts index cc8052cb0152..9fb579494e2a 100644 --- a/packages/core/test/lib/metrics/public-api.test.ts +++ b/packages/core/test/lib/metrics/public-api.test.ts @@ -1,12 +1,16 @@ -import { describe, expect, it } from 'vitest'; +import { beforeEach, describe, expect, it } from 'vitest'; import { Scope } from '../../../src'; import { _INTERNAL_getMetricBuffer } from '../../../src/metrics/internal'; import { count, distribution, gauge } from '../../../src/metrics/public-api'; +import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/timestampSequence'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; describe('Metrics Public API', () => { + beforeEach(() => { + _INTERNAL_resetSequenceNumber(); + }); describe('count', () => { it('captures a counter metric with default value of 1', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); @@ -77,10 +81,6 @@ describe('Metrics Public API', () => { value: 'GET', type: 'string', }, - 'sentry.timestamp.sequence': { - value: expect.any(Number), - type: 'integer', - }, status: { value: 200, type: 'integer', @@ -198,10 +198,6 @@ describe('Metrics Public API', () => { value: 'websocket', type: 'string', }, - 'sentry.timestamp.sequence': { - value: expect.any(Number), - type: 'integer', - }, }, }), ); @@ -292,10 +288,6 @@ describe('Metrics Public API', () => { value: 'async', type: 'string', }, - 'sentry.timestamp.sequence': { - value: expect.any(Number), - type: 'integer', - }, }, }), ); diff --git a/packages/core/test/lib/utils/timestampSequence.test.ts b/packages/core/test/lib/utils/timestampSequence.test.ts index 0608bf296455..88c6ef7be831 100644 --- a/packages/core/test/lib/utils/timestampSequence.test.ts +++ b/packages/core/test/lib/utils/timestampSequence.test.ts @@ -6,19 +6,17 @@ describe('getSequenceAttribute', () => { _INTERNAL_resetSequenceNumber(); }); - it('returns the correct attribute key', () => { + it('returns undefined for the first call (sequence 0)', () => { const attr = getSequenceAttribute(1000.001); - expect(attr.key).toBe('sentry.timestamp.sequence'); + expect(attr).toBeUndefined(); }); - it('returns an integer type attribute', () => { - const attr = getSequenceAttribute(1000.001); - expect(attr.value.type).toBe('integer'); - }); - - it('starts at 0', () => { + it('returns the correct attribute key and integer type for sequence > 0', () => { + getSequenceAttribute(1000.001); const attr = getSequenceAttribute(1000.001); - expect(attr.value.value).toBe(0); + expect(attr).toBeDefined(); + expect(attr!.key).toBe('sentry.timestamp.sequence'); + expect(attr!.value.type).toBe('integer'); }); it('increments by 1 for each call within the same millisecond', () => { @@ -26,34 +24,34 @@ describe('getSequenceAttribute', () => { const second = getSequenceAttribute(1000.001); const third = getSequenceAttribute(1000.001); - expect(first.value.value).toBe(0); - expect(second.value.value).toBe(1); - expect(third.value.value).toBe(2); + expect(first).toBeUndefined(); + expect(second!.value.value).toBe(1); + expect(third!.value.value).toBe(2); }); - it('resets to 0 when the integer millisecond changes', () => { + it('resets to 0 (undefined) when the integer millisecond changes', () => { // Same millisecond (1000001ms) - expect(getSequenceAttribute(1000.001).value.value).toBe(0); - expect(getSequenceAttribute(1000.001).value.value).toBe(1); + expect(getSequenceAttribute(1000.001)).toBeUndefined(); + expect(getSequenceAttribute(1000.001)!.value.value).toBe(1); - // Different millisecond (1000002ms) - expect(getSequenceAttribute(1000.002).value.value).toBe(0); - expect(getSequenceAttribute(1000.002).value.value).toBe(1); + // Different millisecond (1000002ms) - resets + expect(getSequenceAttribute(1000.002)).toBeUndefined(); + expect(getSequenceAttribute(1000.002)!.value.value).toBe(1); }); it('does not reset when the fractional part changes but integer millisecond stays the same', () => { // 1000001.0ms and 1000001.9ms both floor to 1000001ms - expect(getSequenceAttribute(1000.001).value.value).toBe(0); - expect(getSequenceAttribute(1000.0019).value.value).toBe(1); + expect(getSequenceAttribute(1000.001)).toBeUndefined(); + expect(getSequenceAttribute(1000.0019)!.value.value).toBe(1); }); it('resets via _INTERNAL_resetSequenceNumber', () => { - expect(getSequenceAttribute(1000.001).value.value).toBe(0); - expect(getSequenceAttribute(1000.001).value.value).toBe(1); + expect(getSequenceAttribute(1000.001)).toBeUndefined(); + expect(getSequenceAttribute(1000.001)!.value.value).toBe(1); _INTERNAL_resetSequenceNumber(); - expect(getSequenceAttribute(1000.001).value.value).toBe(0); + expect(getSequenceAttribute(1000.001)).toBeUndefined(); }); it('resets to 0 after _INTERNAL_resetSequenceNumber even with same timestamp', () => { @@ -62,9 +60,9 @@ describe('getSequenceAttribute', () => { _INTERNAL_resetSequenceNumber(); - // After reset, _previousTimestampMs is undefined, so it should start at 0 + // After reset, _previousTimestampMs is undefined, so it should start at 0 (undefined) const attr = getSequenceAttribute(1000.001); - expect(attr.value.value).toBe(0); + expect(attr).toBeUndefined(); }); it('shares sequence across interleaved calls (monotonically increasing within same ms)', () => { @@ -73,8 +71,8 @@ describe('getSequenceAttribute', () => { const metricSeq = getSequenceAttribute(1000.001); const logSeq2 = getSequenceAttribute(1000.001); - expect(logSeq.value.value).toBe(0); - expect(metricSeq.value.value).toBe(1); - expect(logSeq2.value.value).toBe(2); + expect(logSeq).toBeUndefined(); + expect(metricSeq!.value.value).toBe(1); + expect(logSeq2!.value.value).toBe(2); }); }); From 7b0ae8034d200fcf149bc391f53ad015b97e7a4c Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 15:41:39 -0500 Subject: [PATCH 09/10] revert: Remove skip-sequence-0 optimization, keep always sending attribute Co-Authored-By: Claude Opus 4.6 --- .../public-api/logger/integration/test.ts | 15 +++++ .../public-api/logger/scopeAttributes/test.ts | 5 ++ .../suites/public-api/logger/simple/test.ts | 12 ++++ .../suites/public-api/metrics/simple/test.ts | 9 +++ .../public-api/metrics/server-address/test.ts | 4 ++ .../suites/light-mode/logs/test.ts | 2 + .../suites/public-api/logs/test.ts | 12 ++++ .../public-api/metrics/server-address/test.ts | 1 + packages/core/src/logs/internal.ts | 2 +- packages/core/src/metrics/internal.ts | 2 +- packages/core/src/utils/timestampSequence.ts | 16 ++--- packages/core/test/lib/logs/internal.test.ts | 64 +++++++++++++++---- .../core/test/lib/metrics/internal.test.ts | 8 +-- .../core/test/lib/metrics/public-api.test.ts | 18 ++++-- .../test/lib/utils/timestampSequence.test.ts | 54 ++++++++-------- 15 files changed, 161 insertions(+), 63 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts index 40c2d18d29bd..7315e8cf4f36 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/test.ts @@ -34,6 +34,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.trace {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -49,6 +50,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.debug {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -64,6 +66,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.log {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -79,6 +82,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.info {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -94,6 +98,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.warn {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -109,6 +114,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'console.error {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 123, type: 'integer' }, 'sentry.message.parameter.1': { value: false, type: 'boolean' }, @@ -124,6 +130,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -136,6 +143,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Object: {}', type: 'string' }, 'sentry.message.parameter.0': { value: '{"key":"value","nested":{"prop":123}}', type: 'string' }, }, @@ -150,6 +158,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Array: {}', type: 'string' }, 'sentry.message.parameter.0': { value: '[1,2,3,"string"]', type: 'string' }, }, @@ -164,6 +173,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'Mixed: {} {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 'prefix', type: 'string' }, 'sentry.message.parameter.1': { value: '{"obj":true}', type: 'string' }, @@ -181,6 +191,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -193,6 +204,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -205,6 +217,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -217,6 +230,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'first {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: 0, type: 'integer' }, 'sentry.message.parameter.1': { value: 1, type: 'integer' }, @@ -233,6 +247,7 @@ sentryTest('should capture console object calls', async ({ getLocalTestUrl, page 'sentry.origin': { value: 'auto.log.console', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'hello {} {} {}', type: 'string' }, 'sentry.message.parameter.0': { value: true, type: 'boolean' }, 'sentry.message.parameter.1': { value: 'null', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts index c02a110046dd..4d7970945436 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/scopeAttributes/test.ts @@ -32,6 +32,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, log_attr: { value: 'log_attr_1', type: 'string' }, }, }, @@ -44,6 +45,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, global_scope_attr: { value: true, type: 'boolean' }, log_attr: { value: 'log_attr_2', type: 'string' }, }, @@ -57,6 +59,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, @@ -71,6 +74,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, @@ -86,6 +90,7 @@ sentryTest('captures logs with scope attributes', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 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' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts b/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts index aa2159d13bc1..db6d174820d7 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/logger/simple/test.ts @@ -33,6 +33,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -44,6 +45,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -55,6 +57,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -66,6 +69,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -77,6 +81,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -88,6 +93,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -99,6 +105,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'trace', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -115,6 +122,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'debug', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -131,6 +139,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'info', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -147,6 +156,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'warn', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -163,6 +173,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'error', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, @@ -179,6 +190,7 @@ sentryTest('should capture all logging methods', async ({ getLocalTestUrl, page attributes: { 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, 'sentry.message.template': { value: 'test %s %s %s %s', type: 'string' }, 'sentry.message.parameter.0': { value: 'fatal', type: 'string' }, 'sentry.message.parameter.1': { value: 'stringArg', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts b/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts index f9722fc0bec8..66f44878ac86 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/metrics/simple/test.ts @@ -40,6 +40,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -55,6 +56,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -70,6 +72,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -85,6 +88,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -102,6 +106,7 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, { @@ -144,6 +149,10 @@ sentryTest('should capture all metric types', async ({ getLocalTestUrl, page }) type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'user.email': { type: 'string', value: 'test@example.com', diff --git a/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts b/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts index 5ee5b0954e59..013bf552d772 100644 --- a/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/public-api/metrics/server-address/test.ts @@ -36,6 +36,10 @@ it('should add server.address attribute to metrics when serverName is set', asyn type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: 'mi-servidor.com', diff --git a/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts b/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts index f1dfde5ecdf8..25096f1be7e5 100644 --- a/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts +++ b/dev-packages/node-core-integration-tests/suites/light-mode/logs/test.ts @@ -18,6 +18,7 @@ describe('light mode logs', () => { 'sentry.release': { type: 'string', value: '1.0.0' }, 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node-light' }, 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, 'server.address': { type: 'string', value: expect.any(String) }, }, body: 'test info log', @@ -31,6 +32,7 @@ describe('light mode logs', () => { 'sentry.release': { type: 'string', value: '1.0.0' }, 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node-light' }, 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, 'server.address': { type: 'string', value: expect.any(String) }, }, body: 'test error log', diff --git a/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts b/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts index 6f19a7152eae..53c80a6194c5 100644 --- a/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts +++ b/dev-packages/node-core-integration-tests/suites/public-api/logs/test.ts @@ -26,6 +26,10 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: expect.any(String), @@ -63,6 +67,10 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: expect.any(String), @@ -100,6 +108,10 @@ describe('logger public API', () => { type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: expect.any(String), diff --git a/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts b/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts index 1ee4eda2de3e..048513da3c19 100644 --- a/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/metrics/server-address/test.ts @@ -24,6 +24,7 @@ describe('metrics server.address', () => { 'sentry.environment': { value: 'test', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }, ], diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 6b40d3c3ab05..097ffbb6906e 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -167,7 +167,7 @@ export function _INTERNAL_captureLog( attributes: { ...serializeAttributes(scopeAttributes), ...serializeAttributes(logAttributes, true), - ...(sequenceAttr && { [sequenceAttr.key]: sequenceAttr.value }), + [sequenceAttr.key]: sequenceAttr.value, }, }; diff --git a/packages/core/src/metrics/internal.ts b/packages/core/src/metrics/internal.ts index 26712b401e76..0545414654ef 100644 --- a/packages/core/src/metrics/internal.ts +++ b/packages/core/src/metrics/internal.ts @@ -150,7 +150,7 @@ function _buildSerializedMetric( attributes: { ...serializeAttributes(scopeAttributes), ...serializeAttributes(metric.attributes, 'skip-undefined'), - ...(sequenceAttr && { [sequenceAttr.key]: sequenceAttr.value }), + [sequenceAttr.key]: sequenceAttr.value, }, }; } diff --git a/packages/core/src/utils/timestampSequence.ts b/packages/core/src/utils/timestampSequence.ts index d03e57d262cc..d2755d7a0724 100644 --- a/packages/core/src/utils/timestampSequence.ts +++ b/packages/core/src/utils/timestampSequence.ts @@ -12,12 +12,10 @@ let _previousTimestampMs: number | undefined; * * @param timestampInSeconds - The timestamp of the telemetry item in seconds. */ -export function getSequenceAttribute(timestampInSeconds: number): - | { - key: string; - value: { value: number; type: 'integer' }; - } - | undefined { +export function getSequenceAttribute(timestampInSeconds: number): { + key: string; + value: { value: number; type: 'integer' }; +} { const nowMs = Math.floor(timestampInSeconds * 1000); if (_previousTimestampMs !== undefined && nowMs !== _previousTimestampMs) { @@ -28,12 +26,6 @@ export function getSequenceAttribute(timestampInSeconds: number): _sequenceNumber++; _previousTimestampMs = nowMs; - // We skip sending 0 sequences to save some bytes - // sequence number is only needed for multiple equal timestamps in a row - if (value === 0) { - return undefined; - } - return { key: SEQUENCE_ATTR_KEY, value: { value, type: 'integer' }, diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index d0af56f682f6..360485f5ca84 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -28,7 +28,9 @@ describe('_INTERNAL_captureLog', () => { timestamp: expect.any(Number), trace_id: expect.any(String), severity_number: 9, - attributes: {}, + attributes: { + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, + }, }), ); }); @@ -91,6 +93,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -122,6 +125,7 @@ describe('_INTERNAL_captureLog', () => { value: '7.0.0', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -173,6 +177,7 @@ describe('_INTERNAL_captureLog', () => { value: 'auth', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -224,6 +229,7 @@ describe('_INTERNAL_captureLog', () => { type: 'boolean', value: true, }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -283,6 +289,7 @@ describe('_INTERNAL_captureLog', () => { value: 'Sentry', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -295,7 +302,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'debug', message: fmt`User logged in` }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, + }); }); it('processes logs through beforeSendLog when provided', () => { @@ -358,6 +367,7 @@ describe('_INTERNAL_captureLog', () => { unit: 'gigabytes', type: 'integer', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }, }), ); @@ -443,6 +453,7 @@ describe('_INTERNAL_captureLog', () => { value: 'sampled-replay-id', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -468,7 +479,9 @@ describe('_INTERNAL_captureLog', () => { expect(mockReplayIntegration.getReplayId).toHaveBeenCalledWith(true); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, + }); }); it('includes replay ID for buffer mode sessions', () => { @@ -502,6 +515,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -517,7 +531,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log without replay' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, + }); }); it('combines replay ID with other log attributes', () => { @@ -570,6 +586,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test-replay-id', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -594,6 +611,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: `test log with replay returning ${returnValue}` }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; + expect(logAttributes).toEqual({ + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, + }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); }); }); @@ -627,6 +647,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -655,6 +676,7 @@ describe('_INTERNAL_captureLog', () => { value: 'session-replay-id', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -684,6 +706,7 @@ describe('_INTERNAL_captureLog', () => { value: 'stopped-replay-id', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -709,7 +732,9 @@ describe('_INTERNAL_captureLog', () => { expect(mockReplayIntegration.getRecordingMode).not.toHaveBeenCalled(); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, + }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); expect(logAttributes).not.toHaveProperty('sentry.internal.replay_is_buffering'); }); @@ -726,7 +751,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log without replay integration' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, + }); expect(logAttributes).not.toHaveProperty('sentry.replay_id'); expect(logAttributes).not.toHaveProperty('sentry._internal.replay_is_buffering'); }); @@ -785,6 +812,7 @@ describe('_INTERNAL_captureLog', () => { value: true, type: 'boolean', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -820,6 +848,7 @@ describe('_INTERNAL_captureLog', () => { value: 'testuser', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -845,6 +874,7 @@ describe('_INTERNAL_captureLog', () => { value: '123', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -875,6 +905,7 @@ describe('_INTERNAL_captureLog', () => { value: 'testuser', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -892,7 +923,9 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'test log with empty user' }, scope); const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; - expect(logAttributes).toEqual({}); + expect(logAttributes).toEqual({ + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, + }); }); it('combines user data with other log attributes', () => { @@ -946,6 +979,7 @@ describe('_INTERNAL_captureLog', () => { value: 'test', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -976,6 +1010,7 @@ describe('_INTERNAL_captureLog', () => { value: 'user@example.com', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1019,6 +1054,7 @@ describe('_INTERNAL_captureLog', () => { value: 'user@example.com', // Only added because user.email wasn't already present type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1067,6 +1103,7 @@ describe('_INTERNAL_captureLog', () => { value: 'scope-user', // Added from scope because not present type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); }); @@ -1127,6 +1164,7 @@ describe('_INTERNAL_captureLog', () => { value: '7.0.0', type: 'string', }, + 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' }, }); }); @@ -1144,7 +1182,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'third' }, scope); const buffer = _INTERNAL_getLogBuffer(client); - expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); expect(buffer?.[2]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 2, type: 'integer' }); @@ -1172,7 +1210,7 @@ describe('_INTERNAL_captureLog', () => { const buffer = _INTERNAL_getLogBuffer(client); expect(buffer).toHaveLength(2); - expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); vi.restoreAllMocks(); @@ -1194,12 +1232,10 @@ describe('_INTERNAL_captureLog', () => { const buffer = _INTERNAL_getLogBuffer(client)!; expect(buffer).toHaveLength(count); - // First item has no sequence attribute (sequence 0 is omitted) - expect(buffer[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); - for (let i = 1; i < count; i++) { + const prev = (buffer[i - 1]?.attributes?.['sentry.timestamp.sequence'] as { value: number }).value; const curr = (buffer[i]?.attributes?.['sentry.timestamp.sequence'] as { value: number }).value; - expect(curr).toBe(i); + expect(curr).toBe(prev + 1); } vi.restoreAllMocks(); @@ -1222,7 +1258,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_captureLog({ level: 'info', message: 'after reset' }, scope2); const buffer2 = _INTERNAL_getLogBuffer(client2); - expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); + expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); }); }); }); diff --git a/packages/core/test/lib/metrics/internal.test.ts b/packages/core/test/lib/metrics/internal.test.ts index e4ab9839f06a..a598f323067d 100644 --- a/packages/core/test/lib/metrics/internal.test.ts +++ b/packages/core/test/lib/metrics/internal.test.ts @@ -12,8 +12,7 @@ import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; -// Sequence 0 is omitted to save bytes, so for single-metric tests the attribute is absent. -const SEQUENCE_ATTR = {}; +const SEQUENCE_ATTR = { 'sentry.timestamp.sequence': { value: expect.any(Number), type: 'integer' } }; describe('_INTERNAL_captureMetric', () => { beforeEach(() => { @@ -540,6 +539,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope }); const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; + expect(metricAttributes).toEqual({ ...SEQUENCE_ATTR }); expect(metricAttributes).not.toHaveProperty('sentry.replay_id'); }); }); @@ -1090,7 +1090,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'third', value: 3 }, { scope }); const buffer = _INTERNAL_getMetricBuffer(client); - expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); + expect(buffer?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); expect(buffer?.[1]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 1, type: 'integer' }); expect(buffer?.[2]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 2, type: 'integer' }); }); @@ -1112,7 +1112,7 @@ describe('_INTERNAL_captureMetric', () => { _INTERNAL_captureMetric({ type: 'counter', name: 'after reset', value: 2 }, { scope: scope2 }); const buffer2 = _INTERNAL_getMetricBuffer(client2); - expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toBeUndefined(); + expect(buffer2?.[0]?.attributes?.['sentry.timestamp.sequence']).toEqual({ value: 0, type: 'integer' }); }); }); }); diff --git a/packages/core/test/lib/metrics/public-api.test.ts b/packages/core/test/lib/metrics/public-api.test.ts index 9fb579494e2a..cc8052cb0152 100644 --- a/packages/core/test/lib/metrics/public-api.test.ts +++ b/packages/core/test/lib/metrics/public-api.test.ts @@ -1,16 +1,12 @@ -import { beforeEach, describe, expect, it } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { Scope } from '../../../src'; import { _INTERNAL_getMetricBuffer } from '../../../src/metrics/internal'; import { count, distribution, gauge } from '../../../src/metrics/public-api'; -import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/timestampSequence'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; describe('Metrics Public API', () => { - beforeEach(() => { - _INTERNAL_resetSequenceNumber(); - }); describe('count', () => { it('captures a counter metric with default value of 1', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); @@ -81,6 +77,10 @@ describe('Metrics Public API', () => { value: 'GET', type: 'string', }, + 'sentry.timestamp.sequence': { + value: expect.any(Number), + type: 'integer', + }, status: { value: 200, type: 'integer', @@ -198,6 +198,10 @@ describe('Metrics Public API', () => { value: 'websocket', type: 'string', }, + 'sentry.timestamp.sequence': { + value: expect.any(Number), + type: 'integer', + }, }, }), ); @@ -288,6 +292,10 @@ describe('Metrics Public API', () => { value: 'async', type: 'string', }, + 'sentry.timestamp.sequence': { + value: expect.any(Number), + type: 'integer', + }, }, }), ); diff --git a/packages/core/test/lib/utils/timestampSequence.test.ts b/packages/core/test/lib/utils/timestampSequence.test.ts index 88c6ef7be831..0608bf296455 100644 --- a/packages/core/test/lib/utils/timestampSequence.test.ts +++ b/packages/core/test/lib/utils/timestampSequence.test.ts @@ -6,17 +6,19 @@ describe('getSequenceAttribute', () => { _INTERNAL_resetSequenceNumber(); }); - it('returns undefined for the first call (sequence 0)', () => { + it('returns the correct attribute key', () => { const attr = getSequenceAttribute(1000.001); - expect(attr).toBeUndefined(); + expect(attr.key).toBe('sentry.timestamp.sequence'); }); - it('returns the correct attribute key and integer type for sequence > 0', () => { - getSequenceAttribute(1000.001); + it('returns an integer type attribute', () => { + const attr = getSequenceAttribute(1000.001); + expect(attr.value.type).toBe('integer'); + }); + + it('starts at 0', () => { const attr = getSequenceAttribute(1000.001); - expect(attr).toBeDefined(); - expect(attr!.key).toBe('sentry.timestamp.sequence'); - expect(attr!.value.type).toBe('integer'); + expect(attr.value.value).toBe(0); }); it('increments by 1 for each call within the same millisecond', () => { @@ -24,34 +26,34 @@ describe('getSequenceAttribute', () => { const second = getSequenceAttribute(1000.001); const third = getSequenceAttribute(1000.001); - expect(first).toBeUndefined(); - expect(second!.value.value).toBe(1); - expect(third!.value.value).toBe(2); + expect(first.value.value).toBe(0); + expect(second.value.value).toBe(1); + expect(third.value.value).toBe(2); }); - it('resets to 0 (undefined) when the integer millisecond changes', () => { + it('resets to 0 when the integer millisecond changes', () => { // Same millisecond (1000001ms) - expect(getSequenceAttribute(1000.001)).toBeUndefined(); - expect(getSequenceAttribute(1000.001)!.value.value).toBe(1); + expect(getSequenceAttribute(1000.001).value.value).toBe(0); + expect(getSequenceAttribute(1000.001).value.value).toBe(1); - // Different millisecond (1000002ms) - resets - expect(getSequenceAttribute(1000.002)).toBeUndefined(); - expect(getSequenceAttribute(1000.002)!.value.value).toBe(1); + // Different millisecond (1000002ms) + expect(getSequenceAttribute(1000.002).value.value).toBe(0); + expect(getSequenceAttribute(1000.002).value.value).toBe(1); }); it('does not reset when the fractional part changes but integer millisecond stays the same', () => { // 1000001.0ms and 1000001.9ms both floor to 1000001ms - expect(getSequenceAttribute(1000.001)).toBeUndefined(); - expect(getSequenceAttribute(1000.0019)!.value.value).toBe(1); + expect(getSequenceAttribute(1000.001).value.value).toBe(0); + expect(getSequenceAttribute(1000.0019).value.value).toBe(1); }); it('resets via _INTERNAL_resetSequenceNumber', () => { - expect(getSequenceAttribute(1000.001)).toBeUndefined(); - expect(getSequenceAttribute(1000.001)!.value.value).toBe(1); + expect(getSequenceAttribute(1000.001).value.value).toBe(0); + expect(getSequenceAttribute(1000.001).value.value).toBe(1); _INTERNAL_resetSequenceNumber(); - expect(getSequenceAttribute(1000.001)).toBeUndefined(); + expect(getSequenceAttribute(1000.001).value.value).toBe(0); }); it('resets to 0 after _INTERNAL_resetSequenceNumber even with same timestamp', () => { @@ -60,9 +62,9 @@ describe('getSequenceAttribute', () => { _INTERNAL_resetSequenceNumber(); - // After reset, _previousTimestampMs is undefined, so it should start at 0 (undefined) + // After reset, _previousTimestampMs is undefined, so it should start at 0 const attr = getSequenceAttribute(1000.001); - expect(attr).toBeUndefined(); + expect(attr.value.value).toBe(0); }); it('shares sequence across interleaved calls (monotonically increasing within same ms)', () => { @@ -71,8 +73,8 @@ describe('getSequenceAttribute', () => { const metricSeq = getSequenceAttribute(1000.001); const logSeq2 = getSequenceAttribute(1000.001); - expect(logSeq).toBeUndefined(); - expect(metricSeq!.value.value).toBe(1); - expect(logSeq2!.value.value).toBe(2); + expect(logSeq.value.value).toBe(0); + expect(metricSeq.value.value).toBe(1); + expect(logSeq2.value.value).toBe(2); }); }); From ac732f754b188ffeed7dfb6c21068fa8e69ddb3a Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 6 Mar 2026 16:48:05 -0500 Subject: [PATCH 10/10] fix: Add sentry.timestamp.sequence to node integration logger test Co-Authored-By: Claude Opus 4.6 --- .../node-integration-tests/suites/public-api/logger/test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dev-packages/node-integration-tests/suites/public-api/logger/test.ts b/dev-packages/node-integration-tests/suites/public-api/logger/test.ts index f4be1cccc84b..6b9f43e738d2 100644 --- a/dev-packages/node-integration-tests/suites/public-api/logger/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/logger/test.ts @@ -19,6 +19,10 @@ const commonAttributes: SerializedLog['attributes'] = { type: 'string', value: expect.any(String), }, + 'sentry.timestamp.sequence': { + type: 'integer', + value: expect.any(Number), + }, 'server.address': { type: 'string', value: expect.any(String),