@@ -28,6 +28,7 @@ import type {
2828 SpanContextData ,
2929 SpanJSON ,
3030 StartSpanOptions ,
31+ TraceContext ,
3132 TransactionEvent ,
3233 Transport ,
3334 TransportMakeRequestResponse ,
@@ -44,7 +45,10 @@ import { afterSetupIntegrations } from './integration';
4445import { setupIntegration , setupIntegrations } from './integration' ;
4546import type { Scope } from './scope' ;
4647import { updateSession } from './session' ;
47- import { getDynamicSamplingContextFromScope } from './tracing/dynamicSamplingContext' ;
48+ import {
49+ getDynamicSamplingContextFromScope ,
50+ getDynamicSamplingContextFromSpan ,
51+ } from './tracing/dynamicSamplingContext' ;
4852import { createClientReportEnvelope } from './utils-hoist/clientreport' ;
4953import { dsnToString , makeDsn } from './utils-hoist/dsn' ;
5054import { addItemToEnvelope , createAttachmentEnvelopeItem } from './utils-hoist/envelope' ;
@@ -57,11 +61,15 @@ import { getPossibleEventMessages } from './utils/eventUtils';
5761import { merge } from './utils/merge' ;
5862import { parseSampleRate } from './utils/parseSampleRate' ;
5963import { prepareEvent } from './utils/prepareEvent' ;
60- import { showSpanDropWarning } from './utils/spanUtils' ;
64+ import { showSpanDropWarning , spanToTraceContext } from './utils/spanUtils' ;
6165import { convertSpanJsonToTransactionEvent , convertTransactionEventToSpanJson } from './utils/transactionEvent' ;
66+ import type { Log , SerializedOtelLog } from './types-hoist/log' ;
67+ import { SEVERITY_TEXT_TO_SEVERITY_NUMBER , createOtelLogEnvelope , logAttributeToSerializedLogAttribute } from './log' ;
68+ import { _getSpanForScope } from './utils/spanOnScope' ;
6269
6370const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured." ;
6471const MISSING_RELEASE_FOR_SESSION_ERROR = 'Discarded session because of missing or non-string release' ;
72+ const MAX_LOG_BUFFER_SIZE = 100 ;
6573
6674/**
6775 * Base implementation for all JavaScript SDK clients.
@@ -117,6 +125,8 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
117125 // eslint-disable-next-line @typescript-eslint/ban-types
118126 private _hooks : Record < string , Function [ ] > ;
119127
128+ private _logsBuffer : Array < SerializedOtelLog > ;
129+
120130 /**
121131 * Initializes this client instance.
122132 *
@@ -129,6 +139,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
129139 this . _outcomes = { } ;
130140 this . _hooks = { } ;
131141 this . _eventProcessors = [ ] ;
142+ this . _logsBuffer = [ ] ;
132143
133144 if ( options . dsn ) {
134145 this . _dsn = makeDsn ( options . dsn ) ;
@@ -256,6 +267,58 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
256267 */
257268 public captureCheckIn ?( checkIn : CheckIn , monitorConfig ?: MonitorConfig , scope ?: Scope ) : string ;
258269
270+ /**
271+ * Captures a log event and sends it to Sentry.
272+ *
273+ * @param log The log event to capture.
274+ *
275+ * @experimental This method will experience breaking changes. This is not yet part of
276+ * the stable Sentry SDK API and can be changed or removed without warning.
277+ */
278+ public captureLog ( { level, message, attributes, severityNumber } : Log , currentScope = getCurrentScope ( ) ) : void {
279+ const { _experiments, release, environment } = this . getOptions ( ) ;
280+ if ( ! _experiments ?. enableLogs ) {
281+ DEBUG_BUILD && logger . warn ( 'logging option not enabled, log will not be captured.' ) ;
282+ return ;
283+ }
284+
285+ const [ , traceContext ] = _getTraceInfoFromScope ( this , currentScope ) ;
286+
287+ const logAttributes = {
288+ ...attributes ,
289+ } ;
290+
291+ if ( release ) {
292+ logAttributes . release = release ;
293+ }
294+
295+ if ( environment ) {
296+ logAttributes . environment = environment ;
297+ }
298+
299+ const span = _getSpanForScope ( currentScope ) ;
300+ if ( span ) {
301+ // Add the parent span ID to the log attributes for trace context
302+ logAttributes [ 'sentry.trace.parent_span_id' ] = span . spanContext ( ) . spanId ;
303+ }
304+
305+ const serializedLog : SerializedOtelLog = {
306+ severityText : level ,
307+ body : {
308+ stringValue : message ,
309+ } ,
310+ attributes : Object . entries ( logAttributes ) . map ( ( [ key , value ] ) => logAttributeToSerializedLogAttribute ( key , value ) ) ,
311+ timeUnixNano : `${ new Date ( ) . getTime ( ) . toString ( ) } 000000` ,
312+ traceId : traceContext ?. trace_id ,
313+ severityNumber : severityNumber ?? SEVERITY_TEXT_TO_SEVERITY_NUMBER [ level ] ,
314+ } ;
315+
316+ this . _logsBuffer . push ( serializedLog ) ;
317+ if ( this . _logsBuffer . length > MAX_LOG_BUFFER_SIZE ) {
318+ this . _flushLogsBuffer ( ) ;
319+ }
320+ }
321+
259322 /**
260323 * Get the current Dsn.
261324 */
@@ -295,6 +358,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
295358 * still events in the queue when the timeout is reached.
296359 */
297360 public flush ( timeout ?: number ) : PromiseLike < boolean > {
361+ this . _flushLogsBuffer ( ) ;
298362 const transport = this . _transport ;
299363 if ( transport ) {
300364 this . emit ( 'flush' ) ;
@@ -1136,6 +1200,21 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
11361200 this . sendEnvelope ( envelope ) ;
11371201 }
11381202
1203+ /**
1204+ * Flushes the logs buffer to Sentry.
1205+ */
1206+ protected _flushLogsBuffer ( ) : void {
1207+ if ( this . _logsBuffer . length === 0 ) {
1208+ return ;
1209+ }
1210+
1211+ const envelope = createOtelLogEnvelope ( this . _logsBuffer , this . _options . _metadata , this . _options . tunnel , this . _dsn ) ;
1212+ this . _logsBuffer = [ ] ;
1213+ // sendEnvelope should not throw
1214+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1215+ this . sendEnvelope ( envelope ) ;
1216+ }
1217+
11391218 /**
11401219 * Creates an {@link Event} from all inputs to `captureException` and non-primitive inputs to `captureMessage`.
11411220 */
@@ -1256,3 +1335,20 @@ function isErrorEvent(event: Event): event is ErrorEvent {
12561335function isTransactionEvent ( event : Event ) : event is TransactionEvent {
12571336 return event . type === 'transaction' ;
12581337}
1338+
1339+ /** Extract trace information from scope */
1340+ export function _getTraceInfoFromScope (
1341+ client : Client ,
1342+ scope : Scope | undefined ,
1343+ ) : [ dynamicSamplingContext : Partial < DynamicSamplingContext > | undefined , traceContext : TraceContext | undefined ] {
1344+ if ( ! scope ) {
1345+ return [ undefined , undefined ] ;
1346+ }
1347+
1348+ const span = _getSpanForScope ( scope ) ;
1349+ const traceContext = span ? spanToTraceContext ( span ) : getTraceContextFromScope ( scope ) ;
1350+ const dynamicSamplingContext = span
1351+ ? getDynamicSamplingContextFromSpan ( span )
1352+ : getDynamicSamplingContextFromScope ( client , scope ) ;
1353+ return [ dynamicSamplingContext , traceContext ] ;
1354+ }
0 commit comments