From bdcb9e116c0226a6db2f6da6e79965da01a0db45 Mon Sep 17 00:00:00 2001 From: Colin Chartier Date: Fri, 14 Feb 2025 10:33:04 -0500 Subject: [PATCH 01/31] initial work on .log --- packages/browser/src/index.ts | 3 + packages/core/src/exports.ts | 26 +++++ packages/core/src/index.ts | 3 + packages/core/src/ourlogs.ts | 111 ++++++++++++++++++++++ packages/core/src/types-hoist/envelope.ts | 9 +- packages/core/src/types-hoist/index.ts | 6 ++ packages/core/src/types-hoist/ourlogs.ts | 67 +++++++++++++ packages/core/src/utils-hoist/envelope.ts | 1 + 8 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/ourlogs.ts create mode 100644 packages/core/src/types-hoist/ourlogs.ts diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 63da52dfd30e..4449b637a34b 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -10,6 +10,9 @@ export { extraErrorDataIntegration, rewriteFramesIntegration, captureFeedback, + _experimentalLogError, + _experimentalLogInfo, + _experimentalLogWarning, } from '@sentry/core'; export { replayIntegration, getReplay } from '@sentry-internal/replay'; diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 4854ee86efb8..d1a15479a760 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -24,6 +24,7 @@ import { timestampInSeconds } from './utils-hoist/time'; import { GLOBAL_OBJ } from './utils-hoist/worldwide'; import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; import { parseEventHintOrCaptureContext } from './utils/prepareEvent'; +import { captureLog } from './ourlogs'; /** * Captures an exception event and sends it to Sentry. @@ -334,3 +335,28 @@ export function captureSession(end: boolean = false): void { // only send the update _sendSessionUpdate(); } + + +/** + * A utility to record a log with level 'INFO' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: sentry.logInfo`user ${username} just bought ${item}!` + */ +export const _experimentalLogInfo = captureLog.bind(null, 'info'); + +/** + * A utility to record a log with level 'ERROR' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: sentry.logError`user ${username} just bought ${item}!` + */ +export const _experimentalLogError = captureLog.bind(null, 'error'); + +/** + * A utility to record a log with level 'WARNING' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: sentry.logWarning`user ${username} just bought ${item}!` + */ +export const _experimentalLogWarning = captureLog.bind(null, 'warning'); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e0e9097bbc53..8e40617e2743 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -29,6 +29,9 @@ export { endSession, captureSession, addEventProcessor, + _experimentalLogError, + _experimentalLogInfo, + _experimentalLogWarning } from './exports'; export { getCurrentScope, diff --git a/packages/core/src/ourlogs.ts b/packages/core/src/ourlogs.ts new file mode 100644 index 000000000000..42066ea61ec4 --- /dev/null +++ b/packages/core/src/ourlogs.ts @@ -0,0 +1,111 @@ +import { getClient, getGlobalScope } from './currentScopes'; +import type { LogEnvelope, LogItem } from './types-hoist/envelope'; +import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/ourlogs'; +import { createEnvelope, dsnToString } from './utils-hoist'; + +/** + * Creates envelope item for a single log + */ +export function createLogEnvelopeItem(log: Log): LogItem { + const headers: LogItem[0] = { + type: 'otel_log', + }; + + return [headers, log]; +} + +/** + * Records a log and sends it to sentry. + * + * Logs represent a message (and optionally some structured data) which provide context for a trace or error. + * Ex: sentry.addLog({level: 'warning', message: `user ${user} just bought ${item}`, attributes: {user, item}} + * + * @params log - the log object which will be sent + */ +function addLog(log: Log): void { + const client = getClient(); + + if (!client) { + return; + } + + // if (!client.getOptions()._experiments?.logSupport) { + // return; + // } + + const globalScope = getGlobalScope(); + const dsn = client.getDsn(); + + const headers: LogEnvelope[0] = { + trace: { + trace_id: globalScope.getPropagationContext().traceId, + public_key: dsn?.publicKey, + }, + ...(dsn ? {dsn: dsnToString(dsn)} : {}), + } + if(!log.traceId) { + log.traceId = globalScope.getPropagationContext().traceId || '00000000-0000-0000-0000-000000000000'; + } + if(!log.timeUnixNano) { + log.timeUnixNano = `${(new Date()).getTime().toString()}000000`; + } + + const envelope = createEnvelope(headers, [createLogEnvelopeItem(log)]); + + client.sendEnvelope(envelope).then(null, ex => console.error(ex)); +} + +/** + * A utility function to be able to create methods like Sentry.info(...) + * + * The first parameter is bound with, e.g., const info = captureLog.bind(null, 'info') + * The other parameters are in the format to be passed a template, Sentry.info`hello ${world}` + */ +export function captureLog(level: LogSeverityLevel, strings: string[], ...values: unknown[]): void { + addLog({ + severityText: level, + body: { + stringValue: strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '' ), + }, + attributes: values.map((value, index) => { + const key = `param${index}`; + if (typeof value === 'number') { + if(Number.isInteger(value)) { + return { + key, + value: { + intValue: value + } + } + } + return { + key, + value: { + doubleValue: value + } + } + } else if (typeof value === 'boolean') { + return { + key, + value: { + boolValue: value + } + } + } else if (typeof value === 'string') { + return { + key, + value: { + stringValue: value + } + } + } else { + return { + key, + value: { + stringValue: JSON.stringify(value) + } + } + } + }, {}) + }) +} diff --git a/packages/core/src/types-hoist/envelope.ts b/packages/core/src/types-hoist/envelope.ts index 5a54ffc7b8c2..d2486f94d5e0 100644 --- a/packages/core/src/types-hoist/envelope.ts +++ b/packages/core/src/types-hoist/envelope.ts @@ -10,6 +10,7 @@ import type { ReplayEvent, ReplayRecordingData } from './replay'; import type { SdkInfo } from './sdkinfo'; import type { SerializedSession, SessionAggregates } from './session'; import type { SpanJSON } from './span'; +import { Log } from './ourlogs'; // Based on: https://develop.sentry.dev/sdk/envelopes/ @@ -43,6 +44,7 @@ export type EnvelopeItemType = | 'replay_recording' | 'check_in' | 'span' + | 'otel_log' | 'raw_security'; export type BaseEnvelopeHeaders = { @@ -85,6 +87,7 @@ type CheckInItemHeaders = { type: 'check_in' }; type ProfileItemHeaders = { type: 'profile' }; type ProfileChunkItemHeaders = { type: 'profile_chunk' }; type SpanItemHeaders = { type: 'span' }; +type LogItemHeaders = { type: 'otel_log' }; type RawSecurityHeaders = { type: 'raw_security'; sentry_release?: string; sentry_environment?: string }; export type EventItem = BaseEnvelopeItem; @@ -101,6 +104,7 @@ export type FeedbackItem = BaseEnvelopeItem; export type ProfileItem = BaseEnvelopeItem; export type ProfileChunkItem = BaseEnvelopeItem; export type SpanItem = BaseEnvelopeItem>; +export type LogItem = BaseEnvelopeItem; export type RawSecurityItem = BaseEnvelopeItem; export type EventEnvelopeHeaders = { event_id: string; sent_at: string; trace?: Partial }; @@ -109,6 +113,7 @@ type CheckInEnvelopeHeaders = { trace?: DynamicSamplingContext }; type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders; type ReplayEnvelopeHeaders = BaseEnvelopeHeaders; type SpanEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext }; +type LogEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext }; export type EventEnvelope = BaseEnvelope< EventEnvelopeHeaders, @@ -121,6 +126,7 @@ export type CheckInEnvelope = BaseEnvelope; export type SpanEnvelope = BaseEnvelope; export type ProfileChunkEnvelope = BaseEnvelope; export type RawSecurityEnvelope = BaseEnvelope; +export type LogEnvelope = BaseEnvelope; export type Envelope = | EventEnvelope @@ -130,6 +136,7 @@ export type Envelope = | ReplayEnvelope | CheckInEnvelope | SpanEnvelope - | RawSecurityEnvelope; + | RawSecurityEnvelope + | LogEnvelope; export type EnvelopeItem = Envelope[1][number]; diff --git a/packages/core/src/types-hoist/index.ts b/packages/core/src/types-hoist/index.ts index c1cbe5284808..469f896ae7fe 100644 --- a/packages/core/src/types-hoist/index.ts +++ b/packages/core/src/types-hoist/index.ts @@ -113,6 +113,12 @@ export type { SpanContextData, TraceFlag, } from './span'; +export type { + Log, + LogAttribute, + LogSeverityLevel, + LogAttributeValueType +} from './ourlogs'; export type { SpanStatus } from './spanStatus'; export type { TimedEvent } from './timedEvent'; export type { StackFrame } from './stackframe'; diff --git a/packages/core/src/types-hoist/ourlogs.ts b/packages/core/src/types-hoist/ourlogs.ts new file mode 100644 index 000000000000..bda15b12692f --- /dev/null +++ b/packages/core/src/types-hoist/ourlogs.ts @@ -0,0 +1,67 @@ +import type { SeverityLevel } from './severity'; + +export type LogSeverityLevel = SeverityLevel | 'critical' | 'trace'; + +export type LogAttributeValueType = { + stringValue: string +} | { + intValue: number +} | { + boolValue: boolean +} | { + doubleValue: number +} + +export type LogAttribute = { + key: string, + value: LogAttributeValueType +}; + +export interface Log { + /** + * Allowed values are, from highest to lowest: + * `critical`, `fatal`, `error`, `warning`, `info`, `debug`, `trace`. + * + * The log level changes how logs are filtered and displayed. + * Critical level logs are emphasized more than trace level logs. + * + * @summary The severity level of the log. + */ + severityText?: LogSeverityLevel; + + /** + * The severity number - generally higher severity are levels like 'error' and lower are levels like 'debug' + */ + severityNumber?: number; + + /** + * OTEL trace flags (bitmap) - currently 1 means sampled, 0 means unsampled - for sentry always set to 0 + */ + traceFlags?: number; + + /** + * The trace ID for this log + */ + traceId?: string; + + /** + * The message to be logged - for example, 'hello world' would become a log like '[INFO] hello world' + */ + body: { + stringValue: string, + }; + + /** + * Arbitrary structured data that stores information about the log - e.g., userId: 100. + */ + attributes?: LogAttribute[]; + + /** + * This doesn't have to be explicitly specified most of the time. If you need to set it, the value + * is the number of seconds since midnight on January 1, 1970 ("unix epoch time") + * + * @summary A timestamp representing when the log occurred. + * @link https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#:~:text=is%20info.-,timestamp,-(recommended) + */ + timeUnixNano?: string; +} diff --git a/packages/core/src/utils-hoist/envelope.ts b/packages/core/src/utils-hoist/envelope.ts index 46512850cefc..9655f9312579 100644 --- a/packages/core/src/utils-hoist/envelope.ts +++ b/packages/core/src/utils-hoist/envelope.ts @@ -223,6 +223,7 @@ const ITEM_TYPE_TO_DATA_CATEGORY_MAP: Record = { feedback: 'feedback', span: 'span', raw_security: 'security', + otel_log: 'log_item', }; /** From 4281cbeeb1f1e837f4fe6d4bfe19f1db36120bf8 Mon Sep 17 00:00:00 2001 From: Colin Chartier Date: Fri, 14 Feb 2025 11:48:32 -0500 Subject: [PATCH 02/31] improve a bit --- packages/core/src/ourlogs.ts | 89 +++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/packages/core/src/ourlogs.ts b/packages/core/src/ourlogs.ts index 42066ea61ec4..82fd6697a7bb 100644 --- a/packages/core/src/ourlogs.ts +++ b/packages/core/src/ourlogs.ts @@ -55,57 +55,60 @@ function addLog(log: Log): void { client.sendEnvelope(envelope).then(null, ex => console.error(ex)); } +function valueToAttribute(key: string, value: unknown): LogAttribute { + if (typeof value === 'number') { + if(Number.isInteger(value)) { + return { + key, + value: { + intValue: value + } + } + } + return { + key, + value: { + doubleValue: value + } + } + } else if (typeof value === 'boolean') { + return { + key, + value: { + boolValue: value + } + } + } else if (typeof value === 'string') { + return { + key, + value: { + stringValue: value + } + } + } else { + return { + key, + value: { + stringValue: JSON.stringify(value) + } + } + } +} + /** - * A utility function to be able to create methods like Sentry.info(...) + * A utility function to be able to create methods like Sentry.info`...` * * The first parameter is bound with, e.g., const info = captureLog.bind(null, 'info') * The other parameters are in the format to be passed a template, Sentry.info`hello ${world}` */ -export function captureLog(level: LogSeverityLevel, strings: string[], ...values: unknown[]): void { +export function captureLog(level: LogSeverityLevel, messages: string[] | string, ...values: unknown[]): void { + const message = Array.isArray(messages) ? messages.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '') : messages; + addLog({ severityText: level, body: { - stringValue: strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '' ), + stringValue: message, }, - attributes: values.map((value, index) => { - const key = `param${index}`; - if (typeof value === 'number') { - if(Number.isInteger(value)) { - return { - key, - value: { - intValue: value - } - } - } - return { - key, - value: { - doubleValue: value - } - } - } else if (typeof value === 'boolean') { - return { - key, - value: { - boolValue: value - } - } - } else if (typeof value === 'string') { - return { - key, - value: { - stringValue: value - } - } - } else { - return { - key, - value: { - stringValue: JSON.stringify(value) - } - } - } - }, {}) + attributes: values.map((value, index) => valueToAttribute(`param${index}`, value)), }) } From 1869d6fcdcd402b1890a81175bfdaed643381ef3 Mon Sep 17 00:00:00 2001 From: Colin Chartier Date: Tue, 18 Feb 2025 15:12:13 -0500 Subject: [PATCH 03/31] linting, etc --- packages/core/src/exports.ts | 3 +- packages/core/src/index.ts | 2 +- packages/core/src/ourlogs.ts | 72 +++++++++++++---------- packages/core/src/types-hoist/envelope.ts | 2 +- packages/core/src/types-hoist/index.ts | 2 +- packages/core/src/types-hoist/ourlogs.ts | 28 +++++---- 6 files changed, 62 insertions(+), 47 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index d1a15479a760..8470d9445b25 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -1,5 +1,6 @@ import { getClient, getCurrentScope, getIsolationScope, withIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; +import { captureLog } from './ourlogs'; import type { CaptureContext } from './scope'; import { closeSession, makeSession, updateSession } from './session'; import type { @@ -24,7 +25,6 @@ import { timestampInSeconds } from './utils-hoist/time'; import { GLOBAL_OBJ } from './utils-hoist/worldwide'; import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; import { parseEventHintOrCaptureContext } from './utils/prepareEvent'; -import { captureLog } from './ourlogs'; /** * Captures an exception event and sends it to Sentry. @@ -336,7 +336,6 @@ export function captureSession(end: boolean = false): void { _sendSessionUpdate(); } - /** * A utility to record a log with level 'INFO' and send it to sentry. * diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8e40617e2743..3fb120a41c5a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -31,7 +31,7 @@ export { addEventProcessor, _experimentalLogError, _experimentalLogInfo, - _experimentalLogWarning + _experimentalLogWarning, } from './exports'; export { getCurrentScope, diff --git a/packages/core/src/ourlogs.ts b/packages/core/src/ourlogs.ts index 82fd6697a7bb..d3c6fa8c8bd5 100644 --- a/packages/core/src/ourlogs.ts +++ b/packages/core/src/ourlogs.ts @@ -29,9 +29,9 @@ function addLog(log: Log): void { return; } - // if (!client.getOptions()._experiments?.logSupport) { - // return; - // } + if (!client.getOptions()._experiments?.logSupport) { + return; + } const globalScope = getGlobalScope(); const dsn = client.getDsn(); @@ -41,57 +41,59 @@ function addLog(log: Log): void { trace_id: globalScope.getPropagationContext().traceId, public_key: dsn?.publicKey, }, - ...(dsn ? {dsn: dsnToString(dsn)} : {}), - } - if(!log.traceId) { + ...(dsn ? { dsn: dsnToString(dsn) } : {}), + }; + if (!log.traceId) { log.traceId = globalScope.getPropagationContext().traceId || '00000000-0000-0000-0000-000000000000'; } - if(!log.timeUnixNano) { - log.timeUnixNano = `${(new Date()).getTime().toString()}000000`; + if (!log.timeUnixNano) { + log.timeUnixNano = `${new Date().getTime().toString()}000000`; } const envelope = createEnvelope(headers, [createLogEnvelopeItem(log)]); - client.sendEnvelope(envelope).then(null, ex => console.error(ex)); + // sendEnvelope should not throw + // eslint-disable-next-line @typescript-eslint/no-floating-promises + client.sendEnvelope(envelope); } function valueToAttribute(key: string, value: unknown): LogAttribute { if (typeof value === 'number') { - if(Number.isInteger(value)) { + if (Number.isInteger(value)) { return { key, value: { - intValue: value - } - } + intValue: value, + }, + }; } return { key, value: { - doubleValue: value - } - } + doubleValue: value, + }, + }; } else if (typeof value === 'boolean') { return { key, value: { - boolValue: value - } - } + boolValue: value, + }, + }; } else if (typeof value === 'string') { return { key, value: { - stringValue: value - } - } + stringValue: value, + }, + }; } else { return { key, value: { - stringValue: JSON.stringify(value) - } - } + stringValue: JSON.stringify(value), + }, + }; } } @@ -99,16 +101,26 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { * A utility function to be able to create methods like Sentry.info`...` * * The first parameter is bound with, e.g., const info = captureLog.bind(null, 'info') - * The other parameters are in the format to be passed a template, Sentry.info`hello ${world}` + * The other parameters are in the format to be passed a tagged template, Sentry.info`hello ${world}` */ export function captureLog(level: LogSeverityLevel, messages: string[] | string, ...values: unknown[]): void { - const message = Array.isArray(messages) ? messages.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '') : messages; - + const message = Array.isArray(messages) + ? messages.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '') + : messages; + const attributes = values.map((value, index) => valueToAttribute(`param${index}`, value)); + if (Array.isArray(messages)) { + attributes.push({ + key: 'sentry.template', + value: { + stringValue: messages.map((s, i) => s + (i < messages.length - 1 ? `$param${i}` : '')).join(''), + }, + }); + } addLog({ severityText: level, body: { stringValue: message, }, - attributes: values.map((value, index) => valueToAttribute(`param${index}`, value)), - }) + attributes: attributes, + }); } diff --git a/packages/core/src/types-hoist/envelope.ts b/packages/core/src/types-hoist/envelope.ts index d2486f94d5e0..b219e78f90c2 100644 --- a/packages/core/src/types-hoist/envelope.ts +++ b/packages/core/src/types-hoist/envelope.ts @@ -5,12 +5,12 @@ import type { LegacyCSPReport } from './csp'; import type { DsnComponents } from './dsn'; import type { Event } from './event'; import type { FeedbackEvent, UserFeedback } from './feedback'; +import type { Log } from './ourlogs'; import type { Profile, ProfileChunk } from './profiling'; import type { ReplayEvent, ReplayRecordingData } from './replay'; import type { SdkInfo } from './sdkinfo'; import type { SerializedSession, SessionAggregates } from './session'; import type { SpanJSON } from './span'; -import { Log } from './ourlogs'; // Based on: https://develop.sentry.dev/sdk/envelopes/ diff --git a/packages/core/src/types-hoist/index.ts b/packages/core/src/types-hoist/index.ts index 469f896ae7fe..66ec0647d182 100644 --- a/packages/core/src/types-hoist/index.ts +++ b/packages/core/src/types-hoist/index.ts @@ -117,7 +117,7 @@ export type { Log, LogAttribute, LogSeverityLevel, - LogAttributeValueType + LogAttributeValueType, } from './ourlogs'; export type { SpanStatus } from './spanStatus'; export type { TimedEvent } from './timedEvent'; diff --git a/packages/core/src/types-hoist/ourlogs.ts b/packages/core/src/types-hoist/ourlogs.ts index bda15b12692f..5324aa683d9e 100644 --- a/packages/core/src/types-hoist/ourlogs.ts +++ b/packages/core/src/types-hoist/ourlogs.ts @@ -2,19 +2,23 @@ import type { SeverityLevel } from './severity'; export type LogSeverityLevel = SeverityLevel | 'critical' | 'trace'; -export type LogAttributeValueType = { - stringValue: string -} | { - intValue: number -} | { - boolValue: boolean -} | { - doubleValue: number -} +export type LogAttributeValueType = + | { + stringValue: string; + } + | { + intValue: number; + } + | { + boolValue: boolean; + } + | { + doubleValue: number; + }; export type LogAttribute = { - key: string, - value: LogAttributeValueType + key: string; + value: LogAttributeValueType; }; export interface Log { @@ -48,7 +52,7 @@ export interface Log { * The message to be logged - for example, 'hello world' would become a log like '[INFO] hello world' */ body: { - stringValue: string, + stringValue: string; }; /** From 4b894862cebed897af3d5632e3baa8451c8860f3 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 19 Feb 2025 20:55:32 -0500 Subject: [PATCH 04/31] fix: Rename ourlog -> log --- packages/core/src/exports.ts | 2 +- packages/core/src/{ourlogs.ts => log.ts} | 2 +- packages/core/src/types-hoist/envelope.ts | 2 +- packages/core/src/types-hoist/index.ts | 2 +- packages/core/src/types-hoist/{ourlogs.ts => log.ts} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename packages/core/src/{ourlogs.ts => log.ts} (99%) rename packages/core/src/types-hoist/{ourlogs.ts => log.ts} (100%) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 8470d9445b25..5c51a90a6d40 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -1,6 +1,6 @@ import { getClient, getCurrentScope, getIsolationScope, withIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; -import { captureLog } from './ourlogs'; +import { captureLog } from './log'; import type { CaptureContext } from './scope'; import { closeSession, makeSession, updateSession } from './session'; import type { diff --git a/packages/core/src/ourlogs.ts b/packages/core/src/log.ts similarity index 99% rename from packages/core/src/ourlogs.ts rename to packages/core/src/log.ts index d3c6fa8c8bd5..db27eb1458b8 100644 --- a/packages/core/src/ourlogs.ts +++ b/packages/core/src/log.ts @@ -1,6 +1,6 @@ import { getClient, getGlobalScope } from './currentScopes'; import type { LogEnvelope, LogItem } from './types-hoist/envelope'; -import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/ourlogs'; +import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/log'; import { createEnvelope, dsnToString } from './utils-hoist'; /** diff --git a/packages/core/src/types-hoist/envelope.ts b/packages/core/src/types-hoist/envelope.ts index b219e78f90c2..d78cccc8384a 100644 --- a/packages/core/src/types-hoist/envelope.ts +++ b/packages/core/src/types-hoist/envelope.ts @@ -5,7 +5,7 @@ import type { LegacyCSPReport } from './csp'; import type { DsnComponents } from './dsn'; import type { Event } from './event'; import type { FeedbackEvent, UserFeedback } from './feedback'; -import type { Log } from './ourlogs'; +import type { Log } from './log'; import type { Profile, ProfileChunk } from './profiling'; import type { ReplayEvent, ReplayRecordingData } from './replay'; import type { SdkInfo } from './sdkinfo'; diff --git a/packages/core/src/types-hoist/index.ts b/packages/core/src/types-hoist/index.ts index 66ec0647d182..57bacb75c7d2 100644 --- a/packages/core/src/types-hoist/index.ts +++ b/packages/core/src/types-hoist/index.ts @@ -118,7 +118,7 @@ export type { LogAttribute, LogSeverityLevel, LogAttributeValueType, -} from './ourlogs'; +} from './log'; export type { SpanStatus } from './spanStatus'; export type { TimedEvent } from './timedEvent'; export type { StackFrame } from './stackframe'; diff --git a/packages/core/src/types-hoist/ourlogs.ts b/packages/core/src/types-hoist/log.ts similarity index 100% rename from packages/core/src/types-hoist/ourlogs.ts rename to packages/core/src/types-hoist/log.ts From 2fe0e1b19b4063b13fc6de020372fc97f3105f16 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 19 Feb 2025 20:56:21 -0500 Subject: [PATCH 05/31] ref: Namespace logging methods in experiment obj --- packages/browser/src/index.ts | 4 +--- packages/core/src/exports.ts | 45 +++++++++++++++++++---------------- packages/core/src/index.ts | 4 +--- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 4449b637a34b..78ddf94246ef 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -10,9 +10,7 @@ export { extraErrorDataIntegration, rewriteFramesIntegration, captureFeedback, - _experimentalLogError, - _experimentalLogInfo, - _experimentalLogWarning, + _experiment_log, } from '@sentry/core'; export { replayIntegration, getReplay } from '@sentry-internal/replay'; diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 5c51a90a6d40..f04d4289b53f 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -337,25 +337,28 @@ export function captureSession(end: boolean = false): void { } /** - * A utility to record a log with level 'INFO' and send it to sentry. - * - * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: sentry.logInfo`user ${username} just bought ${item}!` - */ -export const _experimentalLogInfo = captureLog.bind(null, 'info'); - -/** - * A utility to record a log with level 'ERROR' and send it to sentry. - * - * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: sentry.logError`user ${username} just bought ${item}!` - */ -export const _experimentalLogError = captureLog.bind(null, 'error'); - -/** - * A utility to record a log with level 'WARNING' and send it to sentry. - * - * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: sentry.logWarning`user ${username} just bought ${item}!` + * A namespace for experimental logging functions. */ -export const _experimentalLogWarning = captureLog.bind(null, 'warning'); +export const _experiment_log = { + /** + * A utility to record a log with level 'INFO' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: sentry.logInfo`user ${username} just bought ${item}!` + */ + info: captureLog.bind(null, 'info'), + /** + * A utility to record a log with level 'ERROR' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: sentry.logError`user ${username} just bought ${item}!` + */ + error: captureLog.bind(null, 'error'), + /** + * A utility to record a log with level 'WARNING' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: sentry.logWarning`user ${username} just bought ${item}!` + */ + warning: captureLog.bind(null, 'warning'), +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3fb120a41c5a..f8ac69ed0b97 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -29,9 +29,7 @@ export { endSession, captureSession, addEventProcessor, - _experimentalLogError, - _experimentalLogInfo, - _experimentalLogWarning, + _experiment_log, } from './exports'; export { getCurrentScope, From 21b16e8d45a25c622bde171df1b335220f933280 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 19 Feb 2025 21:03:54 -0500 Subject: [PATCH 06/31] ref: Improve log creation logic and update option name --- packages/core/src/log.ts | 77 ++++++++++-------------- packages/core/src/types-hoist/options.ts | 5 ++ 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index db27eb1458b8..c8a26cae8542 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -1,7 +1,9 @@ -import { getClient, getGlobalScope } from './currentScopes'; -import type { LogEnvelope, LogItem } from './types-hoist/envelope'; +import { getClient, getCurrentScope } from './currentScopes'; +import { DEBUG_BUILD } from './debug-build'; +import { getDynamicSamplingContextFromScope } from './tracing'; +import type { DynamicSamplingContext, LogEnvelope, LogItem } from './types-hoist/envelope'; import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/log'; -import { createEnvelope, dsnToString } from './utils-hoist'; +import { createEnvelope, dropUndefinedKeys, dsnToString, logger } from './utils-hoist'; /** * Creates envelope item for a single log @@ -26,25 +28,26 @@ function addLog(log: Log): void { const client = getClient(); if (!client) { + DEBUG_BUILD && logger.warn('No client available, log will not be captured.'); return; } - if (!client.getOptions()._experiments?.logSupport) { + if (!client.getOptions()._experiments?.enableLogs) { + DEBUG_BUILD && logger.warn('logging option not enabled, log will not be captured.'); return; } - const globalScope = getGlobalScope(); + const scope = getCurrentScope(); + const dsc = getDynamicSamplingContextFromScope(client, scope); + const dsn = client.getDsn(); const headers: LogEnvelope[0] = { - trace: { - trace_id: globalScope.getPropagationContext().traceId, - public_key: dsn?.publicKey, - }, + trace: dropUndefinedKeys(dsc) as DynamicSamplingContext, ...(dsn ? { dsn: dsnToString(dsn) } : {}), }; if (!log.traceId) { - log.traceId = globalScope.getPropagationContext().traceId || '00000000-0000-0000-0000-000000000000'; + log.traceId = dsc.trace_id; } if (!log.timeUnixNano) { log.timeUnixNano = `${new Date().getTime().toString()}000000`; @@ -52,48 +55,32 @@ function addLog(log: Log): void { const envelope = createEnvelope(headers, [createLogEnvelopeItem(log)]); - // sendEnvelope should not throw // eslint-disable-next-line @typescript-eslint/no-floating-promises - client.sendEnvelope(envelope); + void client.sendEnvelope(envelope); } function valueToAttribute(key: string, value: unknown): LogAttribute { - if (typeof value === 'number') { - if (Number.isInteger(value)) { + switch (typeof value) { + case 'number': return { key, - value: { - intValue: value, - }, + value: { doubleValue: value }, + }; + case 'boolean': + return { + key, + value: { boolValue: value }, + }; + case 'string': + return { + key, + value: { stringValue: value }, + }; + default: + return { + key, + value: { stringValue: JSON.stringify(value) }, }; - } - return { - key, - value: { - doubleValue: value, - }, - }; - } else if (typeof value === 'boolean') { - return { - key, - value: { - boolValue: value, - }, - }; - } else if (typeof value === 'string') { - return { - key, - value: { - stringValue: value, - }, - }; - } else { - return { - key, - value: { - stringValue: JSON.stringify(value), - }, - }; } } diff --git a/packages/core/src/types-hoist/options.ts b/packages/core/src/types-hoist/options.ts index 8e52b32eacf7..d0474b959fa9 100644 --- a/packages/core/src/types-hoist/options.ts +++ b/packages/core/src/types-hoist/options.ts @@ -182,7 +182,12 @@ export interface ClientOptions Date: Wed, 19 Feb 2025 21:26:18 -0500 Subject: [PATCH 07/31] allow for multiple logs to be flushed in the same envelope --- packages/core/src/exports.ts | 10 ++--- packages/core/src/log.ts | 82 ++++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index f04d4289b53f..3e4136788689 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -344,21 +344,21 @@ export const _experiment_log = { * A utility to record a log with level 'INFO' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: sentry.logInfo`user ${username} just bought ${item}!` + * Ex: Sentry._experiment_log.info`user ${username} just bought ${item}!` */ info: captureLog.bind(null, 'info'), /** * A utility to record a log with level 'ERROR' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: sentry.logError`user ${username} just bought ${item}!` + * Ex: Sentry._experiment_log.error`user ${username} just bought ${item}!` */ error: captureLog.bind(null, 'error'), /** - * A utility to record a log with level 'WARNING' and send it to sentry. + * A utility to record a log with level 'WARN' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: sentry.logWarning`user ${username} just bought ${item}!` + * Ex: Sentry._experiment_log.warn`user ${username} just bought ${item}!` */ - warning: captureLog.bind(null, 'warning'), + warn: captureLog.bind(null, 'warn'), }; diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index c8a26cae8542..c1a956a384d9 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -1,5 +1,7 @@ +import type { Client } from './client'; import { getClient, getCurrentScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; +import type { Scope } from './scope'; import { getDynamicSamplingContextFromScope } from './tracing'; import type { DynamicSamplingContext, LogEnvelope, LogItem } from './types-hoist/envelope'; import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/log'; @@ -24,20 +26,7 @@ export function createLogEnvelopeItem(log: Log): LogItem { * * @params log - the log object which will be sent */ -function addLog(log: Log): void { - const client = getClient(); - - if (!client) { - DEBUG_BUILD && logger.warn('No client available, log will not be captured.'); - return; - } - - if (!client.getOptions()._experiments?.enableLogs) { - DEBUG_BUILD && logger.warn('logging option not enabled, log will not be captured.'); - return; - } - - const scope = getCurrentScope(); +function createLogEnvelope(logs: Log[], client: Client, scope: Scope): LogEnvelope { const dsc = getDynamicSamplingContextFromScope(client, scope); const dsn = client.getDsn(); @@ -46,17 +35,8 @@ function addLog(log: Log): void { trace: dropUndefinedKeys(dsc) as DynamicSamplingContext, ...(dsn ? { dsn: dsnToString(dsn) } : {}), }; - if (!log.traceId) { - log.traceId = dsc.trace_id; - } - if (!log.timeUnixNano) { - log.timeUnixNano = `${new Date().getTime().toString()}000000`; - } - - const envelope = createEnvelope(headers, [createLogEnvelopeItem(log)]); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - void client.sendEnvelope(envelope); + return createEnvelope(headers, logs.map(createLogEnvelopeItem)); } function valueToAttribute(key: string, value: unknown): LogAttribute { @@ -84,6 +64,37 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { } } +let GLOBAL_LOG_BUFFER: Log[] = []; + +let isFlushingLogs = false; + +function addToLogBuffer(client: Client, log: Log, scope: Scope): void { + function sendLogs(flushedLogs: Log[]): void { + const envelope = createLogEnvelope(flushedLogs, client, scope); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + void client.sendEnvelope(envelope); + } + + if (GLOBAL_LOG_BUFFER.length >= 100) { + sendLogs(GLOBAL_LOG_BUFFER); + GLOBAL_LOG_BUFFER = []; + } else { + GLOBAL_LOG_BUFFER.push(log); + } + + // this is the first time logs have been enabled, let's kick off an interval to flush them + // we should only do this once. + if (!isFlushingLogs) { + setInterval(() => { + if (GLOBAL_LOG_BUFFER.length > 0) { + sendLogs(GLOBAL_LOG_BUFFER); + GLOBAL_LOG_BUFFER = []; + } + }, 5000); + } + isFlushingLogs = true; +} + /** * A utility function to be able to create methods like Sentry.info`...` * @@ -91,6 +102,18 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { * The other parameters are in the format to be passed a tagged template, Sentry.info`hello ${world}` */ export function captureLog(level: LogSeverityLevel, messages: string[] | string, ...values: unknown[]): void { + const client = getClient(); + + if (!client) { + DEBUG_BUILD && logger.warn('No client available, log will not be captured.'); + return; + } + + if (!client.getOptions()._experiments?.enableLogs) { + DEBUG_BUILD && logger.warn('logging option not enabled, log will not be captured.'); + return; + } + const message = Array.isArray(messages) ? messages.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '') : messages; @@ -103,11 +126,18 @@ export function captureLog(level: LogSeverityLevel, messages: string[] | string, }, }); } - addLog({ + + const scope = getCurrentScope(); + + const log: Log = { severityText: level, body: { stringValue: message, }, attributes: attributes, - }); + timeUnixNano: `${new Date().getTime().toString()}000000`, + traceId: scope.getPropagationContext().traceId, + }; + + addToLogBuffer(client, log, scope); } From 8fc4ae2a1f4a91d4ba8f3aefa5879055e2663399 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 20 Feb 2025 09:26:48 -0500 Subject: [PATCH 08/31] feat: Add release and environment to logs --- packages/core/src/log.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index c1a956a384d9..91a9d551537c 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -127,6 +127,26 @@ export function captureLog(level: LogSeverityLevel, messages: string[] | string, }); } + const { release, environment } = client.getOptions(); + + if (release) { + attributes.push({ + key: 'sentry.release', + value: { + stringValue: release, + }, + }); + } + + if (environment) { + attributes.push({ + key: 'sentry.environment', + value: { + stringValue: environment, + }, + }); + } + const scope = getCurrentScope(); const log: Log = { From f3adcde33a25af5b5607a9f65817ba44aaa9cc6d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 20 Feb 2025 09:29:03 -0500 Subject: [PATCH 09/31] ref: Change log buffer max length from 100 -> 25 --- packages/core/src/log.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 91a9d551537c..cc98dab8879b 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -64,6 +64,8 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { } } +const LOG_BUFFER_MAX_LENGTH = 25; + let GLOBAL_LOG_BUFFER: Log[] = []; let isFlushingLogs = false; @@ -75,7 +77,7 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { void client.sendEnvelope(envelope); } - if (GLOBAL_LOG_BUFFER.length >= 100) { + if (GLOBAL_LOG_BUFFER.length >= LOG_BUFFER_MAX_LENGTH) { sendLogs(GLOBAL_LOG_BUFFER); GLOBAL_LOG_BUFFER = []; } else { From bbc170da095484e224403c6a8953a454008b30dc Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 20 Feb 2025 12:01:54 -0500 Subject: [PATCH 10/31] feat: Send severityNumber --- packages/core/src/log.ts | 25 +++++++++++++++++++------ packages/core/src/types-hoist/log.ts | 4 +--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index cc98dab8879b..81d3f4bf7ca2 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -7,6 +7,20 @@ import type { DynamicSamplingContext, LogEnvelope, LogItem } from './types-hoist import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/log'; import { createEnvelope, dropUndefinedKeys, dsnToString, logger } from './utils-hoist'; +const LOG_BUFFER_MAX_LENGTH = 25; + +let GLOBAL_LOG_BUFFER: Log[] = []; + +let isFlushingLogs = false; + +const SEVERITY_TEXT_TO_SEVERITY_NUMBER: Partial> = { + debug: 10, + info: 20, + warning: 30, + error: 40, + critical: 50, +}; + /** * Creates envelope item for a single log */ @@ -64,12 +78,6 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { } } -const LOG_BUFFER_MAX_LENGTH = 25; - -let GLOBAL_LOG_BUFFER: Log[] = []; - -let isFlushingLogs = false; - function addToLogBuffer(client: Client, log: Log, scope: Scope): void { function sendLogs(flushedLogs: Log[]): void { const envelope = createLogEnvelope(flushedLogs, client, scope); @@ -161,5 +169,10 @@ export function captureLog(level: LogSeverityLevel, messages: string[] | string, traceId: scope.getPropagationContext().traceId, }; + const maybeSeverityNumber = SEVERITY_TEXT_TO_SEVERITY_NUMBER[level]; + if (maybeSeverityNumber !== undefined) { + log.severityNumber = maybeSeverityNumber; + } + addToLogBuffer(client, log, scope); } diff --git a/packages/core/src/types-hoist/log.ts b/packages/core/src/types-hoist/log.ts index 5324aa683d9e..2d890ca11982 100644 --- a/packages/core/src/types-hoist/log.ts +++ b/packages/core/src/types-hoist/log.ts @@ -1,6 +1,4 @@ -import type { SeverityLevel } from './severity'; - -export type LogSeverityLevel = SeverityLevel | 'critical' | 'trace'; +export type LogSeverityLevel = 'trace' | 'debug' | 'info' | 'warning' | 'error' | 'fatal' | 'critical'; export type LogAttributeValueType = | { From bab4c6ec751ac15b877c5a88903bce9968989d74 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 20 Feb 2025 12:47:54 -0500 Subject: [PATCH 11/31] ref: re-org functions and exports --- packages/core/src/exports.ts | 40 +++++++++++++-- packages/core/src/log.ts | 77 ++++++++++++++++------------ packages/core/src/types-hoist/log.ts | 4 +- 3 files changed, 81 insertions(+), 40 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 3e4136788689..a71de1dbb2cc 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -1,6 +1,6 @@ import { getClient, getCurrentScope, getIsolationScope, withIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; -import { captureLog } from './log'; +import { captureLog, sendLog } from './log'; import type { CaptureContext } from './scope'; import { closeSession, makeSession, updateSession } from './session'; import type { @@ -11,6 +11,7 @@ import type { Extra, Extras, FinishedCheckIn, + LogSeverityLevel, MonitorConfig, Primitive, Session, @@ -336,29 +337,60 @@ export function captureSession(end: boolean = false): void { _sendSessionUpdate(); } +type OmitFirstArg = F extends (x: LogSeverityLevel, ...args: infer P) => infer R ? (...args: P) => R : never; + /** * A namespace for experimental logging functions. + * + * @experimental Will be removed in future versions. Do not use. */ export const _experiment_log = { + /** + * A utility to record a log with level 'TRACE' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: Sentry._experiment_log.trace`user ${username} just bought ${item}!` + */ + trace: sendLog.bind(null, 'trace') as OmitFirstArg, + /** + * A utility to record a log with level 'DEBUG' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: Sentry._experiment_log.debug`user ${username} just bought ${item}!` + */ + debug: sendLog.bind(null, 'debug') as OmitFirstArg, /** * A utility to record a log with level 'INFO' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. * Ex: Sentry._experiment_log.info`user ${username} just bought ${item}!` */ - info: captureLog.bind(null, 'info'), + info: sendLog.bind(null, 'info') as OmitFirstArg, /** * A utility to record a log with level 'ERROR' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. * Ex: Sentry._experiment_log.error`user ${username} just bought ${item}!` */ - error: captureLog.bind(null, 'error'), + error: sendLog.bind(null, 'error') as OmitFirstArg, /** * A utility to record a log with level 'WARN' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. * Ex: Sentry._experiment_log.warn`user ${username} just bought ${item}!` */ - warn: captureLog.bind(null, 'warn'), + warn: sendLog.bind(null, 'warn') as OmitFirstArg, + /** + * A utility to record a log with level 'FATAL' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: Sentry._experiment_log.warn`user ${username} just bought ${item}!` + */ + fatal: sendLog.bind(null, 'fatal') as OmitFirstArg, + /** + * A flexible utility to record a log with a custom level and send it to sentry. + * + * You can optionally pass in custom attributes and a custom severity number to be attached to the log. + */ + captureLog, }; diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 81d3f4bf7ca2..2057e5c91d74 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -14,11 +14,12 @@ let GLOBAL_LOG_BUFFER: Log[] = []; let isFlushingLogs = false; const SEVERITY_TEXT_TO_SEVERITY_NUMBER: Partial> = { - debug: 10, - info: 20, - warning: 30, - error: 40, - critical: 50, + trace: 1, + debug: 5, + info: 9, + warn: 13, + error: 17, + fatal: 21, }; /** @@ -106,12 +107,36 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { } /** - * A utility function to be able to create methods like Sentry.info`...` + * A utility function to be able to create methods like Sentry.info`...` that use tagged template functions. * * The first parameter is bound with, e.g., const info = captureLog.bind(null, 'info') * The other parameters are in the format to be passed a tagged template, Sentry.info`hello ${world}` */ -export function captureLog(level: LogSeverityLevel, messages: string[] | string, ...values: unknown[]): void { +export function sendLog(level: LogSeverityLevel, messageArr: TemplateStringsArray, ...values: unknown[]): void { + const message = messageArr.reduce((acc, str, i) => acc + str + (values[i] ?? ''), ''); + + const attributes = values.reduce>( + (acc, value, index) => { + acc[`param${index}`] = value; + return acc; + }, + { + 'sentry.template': messageArr.map((s, i) => s + (i < messageArr.length - 1 ? `$param${i}` : '')).join(''), + }, + ); + + captureLog(level, message, attributes); +} + +/** + * Sends a log to Sentry. + */ +export function captureLog( + level: LogSeverityLevel, + message: string, + customAttributes: Record = {}, + severityNumber?: number, +): void { const client = getClient(); if (!client) { @@ -124,53 +149,37 @@ export function captureLog(level: LogSeverityLevel, messages: string[] | string, return; } - const message = Array.isArray(messages) - ? messages.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '') - : messages; - const attributes = values.map((value, index) => valueToAttribute(`param${index}`, value)); - if (Array.isArray(messages)) { - attributes.push({ - key: 'sentry.template', - value: { - stringValue: messages.map((s, i) => s + (i < messages.length - 1 ? `$param${i}` : '')).join(''), - }, - }); - } - const { release, environment } = client.getOptions(); + const logAttributes = { + ...customAttributes, + }; + if (release) { - attributes.push({ - key: 'sentry.release', - value: { - stringValue: release, - }, - }); + logAttributes['sentry.release'] = release; } if (environment) { - attributes.push({ - key: 'sentry.environment', - value: { - stringValue: environment, - }, - }); + logAttributes['sentry.environment'] = environment; } const scope = getCurrentScope(); + const attributes = Object.entries(logAttributes).map(([key, value]) => valueToAttribute(key, value)); + const log: Log = { severityText: level, body: { stringValue: message, }, - attributes: attributes, + attributes, timeUnixNano: `${new Date().getTime().toString()}000000`, traceId: scope.getPropagationContext().traceId, + severityNumber, }; const maybeSeverityNumber = SEVERITY_TEXT_TO_SEVERITY_NUMBER[level]; - if (maybeSeverityNumber !== undefined) { + if (maybeSeverityNumber !== undefined && log.severityNumber === undefined) { log.severityNumber = maybeSeverityNumber; } diff --git a/packages/core/src/types-hoist/log.ts b/packages/core/src/types-hoist/log.ts index 2d890ca11982..a4ca06133a2c 100644 --- a/packages/core/src/types-hoist/log.ts +++ b/packages/core/src/types-hoist/log.ts @@ -1,4 +1,4 @@ -export type LogSeverityLevel = 'trace' | 'debug' | 'info' | 'warning' | 'error' | 'fatal' | 'critical'; +export type LogSeverityLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'critical'; export type LogAttributeValueType = | { @@ -22,7 +22,7 @@ export type LogAttribute = { export interface Log { /** * Allowed values are, from highest to lowest: - * `critical`, `fatal`, `error`, `warning`, `info`, `debug`, `trace`. + * `critical`, `fatal`, `error`, `warn`, `info`, `debug`, `trace`. * * The log level changes how logs are filtered and displayed. * Critical level logs are emphasized more than trace level logs. From f750d08c7b300e595e093360d3c2db5d11fbbc4b Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 20 Feb 2025 13:59:16 -0500 Subject: [PATCH 12/31] feat: Add log function --- packages/core/src/exports.ts | 7 +++++++ packages/core/src/log.ts | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index a71de1dbb2cc..c64bc1a86a48 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -366,6 +366,13 @@ export const _experiment_log = { * Ex: Sentry._experiment_log.info`user ${username} just bought ${item}!` */ info: sendLog.bind(null, 'info') as OmitFirstArg, + /** + * A utility to record a log with level 'INFO' and send it to sentry. + * + * Logs represent a message and some parameters which provide context for a trace or error. + * Ex: Sentry._experiment_log.log`user ${username} just bought ${item}!` + */ + log: sendLog.bind(null, 'log') as OmitFirstArg, /** * A utility to record a log with level 'ERROR' and send it to sentry. * diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 2057e5c91d74..56edf0702658 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -13,8 +13,9 @@ let GLOBAL_LOG_BUFFER: Log[] = []; let isFlushingLogs = false; -const SEVERITY_TEXT_TO_SEVERITY_NUMBER: Partial> = { +const SEVERITY_TEXT_TO_SEVERITY_NUMBER: Partial> = { trace: 1, + log: 2, debug: 5, info: 9, warn: 13, From 7218377cb7464c644cb2332f0b18aca721d2bd3f Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 20 Feb 2025 14:09:11 -0500 Subject: [PATCH 13/31] fix: use correct severity number for log --- packages/core/src/log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 56edf0702658..a0202f102622 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -15,9 +15,9 @@ let isFlushingLogs = false; const SEVERITY_TEXT_TO_SEVERITY_NUMBER: Partial> = { trace: 1, - log: 2, debug: 5, info: 9, + log: 10, warn: 13, error: 17, fatal: 21, From 3e936c97d122b7b4320b6dc28ff38ba2ddbd973d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 24 Feb 2025 13:52:50 -0500 Subject: [PATCH 14/31] chore: Bump size-limit --- .size-limit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 08adf5a80c29..c9a1163acb81 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -8,7 +8,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init'), gzip: true, - limit: '24 KB', + limit: '25 KB', }, { name: '@sentry/browser - with treeshaking flags', @@ -47,7 +47,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration'), gzip: true, - limit: '75 KB', + limit: '77 KB', }, { name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags', @@ -210,7 +210,7 @@ module.exports = [ import: createImport('init'), ignore: ['next/router', 'next/constants'], gzip: true, - limit: '41 KB', + limit: '43 KB', }, // SvelteKit SDK (ESM) { From b702cf0df1c719210bd0bdcef96fa1de99475eef Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 24 Feb 2025 14:36:54 -0500 Subject: [PATCH 15/31] chore: size limit entries that were missed --- .size-limit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index c9a1163acb81..d312dbcdac3b 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -40,7 +40,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration'), gzip: true, - limit: '37.5 KB', + limit: '38 KB', }, { name: '@sentry/browser (incl. Tracing, Replay)', @@ -79,7 +79,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, - limit: '80 KB', + limit: '82 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback)', From 301a1ca64dfe70ff903a0c13b2387354d307dc07 Mon Sep 17 00:00:00 2001 From: Kev <6111995+k-fish@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:12:04 -0500 Subject: [PATCH 16/31] Update packages/core/src/log.ts --- packages/core/src/log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index a0202f102622..af34a86a3cc3 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -75,7 +75,7 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { default: return { key, - value: { stringValue: JSON.stringify(value) }, + value: { stringValue: JSON.stringify(value) ?? "" }, }; } } From 6f3560cb5768a2ca757bf296e33e4ddce67c3e09 Mon Sep 17 00:00:00 2001 From: Kev Date: Tue, 4 Mar 2025 14:36:18 -0500 Subject: [PATCH 17/31] Use '.' for param so it renders more nicely in the log attribute tree structure --- packages/core/src/log.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index af34a86a3cc3..480ad85e9e39 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -75,7 +75,7 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { default: return { key, - value: { stringValue: JSON.stringify(value) ?? "" }, + value: { stringValue: JSON.stringify(value) ?? '' }, }; } } @@ -118,11 +118,11 @@ export function sendLog(level: LogSeverityLevel, messageArr: TemplateStringsArra const attributes = values.reduce>( (acc, value, index) => { - acc[`param${index}`] = value; + acc[`param.${index}`] = value; return acc; }, { - 'sentry.template': messageArr.map((s, i) => s + (i < messageArr.length - 1 ? `$param${i}` : '')).join(''), + 'sentry.template': messageArr.map((s, i) => s + (i < messageArr.length - 1 ? `$param.${i}` : '')).join(''), }, ); From 6215ad99866b110e7f8bd2744a10d86be1c674a0 Mon Sep 17 00:00:00 2001 From: Colin Chartier Date: Tue, 4 Mar 2025 14:45:59 -0500 Subject: [PATCH 18/31] json the parameters --- packages/core/src/log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 480ad85e9e39..08f03425a5e0 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -114,7 +114,7 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { * The other parameters are in the format to be passed a tagged template, Sentry.info`hello ${world}` */ export function sendLog(level: LogSeverityLevel, messageArr: TemplateStringsArray, ...values: unknown[]): void { - const message = messageArr.reduce((acc, str, i) => acc + str + (values[i] ?? ''), ''); + const message = messageArr.reduce((acc, str, i) => acc + str + (JSON.stringify(values[i]) ?? ''), ''); const attributes = values.reduce>( (acc, value, index) => { From 9048164c6a56714b8b5f6c90fa6c4e80c6183ecb Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 4 Mar 2025 15:05:23 -0500 Subject: [PATCH 19/31] align attributes with what was agreed --- packages/core/src/log.ts | 4 ++-- packages/eslint-config-sdk/src/base.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 08f03425a5e0..8e8971e694cc 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -118,11 +118,11 @@ export function sendLog(level: LogSeverityLevel, messageArr: TemplateStringsArra const attributes = values.reduce>( (acc, value, index) => { - acc[`param.${index}`] = value; + acc[`sentry.message.parameters.${index}`] = value; return acc; }, { - 'sentry.template': messageArr.map((s, i) => s + (i < messageArr.length - 1 ? `$param.${i}` : '')).join(''), + 'sentry.message.template': messageArr.map((s, i) => s + (i < messageArr.length - 1 ? `$param.${i}` : '')).join(''), }, ); diff --git a/packages/eslint-config-sdk/src/base.js b/packages/eslint-config-sdk/src/base.js index 8c11f26dd925..0b513b7316ba 100644 --- a/packages/eslint-config-sdk/src/base.js +++ b/packages/eslint-config-sdk/src/base.js @@ -84,7 +84,7 @@ module.exports = { // Make sure all expressions are used. Turned off in tests // Must disable base rule to prevent false positives 'no-unused-expressions': 'off', - '@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true }], + '@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true, allowTaggedTemplates: true }], // Make sure Promises are handled appropriately '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: false }], From a8673b7a01bbebf9ae555ef39dcc8331ea536c32 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 4 Mar 2025 15:14:56 -0500 Subject: [PATCH 20/31] fix: Duplicate export identifier --- packages/core/src/types-hoist/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/core/src/types-hoist/index.ts b/packages/core/src/types-hoist/index.ts index 899a2aa62a01..95bc5fc55bd4 100644 --- a/packages/core/src/types-hoist/index.ts +++ b/packages/core/src/types-hoist/index.ts @@ -109,12 +109,6 @@ export type { SpanContextData, TraceFlag, } from './span'; -export type { - Log, - LogAttribute, - LogSeverityLevel, - LogAttributeValueType, -} from './log'; export type { SpanStatus } from './spanStatus'; export type { Log, LogAttribute, LogSeverityLevel, LogAttributeValueType } from './log'; export type { TimedEvent } from './timedEvent'; From e9fa3bc7fd45d403bfd9f6cf9192d72e18edab02 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 4 Mar 2025 16:03:25 -0500 Subject: [PATCH 21/31] ref: introduce format function and restructure exports --- packages/core/src/exports.ts | 85 ++++++++++++++++++++++++++++-------- packages/core/src/log.ts | 63 ++++++++++++++------------ 2 files changed, 102 insertions(+), 46 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index c64bc1a86a48..aa173f5e5237 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -11,7 +11,6 @@ import type { Extra, Extras, FinishedCheckIn, - LogSeverityLevel, MonitorConfig, Primitive, Session, @@ -24,6 +23,7 @@ import { logger } from './utils-hoist/logger'; import { uuid4 } from './utils-hoist/misc'; import { timestampInSeconds } from './utils-hoist/time'; import { GLOBAL_OBJ } from './utils-hoist/worldwide'; +import { parameterize } from './utils/parameterize'; import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; import { parseEventHintOrCaptureContext } from './utils/prepareEvent'; @@ -337,8 +337,6 @@ export function captureSession(end: boolean = false): void { _sendSessionUpdate(); } -type OmitFirstArg = F extends (x: LogSeverityLevel, ...args: infer P) => infer R ? (...args: P) => R : never; - /** * A namespace for experimental logging functions. * @@ -349,55 +347,106 @@ export const _experiment_log = { * A utility to record a log with level 'TRACE' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: Sentry._experiment_log.trace`user ${username} just bought ${item}!` + * + * @example + * ```js + * const { trace, fmt } = Sentry._experiment_log; + * trace(fmt`user ${username} just bought ${item}!`); + * ``` */ - trace: sendLog.bind(null, 'trace') as OmitFirstArg, + trace: sendLog('trace'), /** * A utility to record a log with level 'DEBUG' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: Sentry._experiment_log.debug`user ${username} just bought ${item}!` + * + * @example + * ```js + * const { debug, fmt } = Sentry._experiment_log; + * debug(fmt`user ${username} just bought ${item}!`); + * ``` */ - debug: sendLog.bind(null, 'debug') as OmitFirstArg, + debug: sendLog('debug'), /** * A utility to record a log with level 'INFO' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: Sentry._experiment_log.info`user ${username} just bought ${item}!` + * + * @example + * ```js + * const { info, fmt } = Sentry._experiment_log; + * info(fmt`user ${username} just bought ${item}!`); + * ``` */ - info: sendLog.bind(null, 'info') as OmitFirstArg, + info: sendLog('info'), /** * A utility to record a log with level 'INFO' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: Sentry._experiment_log.log`user ${username} just bought ${item}!` + * + * @example + * ```js + * const { log, fmt } = Sentry._experiment_log; + * log(fmt`user ${username} just bought ${item}!`); + * ``` */ - log: sendLog.bind(null, 'log') as OmitFirstArg, + log: sendLog('info', 10), /** * A utility to record a log with level 'ERROR' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: Sentry._experiment_log.error`user ${username} just bought ${item}!` + * + * @example + * ```js + * const { error, fmt } = Sentry._experiment_log; + * error(fmt`user ${username} just bought ${item}!`); + * ``` */ - error: sendLog.bind(null, 'error') as OmitFirstArg, + error: sendLog('error'), /** * A utility to record a log with level 'WARN' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: Sentry._experiment_log.warn`user ${username} just bought ${item}!` + * + * @example + * ```js + * const { warn, fmt } = Sentry._experiment_log; + * warn(fmt`user ${username} just bought ${item}!`); + * ``` */ - warn: sendLog.bind(null, 'warn') as OmitFirstArg, + warn: sendLog('warn'), /** * A utility to record a log with level 'FATAL' and send it to sentry. * * Logs represent a message and some parameters which provide context for a trace or error. - * Ex: Sentry._experiment_log.warn`user ${username} just bought ${item}!` + * + * @example + * ```js + * const { fatal, fmt } = Sentry._experiment_log; + * fatal(fmt`user ${username} just bought ${item}!`); + * ``` */ - fatal: sendLog.bind(null, 'fatal') as OmitFirstArg, + fatal: sendLog('fatal'), + + /** + * Tagged template function which returns parameterized representation of the message + * + * @example + * ```js + * Sentry._experiment_log.fmt`This is a log statement with ${x} and ${y} params` + * ``` + */ + fmt: parameterize, + /** * A flexible utility to record a log with a custom level and send it to sentry. * * You can optionally pass in custom attributes and a custom severity number to be attached to the log. + * + * @example + * ```js + * Sentry._experiment_log.emit({ level: 'info', message: Sentry._experiment_log.fmt`user ${username }just bought ${item}`, attributes: { extra: 123 } }) + * ``` */ - captureLog, + emit: captureLog, }; diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 8e8971e694cc..c23e08494b5d 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -3,9 +3,10 @@ import { getClient, getCurrentScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { Scope } from './scope'; import { getDynamicSamplingContextFromScope } from './tracing'; +import type { ParameterizedString } from './types-hoist'; import type { DynamicSamplingContext, LogEnvelope, LogItem } from './types-hoist/envelope'; import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/log'; -import { createEnvelope, dropUndefinedKeys, dsnToString, logger } from './utils-hoist'; +import { createEnvelope, dropUndefinedKeys, dsnToString, isParameterizedString, logger } from './utils-hoist'; const LOG_BUFFER_MAX_LENGTH = 25; @@ -108,36 +109,30 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { } /** - * A utility function to be able to create methods like Sentry.info`...` that use tagged template functions. - * - * The first parameter is bound with, e.g., const info = captureLog.bind(null, 'info') - * The other parameters are in the format to be passed a tagged template, Sentry.info`hello ${world}` + * A utility function to be able to create methods like Sentry.info(...). */ -export function sendLog(level: LogSeverityLevel, messageArr: TemplateStringsArray, ...values: unknown[]): void { - const message = messageArr.reduce((acc, str, i) => acc + str + (JSON.stringify(values[i]) ?? ''), ''); - - const attributes = values.reduce>( - (acc, value, index) => { - acc[`sentry.message.parameters.${index}`] = value; - return acc; - }, - { - 'sentry.message.template': messageArr.map((s, i) => s + (i < messageArr.length - 1 ? `$param.${i}` : '')).join(''), - }, - ); - - captureLog(level, message, attributes); +export function sendLog( + level: LogSeverityLevel, + severityNumber?: number, +): (message: ParameterizedString | string, customAttributes?: Record) => void { + return (message: ParameterizedString | string, attributes: Record = {}): void => + captureLog({ level, message, attributes, severityNumber }); } /** * Sends a log to Sentry. */ -export function captureLog( - level: LogSeverityLevel, - message: string, - customAttributes: Record = {}, - severityNumber?: number, -): void { +export function captureLog({ + level, + message, + attributes, + severityNumber, +}: { + level: LogSeverityLevel; + message: ParameterizedString | string; + attributes?: Record; + severityNumber?: number; +}): void { const client = getClient(); if (!client) { @@ -153,9 +148,19 @@ export function captureLog( const { release, environment } = client.getOptions(); const logAttributes = { - ...customAttributes, + ...attributes, }; + if (isParameterizedString(message)) { + const { __sentry_template_string__ = '', __sentry_template_values__ = [] } = message; + if (__sentry_template_string__) { + logAttributes['sentry.message.template'] = __sentry_template_string__; + __sentry_template_values__.forEach((value, index) => { + logAttributes[`sentry.message.parameters.${index}`] = value; + }); + } + } + if (release) { logAttributes['sentry.release'] = release; } @@ -166,14 +171,16 @@ export function captureLog( const scope = getCurrentScope(); - const attributes = Object.entries(logAttributes).map(([key, value]) => valueToAttribute(key, value)); + const finalAttributes = Object.entries(logAttributes).map(([key, value]) => + valueToAttribute(key, value), + ); const log: Log = { severityText: level, body: { stringValue: message, }, - attributes, + attributes: finalAttributes, timeUnixNano: `${new Date().getTime().toString()}000000`, traceId: scope.getPropagationContext().traceId, severityNumber, From cdf8bc5e1a1151ee233901d778bcb87927d37558 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 5 Mar 2025 10:20:11 -0500 Subject: [PATCH 22/31] chore: bump size-limit --- .size-limit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index d312dbcdac3b..3f4bd184a537 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -15,7 +15,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init'), gzip: true, - limit: '24.1 KB', + limit: '24.5 KB', modifyWebpackConfig: function (config) { const webpack = require('webpack'); const TerserPlugin = require('terser-webpack-plugin'); @@ -100,7 +100,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'sendFeedback'), gzip: true, - limit: '29 KB', + limit: '30 KB', }, { name: '@sentry/browser (incl. FeedbackAsync)', @@ -219,7 +219,7 @@ module.exports = [ import: createImport('init'), ignore: ['$app/stores'], gzip: true, - limit: '38 KB', + limit: '39 KB', }, // Node SDK (ESM) { From 0e7740777e341729e37d332172ee646dd0e79a11 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 6 Mar 2025 10:46:35 -0500 Subject: [PATCH 23/31] fix: ParamaterizeString typing to allow more than string values --- packages/core/src/exports.ts | 5 +++-- packages/core/src/log.ts | 6 +++--- packages/core/src/types-hoist/parameterize.ts | 4 ++-- packages/core/src/utils/parameterize.ts | 19 +++++++++++++++++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index aa173f5e5237..a345dbff1654 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -12,6 +12,7 @@ import type { Extras, FinishedCheckIn, MonitorConfig, + ParameterizedString, Primitive, Session, SessionContext, @@ -23,7 +24,7 @@ import { logger } from './utils-hoist/logger'; import { uuid4 } from './utils-hoist/misc'; import { timestampInSeconds } from './utils-hoist/time'; import { GLOBAL_OBJ } from './utils-hoist/worldwide'; -import { parameterize } from './utils/parameterize'; +import { parameterizeStringTemplate } from './utils/parameterize'; import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; import { parseEventHintOrCaptureContext } from './utils/prepareEvent'; @@ -436,7 +437,7 @@ export const _experiment_log = { * Sentry._experiment_log.fmt`This is a log statement with ${x} and ${y} params` * ``` */ - fmt: parameterize, + fmt: parameterizeStringTemplate, /** * A flexible utility to record a log with a custom level and send it to sentry. diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index c23e08494b5d..37d2bbfc7f2b 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -114,8 +114,8 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { export function sendLog( level: LogSeverityLevel, severityNumber?: number, -): (message: ParameterizedString | string, customAttributes?: Record) => void { - return (message: ParameterizedString | string, attributes: Record = {}): void => +): (message: ParameterizedString | string, customAttributes?: Record) => void { + return (message: ParameterizedString | string, attributes: Record = {}): void => captureLog({ level, message, attributes, severityNumber }); } @@ -129,7 +129,7 @@ export function captureLog({ severityNumber, }: { level: LogSeverityLevel; - message: ParameterizedString | string; + message: ParameterizedString | string; attributes?: Record; severityNumber?: number; }): void { diff --git a/packages/core/src/types-hoist/parameterize.ts b/packages/core/src/types-hoist/parameterize.ts index a94daa3684db..1c9f405ae62f 100644 --- a/packages/core/src/types-hoist/parameterize.ts +++ b/packages/core/src/types-hoist/parameterize.ts @@ -1,4 +1,4 @@ -export type ParameterizedString = string & { +export type ParameterizedString = string & { __sentry_template_string__?: string; - __sentry_template_values__?: string[]; + __sentry_template_values__?: Values; }; diff --git a/packages/core/src/utils/parameterize.ts b/packages/core/src/utils/parameterize.ts index 392f4b70b444..5a9cb29d6aeb 100644 --- a/packages/core/src/utils/parameterize.ts +++ b/packages/core/src/utils/parameterize.ts @@ -15,3 +15,22 @@ export function parameterize(strings: TemplateStringsArray, ...values: string[]) formatted.__sentry_template_values__ = values; return formatted; } + +/** + * Tagged template function which returns parameterized representation of the message + * For example: parameterize`This is a log statement with ${x} and ${y} params`, would return: + * "__sentry_template_string__": 'This is a log statement with %s and %s params', + * "__sentry_template_values__": ['first', 'second'] + * @param strings An array of string values splitted between expressions + * @param values Expressions extracted from template string + * @returns String with template information in __sentry_template_string__ and __sentry_template_values__ properties + */ +export function parameterizeStringTemplate( + strings: TemplateStringsArray, + ...values: unknown[] +): ParameterizedString { + const formatted = new String(String.raw(strings, ...values)) as ParameterizedString; + formatted.__sentry_template_string__ = strings.join('\x00').replace(/%/g, '%%').replace(/\0/g, '%s'); + formatted.__sentry_template_values__ = values; + return formatted; +} From 4d0678ade7d09624413cc7c8596ae7477e98d224 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 6 Mar 2025 10:47:03 -0500 Subject: [PATCH 24/31] feat: Add parent_span_id to outgoing logs --- packages/core/src/log.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 37d2bbfc7f2b..7965203a31d8 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -7,6 +7,7 @@ import type { ParameterizedString } from './types-hoist'; import type { DynamicSamplingContext, LogEnvelope, LogItem } from './types-hoist/envelope'; import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/log'; import { createEnvelope, dropUndefinedKeys, dsnToString, isParameterizedString, logger } from './utils-hoist'; +import { getActiveSpan, spanToJSON } from './utils/spanUtils'; const LOG_BUFFER_MAX_LENGTH = 25; @@ -161,6 +162,11 @@ export function captureLog({ } } + const span = getActiveSpan(); + if (span) { + logAttributes['sentry.trace.parent_span_id'] = spanToJSON(span).parent_span_id; + } + if (release) { logAttributes['sentry.release'] = release; } From 7b8315550b53665765e5133178dadf53bc524b8a Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 6 Mar 2025 11:03:03 -0500 Subject: [PATCH 25/31] feat: Export logging methods for node/server-side SDKs --- packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/cloudflare/src/index.ts | 1 + packages/core/src/exports.ts | 3 +-- packages/core/src/log.ts | 18 +++++++++++++++++- packages/deno/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + packages/remix/src/server/index.ts | 1 + packages/solidstart/src/server/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + packages/vercel-edge/src/index.ts | 1 + 13 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 6f9647d0134e..6364849d2b16 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -127,6 +127,7 @@ export { withScope, zodErrorsIntegration, profiler, + _experiment_log, } from '@sentry/node'; export { init } from './server/sdk'; diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 51848530712b..f3a1d2a3e476 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -113,6 +113,7 @@ export { profiler, amqplibIntegration, vercelAIIntegration, + _experiment_log, } from '@sentry/node'; export { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 52b3d9fa4c42..9714cfecfd6c 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -132,6 +132,7 @@ export { profiler, amqplibIntegration, vercelAIIntegration, + _experiment_log, } from '@sentry/node'; export { diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 05fd40fb4c96..2cfbca9bebc8 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -85,6 +85,7 @@ export { spanToTraceHeader, spanToBaggageHeader, updateSpanName, + _experiment_log, } from '@sentry/core'; export { withSentry } from './handler'; diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index a345dbff1654..b748375c7165 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -12,7 +12,6 @@ import type { Extras, FinishedCheckIn, MonitorConfig, - ParameterizedString, Primitive, Session, SessionContext, @@ -341,7 +340,7 @@ export function captureSession(end: boolean = false): void { /** * A namespace for experimental logging functions. * - * @experimental Will be removed in future versions. Do not use. + * @experimental Will be removed in future versions. Use with caution. */ export const _experiment_log = { /** diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 7965203a31d8..76d3ecbc2c55 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -82,6 +82,8 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { } } +let hasRegisteredFlushHook = false; + function addToLogBuffer(client: Client, log: Log, scope: Scope): void { function sendLogs(flushedLogs: Log[]): void { const envelope = createLogEnvelope(flushedLogs, client, scope); @@ -89,6 +91,15 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { void client.sendEnvelope(envelope); } + // Only register the hook once + if (!hasRegisteredFlushHook) { + client.on('flush', () => { + sendLogs(GLOBAL_LOG_BUFFER); + GLOBAL_LOG_BUFFER = []; + }); + hasRegisteredFlushHook = true; + } + if (GLOBAL_LOG_BUFFER.length >= LOG_BUFFER_MAX_LENGTH) { sendLogs(GLOBAL_LOG_BUFFER); GLOBAL_LOG_BUFFER = []; @@ -99,12 +110,17 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { // this is the first time logs have been enabled, let's kick off an interval to flush them // we should only do this once. if (!isFlushingLogs) { - setInterval(() => { + const flushTimer = setInterval(() => { if (GLOBAL_LOG_BUFFER.length > 0) { sendLogs(GLOBAL_LOG_BUFFER); GLOBAL_LOG_BUFFER = []; } }, 5000); + + // We need to unref the timer in node.js, otherwise the node process never exit. + if (typeof flushTimer !== 'number' && flushTimer.unref) { + flushTimer.unref(); + } } isFlushingLogs = true; } diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index a906197b40c2..c2fad35f5e2b 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -83,6 +83,7 @@ export { spanToTraceHeader, spanToBaggageHeader, updateSpanName, + _experiment_log, } from '@sentry/core'; export { DenoClient } from './client'; diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 9505ef6dd248..b0c8cb572744 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -113,6 +113,7 @@ export { amqplibIntegration, childProcessIntegration, vercelAIIntegration, + _experiment_log, } from '@sentry/node'; export { diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index bdc8d6405217..99de94d7c3b2 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -130,6 +130,7 @@ export { updateSpanName, zodErrorsIntegration, profiler, + _experiment_log, } from '@sentry/core'; export type { diff --git a/packages/remix/src/server/index.ts b/packages/remix/src/server/index.ts index 4160a871d165..af0cfe09be9a 100644 --- a/packages/remix/src/server/index.ts +++ b/packages/remix/src/server/index.ts @@ -112,6 +112,7 @@ export { withMonitor, withScope, zodErrorsIntegration, + _experiment_log, } from '@sentry/node'; // Keeping the `*` exports for backwards compatibility and types diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index 948c3c746d0c..877bd4f312c2 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -115,6 +115,7 @@ export { withMonitor, withScope, zodErrorsIntegration, + _experiment_log, } from '@sentry/node'; // We can still leave this for the carrier init and type exports diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index f8844c1e264d..75fe460bc841 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -117,6 +117,7 @@ export { withMonitor, withScope, zodErrorsIntegration, + _experiment_log, } from '@sentry/node'; // We can still leave this for the carrier init and type exports diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index eb6429c441fa..fef448433c29 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -84,6 +84,7 @@ export { spanToJSON, spanToTraceHeader, spanToBaggageHeader, + _experiment_log, } from '@sentry/core'; export { VercelEdgeClient } from './client'; From 69947819a9e0323b6de2a699b9fa667cf31254c8 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 6 Mar 2025 11:28:10 -0500 Subject: [PATCH 26/31] fix: Set parent_span_id properly --- packages/core/src/log.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 76d3ecbc2c55..191bba212545 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -82,8 +82,6 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { } } -let hasRegisteredFlushHook = false; - function addToLogBuffer(client: Client, log: Log, scope: Scope): void { function sendLogs(flushedLogs: Log[]): void { const envelope = createLogEnvelope(flushedLogs, client, scope); @@ -91,18 +89,15 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { void client.sendEnvelope(envelope); } - // Only register the hook once - if (!hasRegisteredFlushHook) { - client.on('flush', () => { + function sendAndClearLogs(): void { + if (GLOBAL_LOG_BUFFER.length > 0) { sendLogs(GLOBAL_LOG_BUFFER); GLOBAL_LOG_BUFFER = []; - }); - hasRegisteredFlushHook = true; + } } if (GLOBAL_LOG_BUFFER.length >= LOG_BUFFER_MAX_LENGTH) { - sendLogs(GLOBAL_LOG_BUFFER); - GLOBAL_LOG_BUFFER = []; + sendAndClearLogs(); } else { GLOBAL_LOG_BUFFER.push(log); } @@ -110,11 +105,12 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { // this is the first time logs have been enabled, let's kick off an interval to flush them // we should only do this once. if (!isFlushingLogs) { + client.on('flush', () => { + sendAndClearLogs(); + }); + const flushTimer = setInterval(() => { - if (GLOBAL_LOG_BUFFER.length > 0) { - sendLogs(GLOBAL_LOG_BUFFER); - GLOBAL_LOG_BUFFER = []; - } + sendAndClearLogs(); }, 5000); // We need to unref the timer in node.js, otherwise the node process never exit. @@ -180,7 +176,7 @@ export function captureLog({ const span = getActiveSpan(); if (span) { - logAttributes['sentry.trace.parent_span_id'] = spanToJSON(span).parent_span_id; + logAttributes['sentry.trace.parent_span_id'] = span.spanContext().spanId; } if (release) { From 8602f81fdc16a86175a534afa528011a36f97bc2 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 6 Mar 2025 11:35:44 -0500 Subject: [PATCH 27/31] fix: Remove unneeded import --- packages/core/src/log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 191bba212545..dd346cc19114 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -7,7 +7,7 @@ import type { ParameterizedString } from './types-hoist'; import type { DynamicSamplingContext, LogEnvelope, LogItem } from './types-hoist/envelope'; import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/log'; import { createEnvelope, dropUndefinedKeys, dsnToString, isParameterizedString, logger } from './utils-hoist'; -import { getActiveSpan, spanToJSON } from './utils/spanUtils'; +import { getActiveSpan } from './utils/spanUtils'; const LOG_BUFFER_MAX_LENGTH = 25; From 7d22aa284bf93554c3396d0b98f87761547bb26d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 6 Mar 2025 12:23:01 -0500 Subject: [PATCH 28/31] chore: bump size-limit --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 3f4bd184a537..1ff20d60a62e 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -107,7 +107,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'feedbackAsyncIntegration'), gzip: true, - limit: '34 KB', + limit: '34.5 KB', }, // React SDK (ESM) { From c4b6d2d9bf512fe26832b7bee4b5a4592d39a21d Mon Sep 17 00:00:00 2001 From: Colin Chartier Date: Thu, 13 Mar 2025 13:58:11 -0400 Subject: [PATCH 29/31] feat(buffer): Add beforeSendLogs for sampling decisions --- packages/core/src/log.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index dd346cc19114..c33a0d778fa3 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -12,6 +12,7 @@ import { getActiveSpan } from './utils/spanUtils'; const LOG_BUFFER_MAX_LENGTH = 25; let GLOBAL_LOG_BUFFER: Log[] = []; +let GLOBAL_LOG_BUFFER_INDEX = 0; let isFlushingLogs = false; @@ -89,15 +90,16 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { void client.sendEnvelope(envelope); } - function sendAndClearLogs(): void { - if (GLOBAL_LOG_BUFFER.length > 0) { + if (GLOBAL_LOG_BUFFER.length >= LOG_BUFFER_MAX_LENGTH) { + const shouldSendLogs = client.getOptions()?._experiments?.shouldSendLogs; + if(!shouldSendLogs || shouldSendLogs?.(GLOBAL_LOG_BUFFER, true)) { // TODO: implement error tracking sendLogs(GLOBAL_LOG_BUFFER); - GLOBAL_LOG_BUFFER = []; + GLOBAL_LOG_BUFFER.length = 0; + } else { + // we should not send the logs buffer, evict a single log to make space for this one. + GLOBAL_LOG_BUFFER[GLOBAL_LOG_BUFFER_INDEX] = log; + GLOBAL_LOG_BUFFER_INDEX = (GLOBAL_LOG_BUFFER_INDEX + 1) % LOG_BUFFER_MAX_LENGTH; } - } - - if (GLOBAL_LOG_BUFFER.length >= LOG_BUFFER_MAX_LENGTH) { - sendAndClearLogs(); } else { GLOBAL_LOG_BUFFER.push(log); } @@ -105,12 +107,19 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { // this is the first time logs have been enabled, let's kick off an interval to flush them // we should only do this once. if (!isFlushingLogs) { + const tryFlushLogs = (): void => { + const shouldSendLogs = client.getOptions()?._experiments?.shouldSendLogs; + if (!shouldSendLogs || shouldSendLogs?.(GLOBAL_LOG_BUFFER, true)) { // TODO: implement error tracking + sendLogs(GLOBAL_LOG_BUFFER); + GLOBAL_LOG_BUFFER.length = 0; + } + } client.on('flush', () => { - sendAndClearLogs(); + tryFlushLogs(); }); const flushTimer = setInterval(() => { - sendAndClearLogs(); + tryFlushLogs(); }, 5000); // We need to unref the timer in node.js, otherwise the node process never exit. From 1da9a7f4d37f387d79a3be181cc8e18bd6fe744c Mon Sep 17 00:00:00 2001 From: Colin Chartier Date: Thu, 13 Mar 2025 15:06:45 -0400 Subject: [PATCH 30/31] clean up --- packages/core/src/log.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index c33a0d778fa3..743a16ca45c7 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -1,5 +1,5 @@ import type { Client } from './client'; -import { getClient, getCurrentScope } from './currentScopes'; +import { getClient, getCurrentScope, getIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { Scope } from './scope'; import { getDynamicSamplingContextFromScope } from './tracing'; @@ -11,8 +11,8 @@ import { getActiveSpan } from './utils/spanUtils'; const LOG_BUFFER_MAX_LENGTH = 25; -let GLOBAL_LOG_BUFFER: Log[] = []; -let GLOBAL_LOG_BUFFER_INDEX = 0; +const GLOBAL_LOG_BUFFER: Log[] = []; +let globalLogBufferIndex = 0; let isFlushingLogs = false; @@ -83,7 +83,7 @@ function valueToAttribute(key: string, value: unknown): LogAttribute { } } -function addToLogBuffer(client: Client, log: Log, scope: Scope): void { +function addToLogBuffer(client: Client, log: Log, scope: Scope, isolationScope: Scope): void { function sendLogs(flushedLogs: Log[]): void { const envelope = createLogEnvelope(flushedLogs, client, scope); // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -91,14 +91,15 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { } if (GLOBAL_LOG_BUFFER.length >= LOG_BUFFER_MAX_LENGTH) { - const shouldSendLogs = client.getOptions()?._experiments?.shouldSendLogs; - if(!shouldSendLogs || shouldSendLogs?.(GLOBAL_LOG_BUFFER, true)) { // TODO: implement error tracking + const beforeSendLogs = client.getOptions()?._experiments?.beforeSendLogs; + if(!beforeSendLogs || beforeSendLogs?.(GLOBAL_LOG_BUFFER, isolationScope.lastEventId())) { sendLogs(GLOBAL_LOG_BUFFER); GLOBAL_LOG_BUFFER.length = 0; + globalLogBufferIndex = 0; } else { // we should not send the logs buffer, evict a single log to make space for this one. - GLOBAL_LOG_BUFFER[GLOBAL_LOG_BUFFER_INDEX] = log; - GLOBAL_LOG_BUFFER_INDEX = (GLOBAL_LOG_BUFFER_INDEX + 1) % LOG_BUFFER_MAX_LENGTH; + GLOBAL_LOG_BUFFER[globalLogBufferIndex] = log; + globalLogBufferIndex = (globalLogBufferIndex + 1) % LOG_BUFFER_MAX_LENGTH; } } else { GLOBAL_LOG_BUFFER.push(log); @@ -108,10 +109,11 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope): void { // we should only do this once. if (!isFlushingLogs) { const tryFlushLogs = (): void => { - const shouldSendLogs = client.getOptions()?._experiments?.shouldSendLogs; - if (!shouldSendLogs || shouldSendLogs?.(GLOBAL_LOG_BUFFER, true)) { // TODO: implement error tracking + const beforeSendLogs = client.getOptions()?._experiments?.beforeSendLogs; + if (!beforeSendLogs || beforeSendLogs?.(GLOBAL_LOG_BUFFER, isolationScope.lastEventId())) { // TODO: implement error tracking sendLogs(GLOBAL_LOG_BUFFER); GLOBAL_LOG_BUFFER.length = 0; + globalLogBufferIndex = 0; } } client.on('flush', () => { @@ -197,6 +199,7 @@ export function captureLog({ } const scope = getCurrentScope(); + const isolationScope = getIsolationScope(); const finalAttributes = Object.entries(logAttributes).map(([key, value]) => valueToAttribute(key, value), @@ -218,5 +221,5 @@ export function captureLog({ log.severityNumber = maybeSeverityNumber; } - addToLogBuffer(client, log, scope); + addToLogBuffer(client, log, scope, isolationScope); } From 5b66ccf3abd1789d171cb0d8c028599d541d8e33 Mon Sep 17 00:00:00 2001 From: Colin Chartier Date: Thu, 13 Mar 2025 15:30:51 -0400 Subject: [PATCH 31/31] remove old comment --- packages/core/src/log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/log.ts b/packages/core/src/log.ts index 743a16ca45c7..58798e6ff256 100644 --- a/packages/core/src/log.ts +++ b/packages/core/src/log.ts @@ -110,7 +110,7 @@ function addToLogBuffer(client: Client, log: Log, scope: Scope, isolationScope: if (!isFlushingLogs) { const tryFlushLogs = (): void => { const beforeSendLogs = client.getOptions()?._experiments?.beforeSendLogs; - if (!beforeSendLogs || beforeSendLogs?.(GLOBAL_LOG_BUFFER, isolationScope.lastEventId())) { // TODO: implement error tracking + if (!beforeSendLogs || beforeSendLogs?.(GLOBAL_LOG_BUFFER, isolationScope.lastEventId())) { sendLogs(GLOBAL_LOG_BUFFER); GLOBAL_LOG_BUFFER.length = 0; globalLogBufferIndex = 0;