@@ -19,6 +19,7 @@ import (
1919 "github.com/getsentry/sentry-go/internal/protocol"
2020 "github.com/getsentry/sentry-go/internal/ratelimit"
2121 "github.com/getsentry/sentry-go/internal/telemetry"
22+ "github.com/getsentry/sentry-go/report"
2223)
2324
2425// The identifier of the SDK.
@@ -258,6 +259,8 @@ type ClientOptions struct {
258259 EnableLogs bool
259260 // DisableMetrics controls when metrics should be emitted.
260261 DisableMetrics bool
262+ // DisableClientReports controls when client reports should be emitted.
263+ DisableClientReports bool
261264 // TraceIgnoreStatusCodes is a list of HTTP status codes that should not be traced.
262265 // Each element can be either:
263266 // - A single-element slice [code] for a specific status code
@@ -296,6 +299,8 @@ type Client struct {
296299 batchLogger * logBatchProcessor
297300 batchMeter * metricBatchProcessor
298301 telemetryProcessor * telemetry.Processor
302+ reportRecorder report.ClientReportRecorder
303+ reportProvider report.ClientReportProvider
299304}
300305
301306// NewClient creates and returns an instance of Client configured using
@@ -393,10 +398,18 @@ func NewClient(options ClientOptions) (*Client, error) {
393398 }
394399
395400 client := Client {
396- options : options ,
397- dsn : dsn ,
398- sdkIdentifier : sdkIdentifier ,
399- sdkVersion : SDKVersion ,
401+ options : options ,
402+ dsn : dsn ,
403+ sdkIdentifier : sdkIdentifier ,
404+ sdkVersion : SDKVersion ,
405+ reportRecorder : report .NoopRecorder (),
406+ reportProvider : report .NoopProvider (),
407+ }
408+
409+ if ! options .DisableClientReports {
410+ a := report .NewAggregator ()
411+ client .reportRecorder = a
412+ client .reportProvider = a
400413 }
401414
402415 client .setupTransport ()
@@ -430,7 +443,23 @@ func (client *Client) setupTransport() {
430443 if opts .Dsn == "" {
431444 transport = new (noopTransport )
432445 } else {
433- transport = NewHTTPTransport ()
446+ httpTransport := NewHTTPTransport ()
447+ httpTransport .recorder = client .reportRecorder
448+ httpTransport .provider = client .reportProvider
449+ transport = httpTransport
450+ }
451+ } else {
452+ // For known transport types, inject the client report interfaces.
453+ switch tr := transport .(type ) {
454+ case * HTTPTransport :
455+ tr .recorder = client .reportRecorder
456+ tr .provider = client .reportProvider
457+ case * HTTPSyncTransport :
458+ tr .recorder = client .reportRecorder
459+ tr .provider = client .reportProvider
460+ case * internalAsyncTransportAdapter :
461+ tr .recorder = client .reportRecorder
462+ tr .provider = client .reportProvider
434463 }
435464 }
436465
@@ -470,23 +499,25 @@ func (client *Client) setupTelemetryProcessor() { // nolint: unused
470499 HTTPProxy : client .options .HTTPProxy ,
471500 HTTPSProxy : client .options .HTTPSProxy ,
472501 CaCerts : client .options .CaCerts ,
502+ Recorder : client .reportRecorder ,
503+ Provider : client .reportProvider ,
473504 })
474505 client .Transport = & internalAsyncTransportAdapter {transport : transport }
475506
476507 buffers := map [ratelimit.Category ]telemetry.Buffer [protocol.TelemetryItem ]{
477- ratelimit .CategoryError : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryError , 100 , telemetry .OverflowPolicyDropOldest , 1 , 0 ),
478- ratelimit .CategoryTransaction : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryTransaction , 1000 , telemetry .OverflowPolicyDropOldest , 1 , 0 ),
479- ratelimit .CategoryLog : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryLog , 10 * 100 , telemetry .OverflowPolicyDropOldest , 100 , 5 * time .Second ),
480- ratelimit .CategoryMonitor : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryMonitor , 100 , telemetry .OverflowPolicyDropOldest , 1 , 0 ),
481- ratelimit .CategoryTraceMetric : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryTraceMetric , 10 * 100 , telemetry .OverflowPolicyDropOldest , 100 , 5 * time .Second ),
508+ ratelimit .CategoryError : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryError , 100 , telemetry .OverflowPolicyDropOldest , 1 , 0 , client . reportRecorder ),
509+ ratelimit .CategoryTransaction : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryTransaction , 1000 , telemetry .OverflowPolicyDropOldest , 1 , 0 , client . reportRecorder ),
510+ ratelimit .CategoryLog : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryLog , 10 * 100 , telemetry .OverflowPolicyDropOldest , 100 , 5 * time .Second , client . reportRecorder ),
511+ ratelimit .CategoryMonitor : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryMonitor , 100 , telemetry .OverflowPolicyDropOldest , 1 , 0 , client . reportRecorder ),
512+ ratelimit .CategoryTraceMetric : telemetry .NewRingBuffer [protocol.TelemetryItem ](ratelimit .CategoryTraceMetric , 10 * 100 , telemetry .OverflowPolicyDropOldest , 100 , 5 * time .Second , client . reportRecorder ),
482513 }
483514
484515 sdkInfo := & protocol.SdkInfo {
485516 Name : client .sdkIdentifier ,
486517 Version : client .sdkVersion ,
487518 }
488519
489- client .telemetryProcessor = telemetry .NewProcessor (buffers , transport , & client .dsn .Dsn , sdkInfo )
520+ client .telemetryProcessor = telemetry .NewProcessor (buffers , transport , & client .dsn .Dsn , sdkInfo , client . reportRecorder )
490521}
491522
492523func (client * Client ) setupIntegrations () {
@@ -572,21 +603,27 @@ func (client *Client) captureLog(log *Log, _ *Scope) bool {
572603 }
573604
574605 if client .options .BeforeSendLog != nil {
606+ approxSize := log .ApproximateSize ()
575607 log = client .options .BeforeSendLog (log )
576608 if log == nil {
577609 debuglog .Println ("Log dropped due to BeforeSendLog callback." )
610+ client .reportRecorder .RecordOne (report .ReasonBeforeSend , ratelimit .CategoryLog )
611+ client .reportRecorder .Record (report .ReasonBeforeSend , ratelimit .CategoryLogByte , int64 (approxSize ))
578612 return false
579613 }
580614 }
581615
582616 if client .telemetryProcessor != nil {
583617 if ! client .telemetryProcessor .Add (log ) {
584618 debuglog .Print ("Dropping log: telemetry buffer full or category missing" )
619+ // Note: processor tracks client report
585620 return false
586621 }
587622 } else if client .batchLogger != nil {
588623 if ! client .batchLogger .Send (log ) {
589624 debuglog .Printf ("Dropping log [%s]: buffer full" , log .Level )
625+ client .reportRecorder .RecordOne (report .ReasonBufferOverflow , ratelimit .CategoryLog )
626+ client .reportRecorder .Record (report .ReasonBufferOverflow , ratelimit .CategoryLogByte , int64 (log .ApproximateSize ()))
590627 return false
591628 }
592629 }
@@ -603,18 +640,21 @@ func (client *Client) captureMetric(metric *Metric, _ *Scope) bool {
603640 metric = client .options .BeforeSendMetric (metric )
604641 if metric == nil {
605642 debuglog .Println ("Metric dropped due to BeforeSendMetric callback." )
643+ client .reportRecorder .RecordOne (report .ReasonBeforeSend , ratelimit .CategoryTraceMetric )
606644 return false
607645 }
608646 }
609647
610648 if client .telemetryProcessor != nil {
611649 if ! client .telemetryProcessor .Add (metric ) {
612650 debuglog .Printf ("Dropping metric: telemetry buffer full or category missing" )
651+ // Note: processor tracks client report
613652 return false
614653 }
615654 } else if client .batchMeter != nil {
616655 if ! client .batchMeter .Send (metric ) {
617656 debuglog .Printf ("Dropping metric %q: buffer full" , metric .Name )
657+ client .reportRecorder .RecordOne (report .ReasonBufferOverflow , ratelimit .CategoryTraceMetric )
618658 return false
619659 }
620660 }
@@ -823,6 +863,7 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
823863 // (errors, messages) are sampled here. Does not apply to check-ins.
824864 if event .Type != transactionType && event .Type != checkInType && ! sample (client .options .SampleRate ) {
825865 debuglog .Println ("Event dropped due to SampleRate hit." )
866+ client .reportRecorder .RecordOne (report .ReasonSampleRate , event .toCategory ())
826867 return nil
827868 }
828869
@@ -837,16 +878,25 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
837878 switch event .Type {
838879 case transactionType :
839880 if client .options .BeforeSendTransaction != nil {
840- if event = client .options .BeforeSendTransaction (event , hint ); event == nil {
881+ spanCountBefore := event .GetSpanCount ()
882+ event = client .options .BeforeSendTransaction (event , hint )
883+ if event == nil {
841884 debuglog .Println ("Transaction dropped due to BeforeSendTransaction callback." )
885+ client .reportRecorder .RecordOne (report .ReasonBeforeSend , ratelimit .CategoryTransaction )
886+ client .reportRecorder .Record (report .ReasonBeforeSend , ratelimit .CategorySpan , int64 (spanCountBefore ))
842887 return nil
843888 }
889+ // Track spans removed by the callback
890+ if droppedSpans := spanCountBefore - event .GetSpanCount (); droppedSpans > 0 {
891+ client .reportRecorder .Record (report .ReasonBeforeSend , ratelimit .CategorySpan , int64 (droppedSpans ))
892+ }
844893 }
845894 case checkInType : // not a default case, since we shouldn't apply BeforeSend on check-in events
846895 default :
847896 if client .options .BeforeSend != nil {
848897 if event = client .options .BeforeSend (event , hint ); event == nil {
849898 debuglog .Println ("Event dropped due to BeforeSend callback." )
899+ client .reportRecorder .RecordOne (report .ReasonBeforeSend , ratelimit .CategoryError )
850900 return nil
851901 }
852902 }
@@ -917,20 +967,44 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
917967
918968 for _ , processor := range client .eventProcessors {
919969 id := event .EventID
970+ category := event .toCategory ()
971+ spanCountBefore := event .GetSpanCount ()
920972 event = processor (event , hint )
921973 if event == nil {
922974 debuglog .Printf ("Event dropped by one of the Client EventProcessors: %s\n " , id )
975+ client .reportRecorder .RecordOne (report .ReasonEventProcessor , category )
976+ if category == ratelimit .CategoryTransaction {
977+ client .reportRecorder .Record (report .ReasonEventProcessor , ratelimit .CategorySpan , int64 (spanCountBefore ))
978+ }
923979 return nil
924980 }
981+ // Track spans removed by the processor
982+ if category == ratelimit .CategoryTransaction {
983+ if droppedSpans := spanCountBefore - event .GetSpanCount (); droppedSpans > 0 {
984+ client .reportRecorder .Record (report .ReasonEventProcessor , ratelimit .CategorySpan , int64 (droppedSpans ))
985+ }
986+ }
925987 }
926988
927989 for _ , processor := range globalEventProcessors {
928990 id := event .EventID
991+ category := event .toCategory ()
992+ spanCountBefore := event .GetSpanCount ()
929993 event = processor (event , hint )
930994 if event == nil {
931995 debuglog .Printf ("Event dropped by one of the Global EventProcessors: %s\n " , id )
996+ client .reportRecorder .RecordOne (report .ReasonEventProcessor , category )
997+ if category == ratelimit .CategoryTransaction {
998+ client .reportRecorder .Record (report .ReasonEventProcessor , ratelimit .CategorySpan , int64 (spanCountBefore ))
999+ }
9321000 return nil
9331001 }
1002+ // Track spans removed by the processor
1003+ if category == ratelimit .CategoryTransaction {
1004+ if droppedSpans := spanCountBefore - event .GetSpanCount (); droppedSpans > 0 {
1005+ client .reportRecorder .Record (report .ReasonEventProcessor , ratelimit .CategorySpan , int64 (droppedSpans ))
1006+ }
1007+ }
9341008 }
9351009
9361010 return event
0 commit comments