@@ -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 ( ) }  , 
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