sentry-ruby 5.17.2 → 5.18.0

Sign up to get free protection for your applications and to get access to all the features.
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