sentry-ruby 5.28.1 → 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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +25 -1
  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 +57 -135
  11. data/lib/sentry/configuration.rb +117 -75
  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/envelope/item.rb +3 -3
  16. data/lib/sentry/error_event.rb +3 -3
  17. data/lib/sentry/event.rb +4 -10
  18. data/lib/sentry/hub.rb +26 -4
  19. data/lib/sentry/interface.rb +1 -1
  20. data/lib/sentry/interfaces/exception.rb +2 -2
  21. data/lib/sentry/interfaces/request.rb +2 -0
  22. data/lib/sentry/interfaces/single_exception.rb +4 -4
  23. data/lib/sentry/interfaces/stacktrace.rb +3 -3
  24. data/lib/sentry/interfaces/stacktrace_builder.rb +0 -8
  25. data/lib/sentry/interfaces/threads.rb +2 -2
  26. data/lib/sentry/log_event.rb +24 -142
  27. data/lib/sentry/log_event_buffer.rb +13 -60
  28. data/lib/sentry/metric_event.rb +49 -0
  29. data/lib/sentry/metric_event_buffer.rb +28 -0
  30. data/lib/sentry/metrics.rb +47 -54
  31. data/lib/sentry/profiler.rb +4 -5
  32. data/lib/sentry/rack/capture_exceptions.rb +5 -1
  33. data/lib/sentry/rspec.rb +1 -1
  34. data/lib/sentry/scope.rb +50 -18
  35. data/lib/sentry/sequel.rb +35 -0
  36. data/lib/sentry/span.rb +2 -17
  37. data/lib/sentry/std_lib_logger.rb +4 -0
  38. data/lib/sentry/telemetry_event_buffer.rb +130 -0
  39. data/lib/sentry/test_helper.rb +8 -0
  40. data/lib/sentry/transaction.rb +52 -103
  41. data/lib/sentry/transaction_event.rb +4 -9
  42. data/lib/sentry/transport.rb +2 -5
  43. data/lib/sentry/utils/encoding_helper.rb +6 -0
  44. data/lib/sentry/utils/logging_helper.rb +25 -9
  45. data/lib/sentry/utils/telemetry_attributes.rb +30 -0
  46. data/lib/sentry/vernier/profiler.rb +4 -3
  47. data/lib/sentry/version.rb +1 -1
  48. data/lib/sentry-ruby.rb +53 -30
  49. data/sentry-ruby-core.gemspec +1 -1
  50. data/sentry-ruby.gemspec +2 -1
  51. metadata +27 -16
  52. data/lib/sentry/metrics/aggregator.rb +0 -248
  53. data/lib/sentry/metrics/configuration.rb +0 -57
  54. data/lib/sentry/metrics/counter_metric.rb +0 -25
  55. data/lib/sentry/metrics/distribution_metric.rb +0 -25
  56. data/lib/sentry/metrics/gauge_metric.rb +0 -35
  57. data/lib/sentry/metrics/local_aggregator.rb +0 -53
  58. data/lib/sentry/metrics/metric.rb +0 -19
  59. data/lib/sentry/metrics/set_metric.rb +0 -28
  60. data/lib/sentry/metrics/timing.rb +0 -51
@@ -1,134 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "sentry/utils/telemetry_attributes"
4
+
3
5
  module Sentry
4
6
  # Event type that represents a log entry with its attributes
5
7
  #
6
8
  # @see https://develop.sentry.dev/sdk/telemetry/logs/#log-envelope-item-payload
7
9
  class LogEvent
10
+ include Sentry::Utils::TelemetryAttributes
11
+
8
12
  TYPE = "log"
9
13
 
10
14
  DEFAULT_PARAMETERS = [].freeze
11
- DEFAULT_ATTRIBUTES = {}.freeze
12
-
13
- SERIALIZEABLE_ATTRIBUTES = %i[
14
- level
15
- body
16
- timestamp
17
- environment
18
- release
19
- server_name
20
- trace_id
21
- attributes
22
- contexts
23
- ]
24
-
25
- SENTRY_ATTRIBUTES = {
26
- "sentry.trace.parent_span_id" => :parent_span_id,
27
- "sentry.environment" => :environment,
28
- "sentry.release" => :release,
29
- "sentry.address" => :server_name,
30
- "sentry.sdk.name" => :sdk_name,
31
- "sentry.sdk.version" => :sdk_version,
32
- "sentry.message.template" => :template,
33
- "sentry.origin" => :origin
34
- }
35
15
 
36
16
  PARAMETER_PREFIX = "sentry.message.parameter"
37
17
 
38
- USER_ATTRIBUTES = {
39
- "user.id" => :user_id,
40
- "user.name" => :user_username,
41
- "user.email" => :user_email
42
- }
43
-
44
18
  LEVELS = %i[trace debug info warn error fatal].freeze
45
19
 
46
- attr_accessor :level, :body, :template, :attributes, :user, :origin
47
-
48
- attr_reader :configuration, *(SERIALIZEABLE_ATTRIBUTES - %i[level body attributes])
49
-
50
- SERIALIZERS = %i[
51
- attributes
52
- body
53
- level
54
- parent_span_id
55
- sdk_name
56
- sdk_version
57
- template
58
- timestamp
59
- trace_id
60
- user_id
61
- user_username
62
- user_email
63
- ].map { |name| [name, :"serialize_#{name}"] }.to_h
64
-
65
- VALUE_TYPES = Hash.new("string").merge!({
66
- TrueClass => "boolean",
67
- FalseClass => "boolean",
68
- Integer => "integer",
69
- Float => "double"
70
- }).freeze
20
+ attr_accessor :level, :body, :template, :attributes, :origin, :trace_id, :span_id
21
+ attr_reader :timestamp
71
22
 
72
23
  TOKEN_REGEXP = /%\{(\w+)\}/
73
24
 
74
- def initialize(configuration: Sentry.configuration, **options)
75
- @configuration = configuration
25
+ def initialize(**options)
76
26
  @type = TYPE
77
- @server_name = configuration.server_name
78
- @environment = configuration.environment
79
- @release = configuration.release
80
27
  @timestamp = Sentry.utc_now
81
28
  @level = options.fetch(:level)
82
29
  @body = options[:body]
83
30
  @template = @body if is_template?
84
- @attributes = options[:attributes] || DEFAULT_ATTRIBUTES
85
- @user = options[:user] || {}
31
+ @attributes = options[:attributes] || {}
86
32
  @origin = options[:origin]
87
- @contexts = {}
33
+ @trace_id = nil
34
+ @span_id = nil
88
35
  end
89
36
 
90
- def to_hash
91
- SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |name, memo|
92
- memo[name] = serialize(name)
93
- end
37
+ def to_h
38
+ {
39
+ level: level.to_s,
40
+ timestamp: timestamp.to_f,
41
+ trace_id: @trace_id,
42
+ span_id: @span_id,
43
+ body: serialize_body,
44
+ attributes: serialize_attributes
45
+ }.compact
94
46
  end
95
47
 
96
48
  private
97
49
 
98
- def serialize(name)
99
- serializer = SERIALIZERS[name]
100
-
101
- if serializer
102
- __send__(serializer)
103
- else
104
- public_send(name)
105
- end
106
- end
107
-
108
- def serialize_level
109
- level.to_s
110
- end
111
-
112
- def serialize_sdk_name
113
- Sentry.sdk_meta["name"]
114
- end
115
-
116
- def serialize_sdk_version
117
- Sentry.sdk_meta["version"]
118
- end
119
-
120
- def serialize_timestamp
121
- timestamp.to_f
122
- end
123
-
124
- def serialize_trace_id
125
- contexts.dig(:trace, :trace_id)
126
- end
127
-
128
- def serialize_parent_span_id
129
- contexts.dig(:trace, :parent_span_id)
130
- end
131
-
132
50
  def serialize_body
133
51
  if parameters.empty?
134
52
  body
@@ -139,50 +57,14 @@ module Sentry
139
57
  end
140
58
  end
141
59
 
142
- def serialize_user_id
143
- user[:id]
144
- end
145
-
146
- def serialize_user_username
147
- user[:username]
148
- end
149
-
150
- def serialize_user_email
151
- user[:email]
152
- end
153
-
154
- def serialize_template
155
- template if has_parameters?
156
- end
157
-
158
60
  def serialize_attributes
159
- hash = {}
160
-
161
- attributes.each do |key, value|
162
- hash[key] = attribute_hash(value)
163
- end
164
-
165
- SENTRY_ATTRIBUTES.each do |key, name|
166
- if (value = serialize(name))
167
- hash[key] = attribute_hash(value)
168
- end
169
- end
170
-
171
- USER_ATTRIBUTES.each do |key, name|
172
- if (value = serialize(name))
173
- hash[key] = value
174
- end
175
- end
176
-
177
- hash
178
- end
179
-
180
- def attribute_hash(value)
181
- { value: value, type: value_type(value) }
61
+ populate_sentry_attributes!
62
+ @attributes.transform_values! { |v| attribute_hash(v) }
182
63
  end
183
64
 
184
- def value_type(value)
185
- VALUE_TYPES[value.class]
65
+ def populate_sentry_attributes!
66
+ @attributes["sentry.origin"] ||= @origin if @origin
67
+ @attributes["sentry.message.template"] ||= template if has_parameters?
186
68
  end
187
69
 
188
70
  def parameters
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sentry/threaded_periodic_worker"
3
+ require "sentry/telemetry_event_buffer"
4
4
 
5
5
  module Sentry
6
6
  # LogEventBuffer buffers log events and sends them to Sentry in a single envelope.
@@ -8,68 +8,21 @@ module Sentry
8
8
  # This is used internally by the `Sentry::Client`.
9
9
  #
10
10
  # @!visibility private
11
- class LogEventBuffer < ThreadedPeriodicWorker
12
- FLUSH_INTERVAL = 5 # seconds
11
+ class LogEventBuffer < TelemetryEventBuffer
13
12
  DEFAULT_MAX_EVENTS = 100
14
-
15
- # @!visibility private
16
- attr_reader :pending_events
13
+ MAX_EVENTS_BEFORE_DROP = 1000
17
14
 
18
15
  def initialize(configuration, client)
19
- super(configuration.sdk_logger, FLUSH_INTERVAL)
20
-
21
- @client = client
22
- @pending_events = []
23
- @max_events = configuration.max_log_events || DEFAULT_MAX_EVENTS
24
- @mutex = Mutex.new
25
-
26
- log_debug("[Logging] Initialized buffer with max_events=#{@max_events}, flush_interval=#{FLUSH_INTERVAL}s")
27
- end
28
-
29
- def start
30
- ensure_thread
31
- self
32
- end
33
-
34
- def flush
35
- @mutex.synchronize do
36
- return if empty?
37
-
38
- log_debug("[LogEventBuffer] flushing #{size} log events")
39
-
40
- send_events
41
- end
42
-
43
- log_debug("[LogEventBuffer] flushed #{size} log events")
44
-
45
- self
46
- end
47
- alias_method :run, :flush
48
-
49
- def add_event(event)
50
- raise ArgumentError, "expected a LogEvent, got #{event.class}" unless event.is_a?(LogEvent)
51
-
52
- @mutex.synchronize do
53
- @pending_events << event
54
- send_events if size >= @max_events
55
- end
56
-
57
- self
58
- end
59
-
60
- def empty?
61
- @pending_events.empty?
62
- end
63
-
64
- def size
65
- @pending_events.size
66
- end
67
-
68
- private
69
-
70
- def send_events
71
- @client.send_logs(@pending_events)
72
- @pending_events.clear
16
+ super(
17
+ configuration,
18
+ client,
19
+ event_class: LogEvent,
20
+ max_items: configuration.max_log_events || DEFAULT_MAX_EVENTS,
21
+ max_items_before_drop: MAX_EVENTS_BEFORE_DROP,
22
+ envelope_type: "log",
23
+ envelope_content_type: "application/vnd.sentry.items.log+json",
24
+ before_send: configuration.before_send_log
25
+ )
73
26
  end
74
27
  end
75
28
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/utils/telemetry_attributes"
4
+
5
+ module Sentry
6
+ class MetricEvent
7
+ include Sentry::Utils::TelemetryAttributes
8
+
9
+ attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes
10
+ attr_writer :trace_id, :span_id, :attributes
11
+
12
+ def initialize(
13
+ name:,
14
+ type:,
15
+ value:,
16
+ unit: nil,
17
+ attributes: nil
18
+ )
19
+ @name = name
20
+ @type = type
21
+ @value = value
22
+ @unit = unit
23
+ @attributes = attributes || {}
24
+
25
+ @timestamp = Sentry.utc_now
26
+ @trace_id = nil
27
+ @span_id = nil
28
+ end
29
+
30
+ def to_h
31
+ {
32
+ name: @name,
33
+ type: @type,
34
+ value: @value,
35
+ unit: @unit,
36
+ timestamp: @timestamp,
37
+ trace_id: @trace_id,
38
+ span_id: @span_id,
39
+ attributes: serialize_attributes
40
+ }.compact
41
+ end
42
+
43
+ private
44
+
45
+ def serialize_attributes
46
+ @attributes.transform_values! { |v| attribute_hash(v) }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/telemetry_event_buffer"
4
+
5
+ module Sentry
6
+ # MetricEventBuffer buffers metric events and sends them to Sentry in a single envelope.
7
+ #
8
+ # This is used internally by the `Sentry::Client`.
9
+ #
10
+ # @!visibility private
11
+ class MetricEventBuffer < TelemetryEventBuffer
12
+ DEFAULT_MAX_METRICS = 1000
13
+ MAX_METRICS_BEFORE_DROP = 10_000
14
+
15
+ def initialize(configuration, client)
16
+ super(
17
+ configuration,
18
+ client,
19
+ event_class: MetricEvent,
20
+ max_items: configuration.max_metric_events || DEFAULT_MAX_METRICS,
21
+ max_items_before_drop: MAX_METRICS_BEFORE_DROP,
22
+ envelope_type: "trace_metric",
23
+ envelope_content_type: "application/vnd.sentry.items.trace-metric+json",
24
+ before_send: configuration.before_send_metric
25
+ )
26
+ end
27
+ end
28
+ end
@@ -1,67 +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
- log_deprecation
23
- Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
24
- end
25
-
26
- def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
27
- log_deprecation
28
- Sentry.metrics_aggregator&.add(:d, 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
+ )
29
22
  end
30
23
 
31
- def set(key, value, unit: "none", tags: {}, timestamp: nil)
32
- log_deprecation
33
- Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
34
- end
35
-
36
- def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
37
- log_deprecation
38
- Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
39
- end
40
-
41
- def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
42
- log_deprecation
43
-
44
- return unless block_given?
45
- return yield unless DURATION_UNITS.include?(unit)
46
-
47
- result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
48
- tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
49
-
50
- start = Timing.send(unit.to_sym)
51
- result = yield
52
- value = Timing.send(unit.to_sym) - start
53
-
54
- [result, value]
55
- end
56
-
57
- Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
58
- result
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
+ )
59
40
  end
60
41
 
61
- def log_deprecation
62
- Sentry.sdk_logger.warn(LOGGER_PROGNAME) do
63
- "`Sentry::Metrics` is now deprecated and will be removed in the next major."
64
- end
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
+ )
65
58
  end
66
59
  end
67
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
 
@@ -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