sentry-ruby 5.26.0 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +26 -4
  3. data/README.md +2 -2
  4. data/lib/sentry/background_worker.rb +1 -4
  5. data/lib/sentry/backtrace/line.rb +99 -0
  6. data/lib/sentry/backtrace.rb +44 -76
  7. data/lib/sentry/breadcrumb.rb +1 -1
  8. data/lib/sentry/breadcrumb_buffer.rb +2 -2
  9. data/lib/sentry/check_in_event.rb +2 -2
  10. data/lib/sentry/client.rb +59 -136
  11. data/lib/sentry/configuration.rb +168 -78
  12. data/lib/sentry/cron/monitor_check_ins.rb +3 -3
  13. data/lib/sentry/cron/monitor_config.rb +2 -2
  14. data/lib/sentry/cron/monitor_schedule.rb +2 -2
  15. data/lib/sentry/debug_structured_logger.rb +94 -0
  16. data/lib/sentry/dsn.rb +32 -0
  17. data/lib/sentry/envelope/item.rb +3 -3
  18. data/lib/sentry/error_event.rb +3 -3
  19. data/lib/sentry/event.rb +4 -10
  20. data/lib/sentry/graphql.rb +1 -1
  21. data/lib/sentry/hub.rb +29 -5
  22. data/lib/sentry/interface.rb +1 -1
  23. data/lib/sentry/interfaces/exception.rb +2 -2
  24. data/lib/sentry/interfaces/request.rb +2 -0
  25. data/lib/sentry/interfaces/single_exception.rb +4 -4
  26. data/lib/sentry/interfaces/stacktrace.rb +3 -3
  27. data/lib/sentry/interfaces/stacktrace_builder.rb +0 -8
  28. data/lib/sentry/interfaces/threads.rb +2 -2
  29. data/lib/sentry/log_event.rb +33 -138
  30. data/lib/sentry/log_event_buffer.rb +13 -60
  31. data/lib/sentry/metric_event.rb +49 -0
  32. data/lib/sentry/metric_event_buffer.rb +28 -0
  33. data/lib/sentry/metrics.rb +47 -42
  34. data/lib/sentry/profiler.rb +4 -5
  35. data/lib/sentry/propagation_context.rb +55 -18
  36. data/lib/sentry/rack/capture_exceptions.rb +5 -1
  37. data/lib/sentry/rspec.rb +1 -1
  38. data/lib/sentry/scope.rb +50 -18
  39. data/lib/sentry/sequel.rb +35 -0
  40. data/lib/sentry/span.rb +2 -17
  41. data/lib/sentry/std_lib_logger.rb +10 -1
  42. data/lib/sentry/telemetry_event_buffer.rb +130 -0
  43. data/lib/sentry/test_helper.rb +30 -0
  44. data/lib/sentry/transaction.rb +72 -95
  45. data/lib/sentry/transaction_event.rb +4 -9
  46. data/lib/sentry/transport/debug_transport.rb +70 -0
  47. data/lib/sentry/transport/dummy_transport.rb +1 -0
  48. data/lib/sentry/transport/http_transport.rb +9 -5
  49. data/lib/sentry/transport.rb +3 -5
  50. data/lib/sentry/utils/encoding_helper.rb +6 -0
  51. data/lib/sentry/utils/logging_helper.rb +25 -9
  52. data/lib/sentry/utils/sample_rand.rb +97 -0
  53. data/lib/sentry/utils/telemetry_attributes.rb +30 -0
  54. data/lib/sentry/vernier/profiler.rb +4 -3
  55. data/lib/sentry/version.rb +1 -1
  56. data/lib/sentry-ruby.rb +57 -30
  57. data/sentry-ruby-core.gemspec +1 -1
  58. data/sentry-ruby.gemspec +2 -1
  59. metadata +31 -17
  60. data/lib/sentry/metrics/aggregator.rb +0 -248
  61. data/lib/sentry/metrics/configuration.rb +0 -47
  62. data/lib/sentry/metrics/counter_metric.rb +0 -25
  63. data/lib/sentry/metrics/distribution_metric.rb +0 -25
  64. data/lib/sentry/metrics/gauge_metric.rb +0 -35
  65. data/lib/sentry/metrics/local_aggregator.rb +0 -53
  66. data/lib/sentry/metrics/metric.rb +0 -19
  67. data/lib/sentry/metrics/set_metric.rb +0 -28
  68. data/lib/sentry/metrics/timing.rb +0 -51
@@ -1,55 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sentry/metrics/metric"
4
- require "sentry/metrics/counter_metric"
5
- require "sentry/metrics/distribution_metric"
6
- require "sentry/metrics/gauge_metric"
7
- require "sentry/metrics/set_metric"
8
- require "sentry/metrics/timing"
9
- require "sentry/metrics/aggregator"
3
+ require "sentry/metric_event"
10
4
 
11
5
  module Sentry
12
6
  module Metrics
13
- DURATION_UNITS = %w[nanosecond microsecond millisecond second minute hour day week]
14
- INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
15
- FRACTIONAL_UNITS = %w[ratio percent]
16
-
17
- OP_NAME = "metric.timing"
18
- SPAN_ORIGIN = "auto.metric.timing"
19
-
20
7
  class << self
21
- def increment(key, value = 1.0, unit: "none", tags: {}, timestamp: nil)
22
- Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
23
- end
24
-
25
- def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
26
- Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
27
- end
28
-
29
- def set(key, value, unit: "none", tags: {}, timestamp: nil)
30
- Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
8
+ # Increments a counter metric
9
+ # @param name [String] the metric name
10
+ # @param value [Numeric] the value to increment by (default: 1)
11
+ # @param attributes [Hash, nil] additional attributes for the metric (optional)
12
+ # @return [void]
13
+ def count(name, value: 1, attributes: nil)
14
+ return unless Sentry.initialized?
15
+
16
+ Sentry.get_current_hub.capture_metric(
17
+ name: name,
18
+ type: :counter,
19
+ value: value,
20
+ attributes: attributes
21
+ )
31
22
  end
32
23
 
33
- def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
34
- Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
24
+ # Records a gauge metric
25
+ # @param name [String] the metric name
26
+ # @param value [Numeric] the gauge value
27
+ # @param unit [String, nil] the metric unit (optional)
28
+ # @param attributes [Hash, nil] additional attributes for the metric (optional)
29
+ # @return [void]
30
+ def gauge(name, value, unit: nil, attributes: nil)
31
+ return unless Sentry.initialized?
32
+
33
+ Sentry.get_current_hub.capture_metric(
34
+ name: name,
35
+ type: :gauge,
36
+ value: value,
37
+ unit: unit,
38
+ attributes: attributes
39
+ )
35
40
  end
36
41
 
37
- def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
38
- return unless block_given?
39
- return yield unless DURATION_UNITS.include?(unit)
40
-
41
- result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
42
- tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
43
-
44
- start = Timing.send(unit.to_sym)
45
- result = yield
46
- value = Timing.send(unit.to_sym) - start
47
-
48
- [result, value]
49
- end
50
-
51
- Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
52
- result
42
+ # Records a distribution metric
43
+ # @param name [String] the metric name
44
+ # @param value [Numeric] the distribution value
45
+ # @param unit [String, nil] the metric unit (optional)
46
+ # @param attributes [Hash, nil] additional attributes for the metric (optional)
47
+ # @return [void]
48
+ def distribution(name, value, unit: nil, attributes: nil)
49
+ return unless Sentry.initialized?
50
+
51
+ Sentry.get_current_hub.capture_metric(
52
+ name: name,
53
+ type: :distribution,
54
+ value: value,
55
+ unit: unit,
56
+ attributes: attributes
57
+ )
53
58
  end
54
59
  end
55
60
  end
@@ -10,8 +10,6 @@ module Sentry
10
10
 
11
11
  VERSION = "1"
12
12
  PLATFORM = "ruby"
13
- # 101 Hz in microseconds
14
- DEFAULT_INTERVAL = 1e6 / 101
15
13
  MICRO_TO_NANO_SECONDS = 1e3
16
14
  MIN_SAMPLES_REQUIRED = 2
17
15
 
@@ -24,6 +22,7 @@ module Sentry
24
22
 
25
23
  @profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
26
24
  @profiles_sample_rate = configuration.profiles_sample_rate
25
+ @profiles_sample_interval = configuration.profiles_sample_interval
27
26
  @project_root = configuration.project_root
28
27
  @app_dirs_pattern = configuration.app_dirs_pattern
29
28
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
@@ -32,7 +31,7 @@ module Sentry
32
31
  def start
33
32
  return unless @sampled
34
33
 
35
- @started = StackProf.start(interval: DEFAULT_INTERVAL,
34
+ @started = StackProf.start(interval: @profiles_sample_interval,
36
35
  mode: :wall,
37
36
  raw: true,
38
37
  aggregate: false)
@@ -81,9 +80,9 @@ module Sentry
81
80
  log("Discarding profile due to sampling decision") unless @sampled
82
81
  end
83
82
 
84
- def to_hash
83
+ def to_h
85
84
  unless @sampled
86
- record_lost_event(:sample_rate)
85
+ record_lost_event(:sample_rate) if @profiling_enabled
87
86
  return {}
88
87
  end
89
88
 
@@ -3,15 +3,14 @@
3
3
  require "securerandom"
4
4
  require "sentry/baggage"
5
5
  require "sentry/utils/uuid"
6
+ require "sentry/utils/sample_rand"
6
7
 
7
8
  module Sentry
8
9
  class PropagationContext
9
10
  SENTRY_TRACE_REGEXP = Regexp.new(
10
- "^[ \t]*" + # whitespace
11
- "([0-9a-f]{32})?" + # trace_id
11
+ "\\A([0-9a-f]{32})?" + # trace_id
12
12
  "-?([0-9a-f]{16})?" + # span_id
13
- "-?([01])?" + # sampled
14
- "[ \t]*$" # whitespace
13
+ "-?([01])?\\z" # sampled
15
14
  )
16
15
 
17
16
  # An uuid that can be used to identify a trace.
@@ -33,6 +32,53 @@ module Sentry
33
32
  # Please use the #get_baggage method for interfacing outside this class.
34
33
  # @return [Baggage, nil]
35
34
  attr_reader :baggage
35
+ # The propagated random value used for sampling decisions.
36
+ # @return [Float, nil]
37
+ attr_reader :sample_rand
38
+
39
+ # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
40
+ #
41
+ # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
42
+ # @return [Array, nil]
43
+ def self.extract_sentry_trace(sentry_trace)
44
+ value = sentry_trace.to_s.strip
45
+ return if value.empty?
46
+
47
+ match = SENTRY_TRACE_REGEXP.match(value)
48
+ return if match.nil?
49
+
50
+ trace_id, parent_span_id, sampled_flag = match[1..3]
51
+ parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
52
+
53
+ [trace_id, parent_span_id, parent_sampled]
54
+ end
55
+
56
+ def self.extract_sample_rand_from_baggage(baggage, trace_id = nil)
57
+ return unless baggage&.items
58
+
59
+ sample_rand_str = baggage.items["sample_rand"]
60
+ return unless sample_rand_str
61
+
62
+ generator = Utils::SampleRand.new(trace_id: trace_id)
63
+ generator.generate_from_value(sample_rand_str)
64
+ end
65
+
66
+ def self.generate_sample_rand(baggage, trace_id, parent_sampled)
67
+ generator = Utils::SampleRand.new(trace_id: trace_id)
68
+
69
+ if baggage&.items && !parent_sampled.nil?
70
+ sample_rate_str = baggage.items["sample_rate"]
71
+ sample_rate = sample_rate_str&.to_f
72
+
73
+ if sample_rate && !parent_sampled.nil?
74
+ generator.generate_from_sampling_decision(parent_sampled, sample_rate)
75
+ else
76
+ generator.generate_from_trace_id
77
+ end
78
+ else
79
+ generator.generate_from_trace_id
80
+ end
81
+ end
36
82
 
37
83
  def initialize(scope, env = nil)
38
84
  @scope = scope
@@ -40,6 +86,7 @@ module Sentry
40
86
  @parent_sampled = nil
41
87
  @baggage = nil
42
88
  @incoming_trace = false
89
+ @sample_rand = nil
43
90
 
44
91
  if env
45
92
  sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
@@ -61,6 +108,8 @@ module Sentry
61
108
  Baggage.new({})
62
109
  end
63
110
 
111
+ @sample_rand = self.class.extract_sample_rand_from_baggage(@baggage, @trace_id)
112
+
64
113
  @baggage.freeze!
65
114
  @incoming_trace = true
66
115
  end
@@ -69,20 +118,7 @@ module Sentry
69
118
 
70
119
  @trace_id ||= Utils.uuid
71
120
  @span_id = Utils.uuid.slice(0, 16)
72
- end
73
-
74
- # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
75
- #
76
- # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
77
- # @return [Array, nil]
78
- def self.extract_sentry_trace(sentry_trace)
79
- match = SENTRY_TRACE_REGEXP.match(sentry_trace)
80
- return nil if match.nil?
81
-
82
- trace_id, parent_span_id, sampled_flag = match[1..3]
83
- parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
84
-
85
- [trace_id, parent_span_id, parent_sampled]
121
+ @sample_rand ||= self.class.generate_sample_rand(@baggage, @trace_id, @parent_sampled)
86
122
  end
87
123
 
88
124
  # Returns the trace context that can be used to embed in an Event.
@@ -123,6 +159,7 @@ module Sentry
123
159
 
124
160
  items = {
125
161
  "trace_id" => trace_id,
162
+ "sample_rand" => Utils::SampleRand.format(@sample_rand),
126
163
  "environment" => configuration.environment,
127
164
  "release" => configuration.release,
128
165
  "public_key" => configuration.dsn&.public_key
@@ -33,7 +33,7 @@ module Sentry
33
33
  raise # Don't capture Sentry errors
34
34
  rescue Exception => e
35
35
  capture_exception(e, env)
36
- finish_transaction(transaction, 500)
36
+ finish_transaction(transaction, status_code_for_exception(e))
37
37
  raise
38
38
  end
39
39
 
@@ -86,6 +86,10 @@ module Sentry
86
86
  def mechanism
87
87
  Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
88
88
  end
89
+
90
+ def status_code_for_exception(exception)
91
+ 500
92
+ end
89
93
  end
90
94
  end
91
95
  end
data/lib/sentry/rspec.rb CHANGED
@@ -70,7 +70,7 @@ RSpec::Matchers.define :include_sentry_event do |event_message = "", **opts|
70
70
  end
71
71
 
72
72
  def dump_events(sentry_events)
73
- sentry_events.map(&Kernel.method(:Hash)).map do |hash|
73
+ sentry_events.map(&:to_h).map do |hash|
74
74
  hash.select { |k, _| [:message, :contexts, :tags, :exception].include?(k) }
75
75
  end.map do |hash|
76
76
  JSON.pretty_generate(hash)
data/lib/sentry/scope.rb CHANGED
@@ -46,7 +46,7 @@ module Sentry
46
46
  # @param hint [Hash] the hint data that'll be passed to event processors.
47
47
  # @return [Event]
48
48
  def apply_to_event(event, hint = nil)
49
- unless event.is_a?(CheckInEvent) || event.is_a?(LogEvent)
49
+ unless event.is_a?(CheckInEvent)
50
50
  event.tags = tags.merge(event.tags)
51
51
  event.user = user.merge(event.user)
52
52
  event.extra = extra.merge(event.extra)
@@ -60,23 +60,10 @@ module Sentry
60
60
  event.attachments = attachments
61
61
  end
62
62
 
63
- if event.is_a?(LogEvent)
64
- event.user = user.merge(event.user)
65
- end
66
-
67
- if span
68
- event.contexts[:trace] ||= span.get_trace_context
69
-
70
- if event.respond_to?(:dynamic_sampling_context)
71
- event.dynamic_sampling_context ||= span.get_dynamic_sampling_context
72
- end
73
- else
74
- event.contexts[:trace] ||= propagation_context.get_trace_context
75
-
76
- if event.respond_to?(:dynamic_sampling_context)
77
- event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
78
- end
79
- end
63
+ trace_context = get_trace_context
64
+ dynamic_sampling_context = trace_context.delete(:dynamic_sampling_context)
65
+ event.contexts[:trace] ||= trace_context
66
+ event.dynamic_sampling_context ||= dynamic_sampling_context
80
67
 
81
68
  all_event_processors = self.class.global_event_processors + @event_processors
82
69
 
@@ -89,6 +76,37 @@ module Sentry
89
76
  event
90
77
  end
91
78
 
79
+ # A leaner version of apply_to_event that applies to
80
+ # lightweight payloads like Logs and Metrics.
81
+ #
82
+ # Adds trace_id, span_id, user from the scope and default attributes from configuration.
83
+ #
84
+ # @param telemetry [MetricEvent, LogEvent] the telemetry event to apply scope context to
85
+ # @return [MetricEvent, LogEvent] the telemetry event with scope context applied
86
+ def apply_to_telemetry(telemetry)
87
+ # TODO-neel when new scope set_attribute api is added: add them here
88
+ trace_context = get_trace_context
89
+ telemetry.trace_id = trace_context[:trace_id]
90
+ telemetry.span_id = trace_context[:span_id]
91
+
92
+ configuration = Sentry.configuration
93
+ return telemetry unless configuration
94
+
95
+ telemetry.attributes["sentry.sdk.name"] ||= Sentry.sdk_meta["name"]
96
+ telemetry.attributes["sentry.sdk.version"] ||= Sentry.sdk_meta["version"]
97
+ telemetry.attributes["sentry.environment"] ||= configuration.environment if configuration.environment
98
+ telemetry.attributes["sentry.release"] ||= configuration.release if configuration.release
99
+ telemetry.attributes["server.address"] ||= configuration.server_name if configuration.server_name
100
+
101
+ if configuration.send_default_pii && !user.empty?
102
+ telemetry.attributes["user.id"] ||= user[:id] if user[:id]
103
+ telemetry.attributes["user.name"] ||= user[:username] if user[:username]
104
+ telemetry.attributes["user.email"] ||= user[:email] if user[:email]
105
+ end
106
+
107
+ telemetry
108
+ end
109
+
92
110
  # Adds the breadcrumb to the scope's breadcrumbs buffer.
93
111
  # @param breadcrumb [Breadcrumb]
94
112
  # @return [void]
@@ -278,6 +296,20 @@ module Sentry
278
296
  span
279
297
  end
280
298
 
299
+ # Returns the trace context for this scope.
300
+ # Prioritizes external propagation context (from OTel) over local propagation context.
301
+ # @return [Hash]
302
+ def get_trace_context
303
+ if span
304
+ span.get_trace_context.merge(dynamic_sampling_context: span.get_dynamic_sampling_context)
305
+ elsif (external_context = Sentry.get_external_propagation_context)
306
+ trace_id, span_id = external_context
307
+ { trace_id: trace_id, span_id: span_id }
308
+ else
309
+ propagation_context.get_trace_context.merge(dynamic_sampling_context: propagation_context.get_dynamic_sampling_context)
310
+ end
311
+ end
312
+
281
313
  # Sets the scope's fingerprint attribute.
282
314
  # @param fingerprint [Array]
283
315
  # @return [Array]
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Sequel
5
+ OP_NAME = "db.sql.sequel"
6
+ SPAN_ORIGIN = "auto.db.sequel"
7
+
8
+ # Sequel Database extension module that instruments queries
9
+ module DatabaseExtension
10
+ def log_connection_yield(sql, conn, args = nil)
11
+ return super unless Sentry.initialized?
12
+
13
+ Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |span|
14
+ result = super
15
+
16
+ if span
17
+ span.set_description(sql)
18
+ span.set_data(Span::DataConventions::DB_SYSTEM, database_type.to_s)
19
+ span.set_data(Span::DataConventions::DB_NAME, opts[:database]) if opts[:database]
20
+ span.set_data(Span::DataConventions::SERVER_ADDRESS, opts[:host]) if opts[:host]
21
+ span.set_data(Span::DataConventions::SERVER_PORT, opts[:port]) if opts[:port]
22
+ end
23
+
24
+ result
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ ::Sequel::Database.register_extension(:sentry, Sentry::Sequel::DatabaseExtension)
31
+ end
32
+
33
+ Sentry.register_patch(:sequel) do
34
+ ::Sequel::Database.extension(:sentry) if defined?(::Sequel::Database)
35
+ end
data/lib/sentry/span.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
- require "sentry/metrics/local_aggregator"
5
4
  require "sentry/utils/uuid"
6
5
 
7
6
  module Sentry
@@ -173,8 +172,8 @@ module Sentry
173
172
  end
174
173
 
175
174
  # @return [Hash]
176
- def to_hash
177
- hash = {
175
+ def to_h
176
+ {
178
177
  trace_id: @trace_id,
179
178
  span_id: @span_id,
180
179
  parent_span_id: @parent_span_id,
@@ -187,11 +186,6 @@ module Sentry
187
186
  data: @data,
188
187
  origin: @origin
189
188
  }
190
-
191
- summary = metrics_summary
192
- hash[:_metrics_summary] = summary if summary
193
-
194
- hash
195
189
  end
196
190
 
197
191
  # Returns the span's context that can be used to embed in an Event.
@@ -307,14 +301,5 @@ module Sentry
307
301
  def set_origin(origin)
308
302
  @origin = origin
309
303
  end
310
-
311
- # Collects gauge metrics on the span for metric summaries.
312
- def metrics_local_aggregator
313
- @metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
314
- end
315
-
316
- def metrics_summary
317
- @metrics_local_aggregator&.to_hash
318
- end
319
304
  end
320
305
  end
@@ -12,11 +12,16 @@ module Sentry
12
12
  4 => :fatal
13
13
  }.freeze
14
14
 
15
+ ORIGIN = "auto.log.ruby.std_logger"
16
+
15
17
  def add(severity, message = nil, progname = nil, &block)
16
18
  result = super
17
19
 
18
20
  return unless Sentry.initialized? && Sentry.get_current_hub
19
21
 
22
+ # Only process logs that meet or exceed the logger's level
23
+ return result if severity < level
24
+
20
25
  # exclude sentry SDK logs -- to prevent recursive log action,
21
26
  # do not process internal logs again
22
27
  if message.nil? && progname != Sentry::Logger::PROGNAME
@@ -32,7 +37,11 @@ module Sentry
32
37
  message = message.to_s.strip
33
38
 
34
39
  if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
35
- Sentry.logger.send(method, message)
40
+ if (filter = Sentry.configuration.std_lib_logger_filter) && !filter.call(self, message, method)
41
+ return result
42
+ end
43
+
44
+ Sentry.logger.send(method, message, origin: ORIGIN)
36
45
  end
37
46
  end
38
47
 
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/threaded_periodic_worker"
4
+ require "sentry/envelope"
5
+
6
+ module Sentry
7
+ # TelemetryEventBuffer is a base class for buffering telemetry events (logs, metrics, etc.)
8
+ # and sending them to Sentry in a single envelope.
9
+ #
10
+ # This is used internally by the `Sentry::Client`.
11
+ #
12
+ # @!visibility private
13
+ class TelemetryEventBuffer < ThreadedPeriodicWorker
14
+ FLUSH_INTERVAL = 5 # seconds
15
+
16
+ # @!visibility private
17
+ attr_reader :pending_items, :envelope_type, :data_category, :thread
18
+
19
+ def initialize(configuration, client, event_class:, max_items:, max_items_before_drop:, envelope_type:, envelope_content_type:, before_send:)
20
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
21
+
22
+ @client = client
23
+ @dsn = configuration.dsn
24
+ @debug = configuration.debug
25
+ @event_class = event_class
26
+ @max_items = max_items
27
+ @max_items_before_drop = max_items_before_drop
28
+ @envelope_type = envelope_type
29
+ @data_category = Sentry::Envelope::Item.data_category(@envelope_type)
30
+ @envelope_content_type = envelope_content_type
31
+ @before_send = before_send
32
+
33
+ @pending_items = []
34
+ @mutex = Mutex.new
35
+
36
+ log_debug("[#{self.class}] Initialized buffer with max_items=#{@max_items}, flush_interval=#{FLUSH_INTERVAL}s")
37
+ end
38
+
39
+ def flush
40
+ @mutex.synchronize do
41
+ return if empty?
42
+
43
+ log_debug("[#{self.class}] flushing #{size} #{@event_class}")
44
+
45
+ send_items
46
+ end
47
+
48
+ self
49
+ end
50
+ alias_method :run, :flush
51
+
52
+ def add_item(item)
53
+ @mutex.synchronize do
54
+ return unless ensure_thread
55
+
56
+ if size >= @max_items_before_drop
57
+ log_debug("[#{self.class}] exceeded max capacity, dropping event")
58
+ @client.transport.record_lost_event(:queue_overflow, @data_category)
59
+ else
60
+ @pending_items << item
61
+ end
62
+
63
+ send_items if size >= @max_items
64
+ end
65
+
66
+ self
67
+ end
68
+
69
+ def empty?
70
+ @pending_items.empty?
71
+ end
72
+
73
+ def size
74
+ @pending_items.size
75
+ end
76
+
77
+ def clear!
78
+ @pending_items.clear
79
+ end
80
+
81
+ private
82
+
83
+ def send_items
84
+ envelope = Envelope.new(
85
+ event_id: Sentry::Utils.uuid,
86
+ sent_at: Sentry.utc_now.iso8601,
87
+ dsn: @dsn,
88
+ sdk: Sentry.sdk_meta
89
+ )
90
+
91
+ discarded_count = 0
92
+ envelope_items = []
93
+
94
+ if @before_send
95
+ @pending_items.each do |item|
96
+ processed_item = @before_send.call(item)
97
+
98
+ if processed_item
99
+ envelope_items << processed_item.to_h
100
+ else
101
+ discarded_count += 1
102
+ end
103
+ end
104
+ else
105
+ envelope_items = @pending_items.map(&:to_h)
106
+ end
107
+
108
+ unless discarded_count.zero?
109
+ @client.transport.record_lost_event(:before_send, @data_category, num: discarded_count)
110
+ end
111
+
112
+ return if envelope_items.empty?
113
+
114
+ envelope.add_item(
115
+ {
116
+ type: @envelope_type,
117
+ item_count: envelope_items.size,
118
+ content_type: @envelope_content_type
119
+ },
120
+ { items: envelope_items }
121
+ )
122
+
123
+ @client.send_envelope(envelope)
124
+ rescue => e
125
+ log_error("[#{self.class}] Failed to send #{@event_class}", e, debug: @debug)
126
+ ensure
127
+ clear!
128
+ end
129
+ end
130
+ end