sentry-ruby 5.17.2 → 5.18.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de5757f08dbb0987ccfc9e447f482358722a3684abae2ee581ab03a6bb290418
4
- data.tar.gz: 39b5b3fe8b54be43ecfd06cc39981e670a9c07d86d303a412aa4d83577155513
3
+ metadata.gz: 60b2f077a84b2ebd81dd8db1113b550256994ab7561ea96e94dd9799d3f61b9f
4
+ data.tar.gz: 4739ab8bca432f215b10747c4bc4caa031daa8f0751c90729b1a946554ea1875
5
5
  SHA512:
6
- metadata.gz: 034a330fcb4e8d1722eadcba6dbf6cf69f02919555f255ca98493ade5ebc2441a5b15bb6ce9bc3f2a6305b05e867f2a1e87f7eedc7b0a029036d4939a715ff69
7
- data.tar.gz: d75b0125443e8cff217fc1c4a6c9ff5dc1ff7ee51542e19ab3d3768e2f716233196afdcc72883b9b451d42817bd7a974c73095c27b48abfaffa089ae9a78d066
6
+ metadata.gz: 289e1be06a0d13f066881d3f1b2d0b674c3a61d576f4901b252872831263decb1af3ee7ff8201027ed1003231442680547e01a9f3e1130b773289770552ac10f
7
+ data.tar.gz: f0e5d48799611bd5db4b0b759b18e2b7eea13052bf24a21b9641c4fd755647ee3b4f007ac778f694b63a2ee03d94f7bab6a57ae5c83a7ace58dab7714382aa5c
data/Gemfile CHANGED
@@ -15,6 +15,8 @@ gem "puma"
15
15
  gem "timecop"
16
16
  gem "stackprof" unless RUBY_PLATFORM == "java"
17
17
 
18
+ gem "graphql", ">= 2.2.6" if RUBY_VERSION.to_f >= 2.7
19
+
18
20
  gem "benchmark-ips"
19
21
  gem "benchmark_driver"
20
22
  gem "benchmark-ipsa"
data/README.md CHANGED
@@ -13,14 +13,14 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to he
13
13
  Sentry SDK for Ruby
14
14
  ===========
15
15
 
16
- | current version | build | coverage | downloads |
17
- | --- | ----- | -------- | --------- |
18
- | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-ruby.svg)](https://rubygems.org/gems/sentry-ruby/) |
19
- | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails/) |
20
- | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-sidekiq.svg)](https://rubygems.org/gems/sentry-sidekiq/) |
21
- | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-delayed_job.svg)](https://rubygems.org/gems/sentry-delayed_job/) |
22
- | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-resque.svg)](https://rubygems.org/gems/sentry-resque/) |
23
- | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![Downloads](https://img.shields.io/gem/dt/sentry-opentelemetry.svg)](https://rubygems.org/gems/sentry-opentelemetry/) |
16
+ | Current version | Build | Coverage | API doc |
17
+ | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
18
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-ruby) |
19
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-rails) |
20
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-sidekiq) |
21
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-delayed_job) |
22
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-resque) |
23
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-opentelemetry) |
24
24
 
25
25
 
26
26
 
@@ -103,6 +103,6 @@ To learn more about sampling transactions, please visit the [official documentat
103
103
 
104
104
  * [![Ruby docs](https://img.shields.io/badge/documentation-sentry.io-green.svg?label=ruby%20docs)](https://docs.sentry.io/platforms/ruby/)
105
105
  * [![Forum](https://img.shields.io/badge/forum-sentry-green.svg)](https://forum.sentry.io/c/sdks)
106
- * [![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/PXa5Apfe7K)
106
+ * [![Discord Chat](https://img.shields.io/discord/621778831602221064?logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/PXa5Apfe7K)
107
107
  * [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](https://stackoverflow.com/questions/tagged/sentry)
108
108
  * [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry)
data/bin/console CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
+ require "debug"
4
5
  require "sentry-ruby"
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
@@ -1,19 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- class BackpressureMonitor
5
- include LoggingHelper
6
-
4
+ class BackpressureMonitor < ThreadedPeriodicWorker
7
5
  DEFAULT_INTERVAL = 10
8
6
  MAX_DOWNSAMPLE_FACTOR = 10
9
7
 
10
8
  def initialize(configuration, client, interval: DEFAULT_INTERVAL)
11
- @interval = interval
9
+ super(configuration.logger, interval)
12
10
  @client = client
13
- @logger = configuration.logger
14
-
15
- @thread = nil
16
- @exited = false
17
11
 
18
12
  @healthy = true
19
13
  @downsample_factor = 0
@@ -47,29 +41,5 @@ module Sentry
47
41
  log_debug("[BackpressureMonitor] health check negative, downsampling with a factor of #{@downsample_factor}")
48
42
  end
49
43
  end
50
-
51
- def kill
52
- log_debug("[BackpressureMonitor] killing monitor")
53
-
54
- @exited = true
55
- @thread&.kill
56
- end
57
-
58
- private
59
-
60
- def ensure_thread
61
- return if @exited
62
- return if @thread&.alive?
63
-
64
- @thread = Thread.new do
65
- loop do
66
- sleep(@interval)
67
- run
68
- end
69
- end
70
- rescue ThreadError
71
- log_debug("[BackpressureMonitor] Thread creation failed")
72
- @exited = true
73
- end
74
44
  end
75
45
  end
data/lib/sentry/client.rb CHANGED
@@ -49,16 +49,17 @@ module Sentry
49
49
  return unless configuration.sending_allowed?
50
50
 
51
51
  if event.is_a?(ErrorEvent) && !configuration.sample_allowed?
52
- transport.record_lost_event(:sample_rate, 'event')
52
+ transport.record_lost_event(:sample_rate, 'error')
53
53
  return
54
54
  end
55
55
 
56
56
  event_type = event.is_a?(Event) ? event.type : event["type"]
57
+ data_category = Envelope::Item.data_category(event_type)
57
58
  event = scope.apply_to_event(event, hint)
58
59
 
59
60
  if event.nil?
60
61
  log_debug("Discarded event because one of the event processors returned nil")
61
- transport.record_lost_event(:event_processor, event_type)
62
+ transport.record_lost_event(:event_processor, data_category)
62
63
  return
63
64
  end
64
65
 
@@ -66,7 +67,7 @@ module Sentry
66
67
  dispatch_async_event(async_block, event, hint)
67
68
  elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
68
69
  queued = dispatch_background_event(event, hint)
69
- transport.record_lost_event(:queue_overflow, event_type) unless queued
70
+ transport.record_lost_event(:queue_overflow, data_category) unless queued
70
71
  else
71
72
  send_event(event, hint)
72
73
  end
@@ -77,6 +78,20 @@ module Sentry
77
78
  nil
78
79
  end
79
80
 
81
+ # Capture an envelope directly.
82
+ # @param envelope [Envelope] the envelope to be captured.
83
+ # @return [void]
84
+ def capture_envelope(envelope)
85
+ Sentry.background_worker.perform { send_envelope(envelope) }
86
+ end
87
+
88
+ # Flush pending events to Sentry.
89
+ # @return [void]
90
+ def flush
91
+ transport.flush if configuration.sending_to_dsn_allowed?
92
+ spotlight_transport.flush if spotlight_transport
93
+ end
94
+
80
95
  # Initializes an Event object with the given exception. Returns `nil` if the exception's class is excluded from reporting.
81
96
  # @param exception [Exception] the exception to be reported.
82
97
  # @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
@@ -152,13 +167,14 @@ module Sentry
152
167
  # @!macro send_event
153
168
  def send_event(event, hint = nil)
154
169
  event_type = event.is_a?(Event) ? event.type : event["type"]
170
+ data_category = Envelope::Item.data_category(event_type)
155
171
 
156
172
  if event_type != TransactionEvent::TYPE && configuration.before_send
157
173
  event = configuration.before_send.call(event, hint)
158
174
 
159
175
  if event.nil?
160
176
  log_debug("Discarded event because before_send returned nil")
161
- transport.record_lost_event(:before_send, 'event')
177
+ transport.record_lost_event(:before_send, data_category)
162
178
  return
163
179
  end
164
180
  end
@@ -168,7 +184,7 @@ module Sentry
168
184
 
169
185
  if event.nil?
170
186
  log_debug("Discarded event because before_send_transaction returned nil")
171
- transport.record_lost_event(:before_send, 'transaction')
187
+ transport.record_lost_event(:before_send, data_category)
172
188
  return
173
189
  end
174
190
  end
@@ -179,7 +195,23 @@ module Sentry
179
195
  event
180
196
  rescue => e
181
197
  log_error("Event sending failed", e, debug: configuration.debug)
182
- transport.record_lost_event(:network_error, event_type)
198
+ transport.record_lost_event(:network_error, data_category)
199
+ raise
200
+ end
201
+
202
+ # Send an envelope directly to Sentry.
203
+ # @param envelope [Envelope] the envelope to be sent.
204
+ # @return [void]
205
+ def send_envelope(envelope)
206
+ transport.send_envelope(envelope) if configuration.sending_to_dsn_allowed?
207
+ spotlight_transport.send_envelope(envelope) if spotlight_transport
208
+ rescue => e
209
+ log_error("Envelope sending failed", e, debug: configuration.debug)
210
+
211
+ envelope.items.map(&:data_category).each do |data_category|
212
+ transport.record_lost_event(:network_error, data_category)
213
+ end
214
+
183
215
  raise
184
216
  end
185
217
 
@@ -351,7 +351,7 @@ module Sentry
351
351
  def initialize
352
352
  self.app_dirs_pattern = nil
353
353
  self.debug = false
354
- self.background_worker_threads = Concurrent.processor_count
354
+ self.background_worker_threads = (Concurrent.processor_count / 2.0).ceil
355
355
  self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
356
356
  self.backtrace_cleanup_callback = nil
357
357
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
@@ -18,6 +18,23 @@ module Sentry
18
18
  @headers[:type] || 'event'
19
19
  end
20
20
 
21
+ # rate limits and client reports use the data_category rather than envelope item type
22
+ def self.data_category(type)
23
+ case type
24
+ when 'session', 'attachment', 'transaction', 'profile' then type
25
+ when 'sessions' then 'session'
26
+ when 'check_in' then 'monitor'
27
+ when 'statsd', 'metric_meta' then 'metric_bucket'
28
+ when 'event' then 'error'
29
+ when 'client_report' then 'internal'
30
+ else 'default'
31
+ end
32
+ end
33
+
34
+ def data_category
35
+ self.class.data_category(type)
36
+ end
37
+
21
38
  def to_s
22
39
  [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
23
40
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sentry.register_patch(:graphql) do |config|
4
+ if defined?(::GraphQL::Schema) && defined?(::GraphQL::Tracing::SentryTrace) && ::GraphQL::Schema.respond_to?(:trace_with)
5
+ ::GraphQL::Schema.trace_with(::GraphQL::Tracing::SentryTrace, set_transaction_name: true)
6
+ else
7
+ config.logger.warn(Sentry::LOGGER_PROGNAME) { 'You tried to enable the GraphQL integration but no GraphQL gem was detected. Make sure you have the `graphql` gem (>= 2.2.6) in your Gemfile.' }
8
+ end
9
+ end
data/lib/sentry/hub.rb CHANGED
@@ -193,7 +193,12 @@ module Sentry
193
193
  elsif custom_scope = options[:scope]
194
194
  scope.update_from_scope(custom_scope)
195
195
  elsif !options.empty?
196
- scope.update_from_options(**options)
196
+ unsupported_option_keys = scope.update_from_options(**options)
197
+
198
+ configuration.log_debug <<~MSG
199
+ Options #{unsupported_option_keys} are not supported and will not be applied to the event.
200
+ You may want to set them under the `extra` option.
201
+ MSG
197
202
  end
198
203
 
199
204
  event = current_client.capture_event(event, scope, hint)
@@ -279,6 +284,12 @@ module Sentry
279
284
  headers
280
285
  end
281
286
 
287
+ def get_trace_propagation_meta
288
+ get_trace_propagation_headers.map do |k, v|
289
+ "<meta name=\"#{k}\" content=\"#{v}\">"
290
+ end.join("\n")
291
+ end
292
+
282
293
  def continue_trace(env, **options)
283
294
  configure_scope { |s| s.generate_propagation_context(env) }
284
295
 
@@ -2,9 +2,7 @@
2
2
 
3
3
  module Sentry
4
4
  module Metrics
5
- class Aggregator
6
- include LoggingHelper
7
-
5
+ class Aggregator < ThreadedPeriodicWorker
8
6
  FLUSH_INTERVAL = 5
9
7
  ROLLUP_IN_SECONDS = 10
10
8
 
@@ -12,8 +10,18 @@ module Sentry
12
10
  # when we record code locations
13
11
  DEFAULT_STACKLEVEL = 4
14
12
 
15
- KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\/.-]+/
16
- VALUE_SANITIZATION_REGEX = /[^[[:word:]][[:digit:]][[:space:]]_:\/@\.{}\[\]$-]+/
13
+ KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\-.]+/
14
+ UNIT_SANITIZATION_REGEX = /[^a-zA-Z0-9_]+/
15
+ TAG_KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\-.\/]+/
16
+
17
+ TAG_VALUE_SANITIZATION_MAP = {
18
+ "\n" => "\\n",
19
+ "\r" => "\\r",
20
+ "\t" => "\\t",
21
+ "\\" => "\\\\",
22
+ "|" => "\\u{7c}",
23
+ "," => "\\u{2c}"
24
+ }
17
25
 
18
26
  METRIC_TYPES = {
19
27
  c: CounterMetric,
@@ -23,11 +31,11 @@ module Sentry
23
31
  }
24
32
 
25
33
  # exposed only for testing
26
- attr_reader :thread, :buckets, :flush_shift, :code_locations
34
+ attr_reader :client, :thread, :buckets, :flush_shift, :code_locations
27
35
 
28
36
  def initialize(configuration, client)
37
+ super(configuration.logger, FLUSH_INTERVAL)
29
38
  @client = client
30
- @logger = configuration.logger
31
39
  @before_emit = configuration.metrics.before_emit
32
40
  @enable_code_locations = configuration.metrics.enable_code_locations
33
41
  @stacktrace_builder = configuration.stacktrace_builder
@@ -36,8 +44,6 @@ module Sentry
36
44
  @default_tags['release'] = configuration.release if configuration.release
37
45
  @default_tags['environment'] = configuration.environment if configuration.environment
38
46
 
39
- @thread = nil
40
- @exited = false
41
47
  @mutex = Mutex.new
42
48
 
43
49
  # a nested hash of timestamp -> bucket keys -> Metric instance
@@ -107,39 +113,13 @@ module Sentry
107
113
  end
108
114
  end
109
115
 
110
- Sentry.background_worker.perform do
111
- @client.transport.send_envelope(envelope)
112
- end
116
+ @client.capture_envelope(envelope)
113
117
  end
114
118
 
115
- def kill
116
- log_debug('[Metrics::Aggregator] killing thread')
117
-
118
- @exited = true
119
- @thread&.kill
120
- end
119
+ alias_method :run, :flush
121
120
 
122
121
  private
123
122
 
124
- def ensure_thread
125
- return false if @exited
126
- return true if @thread&.alive?
127
-
128
- @thread = Thread.new do
129
- loop do
130
- # TODO-neel-metrics use event for force flush later
131
- sleep(FLUSH_INTERVAL)
132
- flush
133
- end
134
- end
135
-
136
- true
137
- rescue ThreadError
138
- log_debug('[Metrics::Aggregator] thread creation failed')
139
- @exited = true
140
- false
141
- end
142
-
143
123
  # important to sort for key consistency
144
124
  def serialize_tags(tags)
145
125
  tags.flat_map do |k, v|
@@ -182,9 +162,9 @@ module Sentry
182
162
  timestamp_buckets.map do |metric_key, metric|
183
163
  type, key, unit, tags = metric_key
184
164
  values = metric.serialize.join(':')
185
- sanitized_tags = tags.map { |k, v| "#{sanitize_key(k)}:#{sanitize_value(v)}" }.join(',')
165
+ sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(',')
186
166
 
187
- "#{sanitize_key(key)}@#{unit}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
167
+ "#{sanitize_key(key)}@#{sanitize_unit(unit)}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
188
168
  end
189
169
  end.flatten.join("\n")
190
170
  end
@@ -192,7 +172,7 @@ module Sentry
192
172
  def serialize_locations(timestamp, locations)
193
173
  mapping = locations.map do |meta_key, location|
194
174
  type, key, unit = meta_key
195
- mri = "#{type}:#{sanitize_key(key)}@#{unit}"
175
+ mri = "#{type}:#{sanitize_key(key)}@#{sanitize_unit(unit)}"
196
176
 
197
177
  # note this needs to be an array but it really doesn't serve a purpose right now
198
178
  [mri, [location.merge(type: 'location')]]
@@ -205,8 +185,16 @@ module Sentry
205
185
  key.gsub(KEY_SANITIZATION_REGEX, '_')
206
186
  end
207
187
 
208
- def sanitize_value(value)
209
- value.gsub(VALUE_SANITIZATION_REGEX, '')
188
+ def sanitize_unit(unit)
189
+ unit.gsub(UNIT_SANITIZATION_REGEX, '')
190
+ end
191
+
192
+ def sanitize_tag_key(key)
193
+ key.gsub(TAG_KEY_SANITIZATION_REGEX, '')
194
+ end
195
+
196
+ def sanitize_tag_value(value)
197
+ value.chars.map { |c| TAG_VALUE_SANITIZATION_MAP[c] || c }.join
210
198
  end
211
199
 
212
200
  def get_transaction_name
@@ -15,6 +15,7 @@ module Sentry
15
15
  FRACTIONAL_UNITS = %w[ratio percent]
16
16
 
17
17
  OP_NAME = 'metric.timing'
18
+ SPAN_ORIGIN = 'auto.metric.timing'
18
19
 
19
20
  class << self
20
21
  def increment(key, value = 1.0, unit: 'none', tags: {}, timestamp: nil)
@@ -37,7 +38,7 @@ module Sentry
37
38
  return unless block_given?
38
39
  return yield unless DURATION_UNITS.include?(unit)
39
40
 
40
- result, value = Sentry.with_child_span(op: OP_NAME, description: key) do |span|
41
+ result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
41
42
  tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(', ') : v.to_s) } if span
42
43
 
43
44
  start = Timing.send(unit.to_sym)
@@ -8,6 +8,7 @@ module Sentry
8
8
  module Net
9
9
  module HTTP
10
10
  OP_NAME = "http.client"
11
+ SPAN_ORIGIN = "auto.http.net_http"
11
12
  BREADCRUMB_CATEGORY = "net.http"
12
13
 
13
14
  # To explain how the entire thing works, we need to know how the original Net::HTTP#request works
@@ -30,7 +31,7 @@ module Sentry
30
31
  return super unless started? && Sentry.initialized?
31
32
  return super if from_sentry_sdk?
32
33
 
33
- Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
34
+ Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
34
35
  request_info = extract_request_info(req)
35
36
 
36
37
  if propagate_trace?(request_info[:url], Sentry.configuration)
@@ -5,6 +5,7 @@ module Sentry
5
5
  class CaptureExceptions
6
6
  ERROR_EVENT_ID_KEY = "sentry.error_event_id"
7
7
  MECHANISM_TYPE = "rack"
8
+ SPAN_ORIGIN = "auto.http.rack"
8
9
 
9
10
  def initialize(app)
10
11
  @app = app
@@ -63,7 +64,13 @@ module Sentry
63
64
  end
64
65
 
65
66
  def start_transaction(env, scope)
66
- options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
67
+ options = {
68
+ name: scope.transaction_name,
69
+ source: scope.transaction_source,
70
+ op: transaction_op,
71
+ origin: SPAN_ORIGIN
72
+ }
73
+
67
74
  transaction = Sentry.continue_trace(env, **options)
68
75
  Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
69
76
  end
data/lib/sentry/redis.rb CHANGED
@@ -4,6 +4,7 @@ module Sentry
4
4
  # @api private
5
5
  class Redis
6
6
  OP_NAME = "db.redis"
7
+ SPAN_ORIGIN = "auto.db.redis"
7
8
  LOGGER_NAME = :redis_logger
8
9
 
9
10
  def initialize(commands, host, port, db)
@@ -13,7 +14,7 @@ module Sentry
13
14
  def instrument
14
15
  return yield unless Sentry.initialized?
15
16
 
16
- Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |span|
17
+ Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |span|
17
18
  yield.tap do
18
19
  record_breadcrumb
19
20
 
data/lib/sentry/scope.rb CHANGED
@@ -9,8 +9,8 @@ module Sentry
9
9
  include ArgumentCheckingHelper
10
10
 
11
11
  ATTRIBUTES = [
12
- :transaction_names,
13
- :transaction_sources,
12
+ :transaction_name,
13
+ :transaction_source,
14
14
  :contexts,
15
15
  :extra,
16
16
  :tags,
@@ -96,8 +96,8 @@ module Sentry
96
96
  copy.extra = extra.deep_dup
97
97
  copy.tags = tags.deep_dup
98
98
  copy.user = user.deep_dup
99
- copy.transaction_names = transaction_names.dup
100
- copy.transaction_sources = transaction_sources.dup
99
+ copy.transaction_name = transaction_name.dup
100
+ copy.transaction_source = transaction_source.dup
101
101
  copy.fingerprint = fingerprint.deep_dup
102
102
  copy.span = span.deep_dup
103
103
  copy.session = session.deep_dup
@@ -114,8 +114,8 @@ module Sentry
114
114
  self.extra = scope.extra
115
115
  self.tags = scope.tags
116
116
  self.user = scope.user
117
- self.transaction_names = scope.transaction_names
118
- self.transaction_sources = scope.transaction_sources
117
+ self.transaction_name = scope.transaction_name
118
+ self.transaction_source = scope.transaction_source
119
119
  self.fingerprint = scope.fingerprint
120
120
  self.span = scope.span
121
121
  self.propagation_context = scope.propagation_context
@@ -128,14 +128,15 @@ module Sentry
128
128
  # @param user [Hash]
129
129
  # @param level [String, Symbol]
130
130
  # @param fingerprint [Array]
131
- # @return [void]
131
+ # @return [Array]
132
132
  def update_from_options(
133
133
  contexts: nil,
134
134
  extra: nil,
135
135
  tags: nil,
136
136
  user: nil,
137
137
  level: nil,
138
- fingerprint: nil
138
+ fingerprint: nil,
139
+ **options
139
140
  )
140
141
  self.contexts.merge!(contexts) if contexts
141
142
  self.extra.merge!(extra) if extra
@@ -143,6 +144,9 @@ module Sentry
143
144
  self.user = user if user
144
145
  self.level = level if level
145
146
  self.fingerprint = fingerprint if fingerprint
147
+
148
+ # Returns unsupported option keys so we can notify users.
149
+ options.keys
146
150
  end
147
151
 
148
152
  # Sets the scope's rack_env attribute.
@@ -227,8 +231,8 @@ module Sentry
227
231
  # @param transaction_name [String]
228
232
  # @return [void]
229
233
  def set_transaction_name(transaction_name, source: :custom)
230
- @transaction_names << transaction_name
231
- @transaction_sources << source
234
+ @transaction_name = transaction_name
235
+ @transaction_source = source
232
236
  end
233
237
 
234
238
  # Sets the currently active session on the scope.
@@ -238,20 +242,6 @@ module Sentry
238
242
  @session = session
239
243
  end
240
244
 
241
- # Returns current transaction name.
242
- # The "transaction" here does not refer to `Transaction` objects.
243
- # @return [String, nil]
244
- def transaction_name
245
- @transaction_names.last
246
- end
247
-
248
- # Returns current transaction source.
249
- # The "transaction" here does not refer to `Transaction` objects.
250
- # @return [String, nil]
251
- def transaction_source
252
- @transaction_sources.last
253
- end
254
-
255
245
  # These are high cardinality and thus bad.
256
246
  # @return [Boolean]
257
247
  def transaction_source_low_quality?
@@ -307,8 +297,8 @@ module Sentry
307
297
  @user = {}
308
298
  @level = :error
309
299
  @fingerprint = []
310
- @transaction_names = []
311
- @transaction_sources = []
300
+ @transaction_name = nil
301
+ @transaction_source = nil
312
302
  @event_processors = []
313
303
  @rack_env = {}
314
304
  @span = nil
@@ -1,58 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- class SessionFlusher
5
- include LoggingHelper
6
-
4
+ class SessionFlusher < ThreadedPeriodicWorker
7
5
  FLUSH_INTERVAL = 60
8
6
 
9
7
  def initialize(configuration, client)
10
- @thread = nil
11
- @exited = false
8
+ super(configuration.logger, FLUSH_INTERVAL)
12
9
  @client = client
13
10
  @pending_aggregates = {}
14
11
  @release = configuration.release
15
12
  @environment = configuration.environment
16
- @logger = configuration.logger
17
13
 
18
14
  log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
19
15
  end
20
16
 
21
17
  def flush
22
18
  return if @pending_aggregates.empty?
23
- envelope = pending_envelope
24
-
25
- Sentry.background_worker.perform do
26
- @client.transport.send_envelope(envelope)
27
- end
28
19
 
20
+ @client.capture_envelope(pending_envelope)
29
21
  @pending_aggregates = {}
30
22
  end
31
23
 
24
+ alias_method :run, :flush
25
+
32
26
  def add_session(session)
33
- return if @exited
34
27
  return unless @release
35
28
 
36
- begin
37
- ensure_thread
38
- rescue ThreadError
39
- log_debug("Session flusher thread creation failed")
40
- @exited = true
41
- return
42
- end
29
+ return unless ensure_thread
43
30
 
44
31
  return unless Session::AGGREGATE_STATUSES.include?(session.status)
45
32
  @pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
46
33
  @pending_aggregates[session.aggregation_key][session.status] += 1
47
34
  end
48
35
 
49
- def kill
50
- log_debug("Killing session flusher")
51
-
52
- @exited = true
53
- @thread&.kill
54
- end
55
-
56
36
  private
57
37
 
58
38
  def init_aggregates(aggregation_key)
@@ -74,16 +54,5 @@ module Sentry
74
54
  def attrs
75
55
  { release: @release, environment: @environment }
76
56
  end
77
-
78
- def ensure_thread
79
- return if @thread&.alive?
80
-
81
- @thread = Thread.new do
82
- loop do
83
- sleep(FLUSH_INTERVAL)
84
- flush
85
- end
86
- end
87
- end
88
57
  end
89
58
  end
data/lib/sentry/span.rb CHANGED
@@ -39,6 +39,11 @@ module Sentry
39
39
  # Recommended: If different than server.port.
40
40
  # Example: 16456
41
41
  SERVER_SOCKET_PORT = "server.socket.port"
42
+
43
+ FILEPATH = "code.filepath"
44
+ LINENO = "code.lineno"
45
+ FUNCTION = "code.function"
46
+ NAMESPACE = "code.namespace"
42
47
  end
43
48
 
44
49
  STATUS_MAP = {
@@ -55,6 +60,8 @@ module Sentry
55
60
  504 => "deadline_exceeded"
56
61
  }
57
62
 
63
+ DEFAULT_SPAN_ORIGIN = "manual"
64
+
58
65
  # An uuid that can be used to identify a trace.
59
66
  # @return [String]
60
67
  attr_reader :trace_id
@@ -88,6 +95,9 @@ module Sentry
88
95
  # Span data
89
96
  # @return [Hash]
90
97
  attr_reader :data
98
+ # Span origin that tracks what kind of instrumentation created a span
99
+ # @return [String]
100
+ attr_reader :origin
91
101
 
92
102
  # The SpanRecorder the current span belongs to.
93
103
  # SpanRecorder holds all spans under the same Transaction object (including the Transaction itself).
@@ -109,7 +119,8 @@ module Sentry
109
119
  parent_span_id: nil,
110
120
  sampled: nil,
111
121
  start_timestamp: nil,
112
- timestamp: nil
122
+ timestamp: nil,
123
+ origin: nil
113
124
  )
114
125
  @trace_id = trace_id || SecureRandom.uuid.delete("-")
115
126
  @span_id = span_id || SecureRandom.uuid.delete("-").slice(0, 16)
@@ -123,6 +134,7 @@ module Sentry
123
134
  @status = status
124
135
  @data = {}
125
136
  @tags = {}
137
+ @origin = origin || DEFAULT_SPAN_ORIGIN
126
138
  end
127
139
 
128
140
  # Finishes the span by adding a timestamp.
@@ -160,7 +172,8 @@ module Sentry
160
172
  op: @op,
161
173
  status: @status,
162
174
  tags: @tags,
163
- data: @data
175
+ data: @data,
176
+ origin: @origin
164
177
  }
165
178
 
166
179
  summary = metrics_summary
@@ -178,7 +191,8 @@ module Sentry
178
191
  parent_span_id: @parent_span_id,
179
192
  description: @description,
180
193
  op: @op,
181
- status: @status
194
+ status: @status,
195
+ origin: @origin
182
196
  }
183
197
  end
184
198
 
@@ -275,6 +289,12 @@ module Sentry
275
289
  @tags[key] = value
276
290
  end
277
291
 
292
+ # Sets the origin of the span.
293
+ # @param origin [String]
294
+ def set_origin(origin)
295
+ @origin = origin
296
+ end
297
+
278
298
  # Collects gauge metrics on the span for metric summaries.
279
299
  def metrics_local_aggregator
280
300
  @metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
@@ -20,7 +20,7 @@ module Sentry
20
20
  # set transport to DummyTransport, so we can easily intercept the captured events
21
21
  dummy_config.transport.transport_class = Sentry::DummyTransport
22
22
  # make sure SDK allows sending under the current environment
23
- dummy_config.enabled_environments << dummy_config.environment unless dummy_config.enabled_environments.include?(dummy_config.environment)
23
+ dummy_config.enabled_environments += [dummy_config.environment] unless dummy_config.enabled_environments.include?(dummy_config.environment)
24
24
  # disble async event sending
25
25
  dummy_config.background_worker_threads = 0
26
26
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class ThreadedPeriodicWorker
5
+ include LoggingHelper
6
+
7
+ def initialize(logger, internal)
8
+ @thread = nil
9
+ @exited = false
10
+ @interval = internal
11
+ @logger = logger
12
+ end
13
+
14
+ def ensure_thread
15
+ return false if @exited
16
+ return true if @thread&.alive?
17
+
18
+ @thread = Thread.new do
19
+ loop do
20
+ sleep(@interval)
21
+ run
22
+ end
23
+ end
24
+
25
+ true
26
+ rescue ThreadError
27
+ log_debug("[#{self.class.name}] thread creation failed")
28
+ @exited = true
29
+ false
30
+ end
31
+
32
+ def kill
33
+ log_debug("[#{self.class.name}] thread killed")
34
+
35
+ @exited = true
36
+ @thread&.kill
37
+ end
38
+ end
39
+ end
@@ -61,7 +61,7 @@ module Sentry
61
61
  data, serialized_items = serialize_envelope(envelope)
62
62
 
63
63
  if data
64
- log_info("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
64
+ log_debug("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
65
65
  send_data(data)
66
66
  end
67
67
  end
@@ -88,18 +88,9 @@ module Sentry
88
88
  [data, serialized_items]
89
89
  end
90
90
 
91
- def is_rate_limited?(item_type)
91
+ def is_rate_limited?(data_category)
92
92
  # check category-specific limit
93
- category_delay =
94
- case item_type
95
- when "transaction"
96
- @rate_limits["transaction"]
97
- when "sessions"
98
- @rate_limits["session"]
99
- else
100
- @rate_limits["error"]
101
- end
102
-
93
+ category_delay = @rate_limits[data_category]
103
94
  # check universal limit if not category limit
104
95
  universal_delay = @rate_limits[nil]
105
96
 
@@ -160,11 +151,11 @@ module Sentry
160
151
  envelope
161
152
  end
162
153
 
163
- def record_lost_event(reason, item_type)
154
+ def record_lost_event(reason, data_category)
164
155
  return unless @send_client_reports
165
156
  return unless CLIENT_REPORT_REASONS.include?(reason)
166
157
 
167
- @discarded_events[[reason, item_type]] += 1
158
+ @discarded_events[[reason, data_category]] += 1
168
159
  end
169
160
 
170
161
  def flush
@@ -184,11 +175,7 @@ module Sentry
184
175
  return nil if @discarded_events.empty?
185
176
 
186
177
  discarded_events_hash = @discarded_events.map do |key, val|
187
- reason, type = key
188
-
189
- # 'event' has to be mapped to 'error'
190
- category = type == 'event' ? 'error' : type
191
-
178
+ reason, category = key
192
179
  { reason: reason, category: category, quantity: val }
193
180
  end
194
181
 
@@ -206,9 +193,9 @@ module Sentry
206
193
 
207
194
  def reject_rate_limited_items(envelope)
208
195
  envelope.items.reject! do |item|
209
- if is_rate_limited?(item.type)
196
+ if is_rate_limited?(item.data_category)
210
197
  log_debug("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
211
- record_lost_event(:ratelimit_backoff, item.type)
198
+ record_lost_event(:ratelimit_backoff, item.data_category)
212
199
 
213
200
  true
214
201
  else
@@ -11,10 +11,6 @@ module Sentry
11
11
  end
12
12
  end
13
13
 
14
- def log_info(message)
15
- @logger.info(LOGGER_PROGNAME) { message }
16
- end
17
-
18
14
  def log_debug(message)
19
15
  @logger.debug(LOGGER_PROGNAME) { message }
20
16
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.17.2"
4
+ VERSION = "5.18.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -20,6 +20,7 @@ require "sentry/span"
20
20
  require "sentry/transaction"
21
21
  require "sentry/hub"
22
22
  require "sentry/background_worker"
23
+ require "sentry/threaded_periodic_worker"
23
24
  require "sentry/session_flusher"
24
25
  require "sentry/backpressure_monitor"
25
26
  require "sentry/cron/monitor_check_ins"
@@ -257,7 +258,7 @@ module Sentry
257
258
  end
258
259
 
259
260
  if client = get_current_client
260
- client.transport.flush
261
+ client.flush
261
262
 
262
263
  if client.configuration.include_local_variables
263
264
  exception_locals_tp.disable
@@ -550,6 +551,15 @@ module Sentry
550
551
  get_current_hub.get_trace_propagation_headers
551
552
  end
552
553
 
554
+ # Returns the a Hash containing sentry-trace and baggage.
555
+ # Can be either from the currently active span or the propagation context.
556
+ #
557
+ # @return [String]
558
+ def get_trace_propagation_meta
559
+ return '' unless initialized?
560
+ get_current_hub.get_trace_propagation_meta
561
+ end
562
+
553
563
  # Continue an incoming trace from a rack env like hash.
554
564
  #
555
565
  # @param env [Hash]
@@ -590,3 +600,4 @@ end
590
600
  require "sentry/net/http"
591
601
  require "sentry/redis"
592
602
  require "sentry/puma"
603
+ require "sentry/graphql"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.17.2
4
+ version: 5.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-03 00:00:00.000000000 Z
11
+ date: 2024-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -85,6 +85,7 @@ files:
85
85
  - lib/sentry/error_event.rb
86
86
  - lib/sentry/event.rb
87
87
  - lib/sentry/exceptions.rb
88
+ - lib/sentry/graphql.rb
88
89
  - lib/sentry/hub.rb
89
90
  - lib/sentry/integrable.rb
90
91
  - lib/sentry/interface.rb
@@ -121,6 +122,7 @@ files:
121
122
  - lib/sentry/session_flusher.rb
122
123
  - lib/sentry/span.rb
123
124
  - lib/sentry/test_helper.rb
125
+ - lib/sentry/threaded_periodic_worker.rb
124
126
  - lib/sentry/transaction.rb
125
127
  - lib/sentry/transaction_event.rb
126
128
  - lib/sentry/transport.rb