sentry-ruby 5.16.1 → 5.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +20 -10
  4. data/Rakefile +1 -1
  5. data/bin/console +1 -0
  6. data/lib/sentry/attachment.rb +42 -0
  7. data/lib/sentry/background_worker.rb +1 -1
  8. data/lib/sentry/backpressure_monitor.rb +2 -32
  9. data/lib/sentry/backtrace.rb +7 -3
  10. data/lib/sentry/check_in_event.rb +1 -1
  11. data/lib/sentry/client.rb +59 -9
  12. data/lib/sentry/configuration.rb +25 -12
  13. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  14. data/lib/sentry/dsn.rb +1 -1
  15. data/lib/sentry/envelope.rb +18 -1
  16. data/lib/sentry/error_event.rb +2 -2
  17. data/lib/sentry/event.rb +13 -11
  18. data/lib/sentry/faraday.rb +77 -0
  19. data/lib/sentry/graphql.rb +9 -0
  20. data/lib/sentry/hub.rb +15 -2
  21. data/lib/sentry/integrable.rb +4 -0
  22. data/lib/sentry/interface.rb +1 -0
  23. data/lib/sentry/interfaces/exception.rb +5 -3
  24. data/lib/sentry/interfaces/mechanism.rb +20 -0
  25. data/lib/sentry/interfaces/request.rb +2 -2
  26. data/lib/sentry/interfaces/single_exception.rb +6 -4
  27. data/lib/sentry/interfaces/stacktrace_builder.rb +8 -0
  28. data/lib/sentry/metrics/aggregator.rb +248 -0
  29. data/lib/sentry/metrics/configuration.rb +47 -0
  30. data/lib/sentry/metrics/counter_metric.rb +25 -0
  31. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  32. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  33. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  34. data/lib/sentry/metrics/metric.rb +19 -0
  35. data/lib/sentry/metrics/set_metric.rb +28 -0
  36. data/lib/sentry/metrics/timing.rb +43 -0
  37. data/lib/sentry/metrics.rb +56 -0
  38. data/lib/sentry/net/http.rb +17 -38
  39. data/lib/sentry/propagation_context.rb +9 -8
  40. data/lib/sentry/puma.rb +1 -1
  41. data/lib/sentry/rack/capture_exceptions.rb +14 -2
  42. data/lib/sentry/rake.rb +3 -1
  43. data/lib/sentry/redis.rb +2 -1
  44. data/lib/sentry/scope.rb +35 -26
  45. data/lib/sentry/session.rb +2 -2
  46. data/lib/sentry/session_flusher.rb +6 -38
  47. data/lib/sentry/span.rb +40 -5
  48. data/lib/sentry/test_helper.rb +2 -1
  49. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  50. data/lib/sentry/transaction.rb +16 -14
  51. data/lib/sentry/transaction_event.rb +5 -0
  52. data/lib/sentry/transport/configuration.rb +0 -1
  53. data/lib/sentry/transport.rb +14 -22
  54. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  55. data/lib/sentry/utils/http_tracing.rb +41 -0
  56. data/lib/sentry/utils/logging_helper.rb +0 -4
  57. data/lib/sentry/utils/real_ip.rb +1 -1
  58. data/lib/sentry/utils/request_id.rb +1 -1
  59. data/lib/sentry/version.rb +1 -1
  60. data/lib/sentry-ruby.rb +34 -3
  61. data/sentry-ruby.gemspec +12 -5
  62. metadata +39 -7
data/lib/sentry/span.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require "sentry/metrics/local_aggregator"
4
5
 
5
6
  module Sentry
6
7
  class Span
7
-
8
8
  # We will try to be consistent with OpenTelemetry on this front going forward.
9
9
  # https://develop.sentry.dev/sdk/performance/span-data-conventions/
10
10
  module DataConventions
@@ -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.
@@ -150,7 +162,7 @@ module Sentry
150
162
 
151
163
  # @return [Hash]
152
164
  def to_hash
153
- {
165
+ hash = {
154
166
  trace_id: @trace_id,
155
167
  span_id: @span_id,
156
168
  parent_span_id: @parent_span_id,
@@ -160,8 +172,14 @@ 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
  }
178
+
179
+ summary = metrics_summary
180
+ hash[:_metrics_summary] = summary if summary
181
+
182
+ hash
165
183
  end
166
184
 
167
185
  # Returns the span's context that can be used to embed in an Event.
@@ -173,7 +191,9 @@ module Sentry
173
191
  parent_span_id: @parent_span_id,
174
192
  description: @description,
175
193
  op: @op,
176
- status: @status
194
+ status: @status,
195
+ origin: @origin,
196
+ data: @data
177
197
  }
178
198
  end
179
199
 
@@ -269,5 +289,20 @@ module Sentry
269
289
  def set_tag(key, value)
270
290
  @tags[key] = value
271
291
  end
292
+
293
+ # Sets the origin of the span.
294
+ # @param origin [String]
295
+ def set_origin(origin)
296
+ @origin = origin
297
+ end
298
+
299
+ # Collects gauge metrics on the span for metric summaries.
300
+ def metrics_local_aggregator
301
+ @metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
302
+ end
303
+
304
+ def metrics_summary
305
+ @metrics_local_aggregator&.to_hash
306
+ end
272
307
  end
273
308
  end
@@ -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
 
@@ -50,6 +50,7 @@ module Sentry
50
50
  if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
51
51
  Sentry.get_current_hub.pop_scope
52
52
  end
53
+ Sentry::Scope.global_event_processors.clear
53
54
  end
54
55
 
55
56
  # @return [Transport]
@@ -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
@@ -13,7 +13,7 @@ module Sentry
13
13
  MESSAGE_PREFIX = "[Tracing]"
14
14
 
15
15
  # https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
16
- SOURCES = %i(custom url route view component task)
16
+ SOURCES = %i[custom url route view component task]
17
17
 
18
18
  include LoggingHelper
19
19
 
@@ -110,14 +110,15 @@ module Sentry
110
110
 
111
111
  trace_id, parent_span_id, parent_sampled = sentry_trace_data
112
112
 
113
- baggage = if baggage && !baggage.empty?
114
- Baggage.from_incoming_header(baggage)
115
- else
116
- # If there's an incoming sentry-trace but no incoming baggage header,
117
- # for instance in traces coming from older SDKs,
118
- # baggage will be empty and frozen and won't be populated as head SDK.
119
- Baggage.new({})
120
- end
113
+ baggage =
114
+ if baggage && !baggage.empty?
115
+ Baggage.from_incoming_header(baggage)
116
+ else
117
+ # If there's an incoming sentry-trace but no incoming baggage header,
118
+ # for instance in traces coming from older SDKs,
119
+ # baggage will be empty and frozen and won't be populated as head SDK.
120
+ Baggage.new({})
121
+ end
121
122
 
122
123
  baggage.freeze!
123
124
 
@@ -265,6 +266,7 @@ module Sentry
265
266
  is_backpressure = Sentry.backpressure_monitor&.downsample_factor&.positive?
266
267
  reason = is_backpressure ? :backpressure : :sample_rate
267
268
  hub.current_client.transport.record_lost_event(reason, 'transaction')
269
+ hub.current_client.transport.record_lost_event(reason, 'span')
268
270
  end
269
271
  end
270
272
 
@@ -301,6 +303,11 @@ module Sentry
301
303
  profiler.start
302
304
  end
303
305
 
306
+ # These are high cardinality and thus bad
307
+ def source_low_quality?
308
+ source == :url
309
+ end
310
+
304
311
  protected
305
312
 
306
313
  def init_span_recorder(limit = 1000)
@@ -336,11 +343,6 @@ module Sentry
336
343
  @baggage = Baggage.new(items, mutable: false)
337
344
  end
338
345
 
339
- # These are high cardinality and thus bad
340
- def source_low_quality?
341
- source == :url
342
- end
343
-
344
346
  class SpanRecorder
345
347
  attr_reader :max_length, :spans
346
348
 
@@ -17,6 +17,9 @@ module Sentry
17
17
  # @return [Hash, nil]
18
18
  attr_accessor :profile
19
19
 
20
+ # @return [Hash, nil]
21
+ attr_accessor :metrics_summary
22
+
20
23
  def initialize(transaction:, **options)
21
24
  super(**options)
22
25
 
@@ -29,6 +32,7 @@ module Sentry
29
32
  self.tags = transaction.tags
30
33
  self.dynamic_sampling_context = transaction.get_baggage.dynamic_sampling_context
31
34
  self.measurements = transaction.measurements
35
+ self.metrics_summary = transaction.metrics_summary
32
36
 
33
37
  finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
34
38
  self.spans = finished_spans.map(&:to_hash)
@@ -49,6 +53,7 @@ module Sentry
49
53
  data[:spans] = @spans.map(&:to_hash) if @spans
50
54
  data[:start_timestamp] = @start_timestamp
51
55
  data[:measurements] = @measurements
56
+ data[:_metrics_summary] = @metrics_summary if @metrics_summary
52
57
  data
53
58
  end
54
59
 
@@ -3,7 +3,6 @@
3
3
  module Sentry
4
4
  class Transport
5
5
  class Configuration
6
-
7
6
  # The timeout in seconds to open a connection to Sentry, in seconds.
8
7
  # Default value is 2.
9
8
  #
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
- require "base64"
5
4
  require "sentry/envelope"
6
5
 
7
6
  module Sentry
@@ -62,7 +61,7 @@ module Sentry
62
61
  data, serialized_items = serialize_envelope(envelope)
63
62
 
64
63
  if data
65
- 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")
66
65
  send_data(data)
67
66
  end
68
67
  end
@@ -89,18 +88,9 @@ module Sentry
89
88
  [data, serialized_items]
90
89
  end
91
90
 
92
- def is_rate_limited?(item_type)
91
+ def is_rate_limited?(data_category)
93
92
  # check category-specific limit
94
- category_delay =
95
- case item_type
96
- when "transaction"
97
- @rate_limits["transaction"]
98
- when "sessions"
99
- @rate_limits["session"]
100
- else
101
- @rate_limits["error"]
102
- end
103
-
93
+ category_delay = @rate_limits[data_category]
104
94
  # check universal limit if not category limit
105
95
  universal_delay = @rate_limits[nil]
106
96
 
@@ -155,17 +145,23 @@ module Sentry
155
145
  )
156
146
  end
157
147
 
148
+ if event.is_a?(Event) && event.attachments.any?
149
+ event.attachments.each do |attachment|
150
+ envelope.add_item(attachment.to_envelope_headers, attachment.payload)
151
+ end
152
+ end
153
+
158
154
  client_report_headers, client_report_payload = fetch_pending_client_report
159
155
  envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
160
156
 
161
157
  envelope
162
158
  end
163
159
 
164
- def record_lost_event(reason, item_type)
160
+ def record_lost_event(reason, data_category, num: 1)
165
161
  return unless @send_client_reports
166
162
  return unless CLIENT_REPORT_REASONS.include?(reason)
167
163
 
168
- @discarded_events[[reason, item_type]] += 1
164
+ @discarded_events[[reason, data_category]] += num
169
165
  end
170
166
 
171
167
  def flush
@@ -185,11 +181,7 @@ module Sentry
185
181
  return nil if @discarded_events.empty?
186
182
 
187
183
  discarded_events_hash = @discarded_events.map do |key, val|
188
- reason, type = key
189
-
190
- # 'event' has to be mapped to 'error'
191
- category = type == 'event' ? 'error' : type
192
-
184
+ reason, category = key
193
185
  { reason: reason, category: category, quantity: val }
194
186
  end
195
187
 
@@ -207,9 +199,9 @@ module Sentry
207
199
 
208
200
  def reject_rate_limited_items(envelope)
209
201
  envelope.items.reject! do |item|
210
- if is_rate_limited?(item.type)
202
+ if is_rate_limited?(item.data_category)
211
203
  log_debug("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
212
- record_lost_event(:ratelimit_backoff, item.type)
204
+ record_lost_event(:ratelimit_backoff, item.data_category)
213
205
 
214
206
  true
215
207
  else
@@ -15,5 +15,11 @@ module Sentry
15
15
  raise ArgumentError, "expect the argument to be one of #{values.map(&:inspect).join(' or ')}, got #{argument.inspect}"
16
16
  end
17
17
  end
18
+
19
+ def check_callable!(name, value)
20
+ unless value == nil || value.respond_to?(:call)
21
+ raise ArgumentError, "#{name} must be callable (or nil to disable)"
22
+ end
23
+ end
18
24
  end
19
25
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Utils
5
+ module HttpTracing
6
+ def set_span_info(sentry_span, request_info, response_status)
7
+ sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
8
+ sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
9
+ sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
10
+ sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
11
+ sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, response_status)
12
+ end
13
+
14
+ def set_propagation_headers(req)
15
+ Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
16
+ end
17
+
18
+ def record_sentry_breadcrumb(request_info, response_status)
19
+ crumb = Sentry::Breadcrumb.new(
20
+ level: :info,
21
+ category: self.class::BREADCRUMB_CATEGORY,
22
+ type: :info,
23
+ data: { status: response_status, **request_info }
24
+ )
25
+
26
+ Sentry.add_breadcrumb(crumb)
27
+ end
28
+
29
+ def record_sentry_breadcrumb?
30
+ Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
31
+ end
32
+
33
+ def propagate_trace?(url)
34
+ url &&
35
+ Sentry.initialized? &&
36
+ Sentry.configuration.propagate_traces &&
37
+ Sentry.configuration.trace_propagation_targets.any? { |target| url.match?(target) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -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
@@ -15,7 +15,7 @@ module Sentry
15
15
  "fc00::/7", # private IPv6 range fc00::/7
16
16
  "10.0.0.0/8", # private IPv4 range 10.x.x.x
17
17
  "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
18
- "192.168.0.0/16", # private IPv4 range 192.168.x.x
18
+ "192.168.0.0/16" # private IPv4 range 192.168.x.x
19
19
  ]
20
20
 
21
21
  attr_reader :ip
@@ -3,7 +3,7 @@
3
3
  module Sentry
4
4
  module Utils
5
5
  module RequestId
6
- REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
6
+ REQUEST_ID_HEADERS = %w[action_dispatch.request_id HTTP_X_REQUEST_ID].freeze
7
7
 
8
8
  # Request ID based on ActionDispatch::RequestId
9
9
  def self.read_from(env)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.16.1"
4
+ VERSION = "5.19.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -20,13 +20,15 @@ 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"
27
+ require "sentry/metrics"
26
28
 
27
29
  [
28
30
  "sentry/rake",
29
- "sentry/rack",
31
+ "sentry/rack"
30
32
  ].each do |lib|
31
33
  begin
32
34
  require lib
@@ -77,6 +79,10 @@ module Sentry
77
79
  # @return [BackpressureMonitor, nil]
78
80
  attr_reader :backpressure_monitor
79
81
 
82
+ # @!attribute [r] metrics_aggregator
83
+ # @return [Metrics::Aggregator, nil]
84
+ attr_reader :metrics_aggregator
85
+
80
86
  ##### Patch Registration #####
81
87
 
82
88
  # @!visibility private
@@ -205,6 +211,13 @@ module Sentry
205
211
  get_current_scope.set_context(*args)
206
212
  end
207
213
 
214
+ # @!method add_attachment
215
+ # @!macro add_attachment
216
+ def add_attachment(**opts)
217
+ return unless initialized?
218
+ get_current_scope.add_attachment(**opts)
219
+ end
220
+
208
221
  ##### Main APIs #####
209
222
 
210
223
  # Initializes the SDK with given configuration.
@@ -222,8 +235,9 @@ module Sentry
222
235
  Thread.current.thread_variable_set(THREAD_LOCAL, hub)
223
236
  @main_hub = hub
224
237
  @background_worker = Sentry::BackgroundWorker.new(config)
225
- @session_flusher = config.auto_session_tracking ? Sentry::SessionFlusher.new(config, client) : nil
238
+ @session_flusher = config.session_tracking? ? Sentry::SessionFlusher.new(config, client) : nil
226
239
  @backpressure_monitor = config.enable_backpressure_handling ? Sentry::BackpressureMonitor.new(config, client) : nil
240
+ @metrics_aggregator = config.metrics.enabled ? Sentry::Metrics::Aggregator.new(config, client) : nil
227
241
  exception_locals_tp.enable if config.include_local_variables
228
242
  at_exit { close }
229
243
  end
@@ -244,8 +258,14 @@ module Sentry
244
258
  @backpressure_monitor = nil
245
259
  end
246
260
 
261
+ if @metrics_aggregator
262
+ @metrics_aggregator.flush(force: true)
263
+ @metrics_aggregator.kill
264
+ @metrics_aggregator = nil
265
+ end
266
+
247
267
  if client = get_current_client
248
- client.transport.flush
268
+ client.flush
249
269
 
250
270
  if client.configuration.include_local_variables
251
271
  exception_locals_tp.disable
@@ -538,6 +558,15 @@ module Sentry
538
558
  get_current_hub.get_trace_propagation_headers
539
559
  end
540
560
 
561
+ # Returns the a Hash containing sentry-trace and baggage.
562
+ # Can be either from the currently active span or the propagation context.
563
+ #
564
+ # @return [String]
565
+ def get_trace_propagation_meta
566
+ return '' unless initialized?
567
+ get_current_hub.get_trace_propagation_meta
568
+ end
569
+
541
570
  # Continue an incoming trace from a rack env like hash.
542
571
  #
543
572
  # @param env [Hash]
@@ -578,3 +607,5 @@ end
578
607
  require "sentry/net/http"
579
608
  require "sentry/redis"
580
609
  require "sentry/puma"
610
+ require "sentry/graphql"
611
+ require "sentry/faraday"
data/sentry-ruby.gemspec CHANGED
@@ -7,18 +7,25 @@ Gem::Specification.new do |spec|
7
7
  spec.description = spec.summary = "A gem that provides a client interface for the Sentry error logger"
8
8
  spec.email = "accounts@sentry.io"
9
9
  spec.license = 'MIT'
10
- spec.homepage = "https://github.com/getsentry/sentry-ruby"
11
10
 
12
11
  spec.platform = Gem::Platform::RUBY
13
12
  spec.required_ruby_version = '>= 2.4'
14
13
  spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
15
14
  spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples)'`.split("\n")
16
15
 
17
- spec.metadata["homepage_uri"] = spec.homepage
18
- spec.metadata["source_code_uri"] = spec.homepage
19
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
16
+ github_root_uri = 'https://github.com/getsentry/sentry-ruby'
17
+ spec.homepage = "#{github_root_uri}/tree/#{spec.version}/#{spec.name}"
18
+
19
+ spec.metadata = {
20
+ "homepage_uri" => spec.homepage,
21
+ "source_code_uri" => spec.homepage,
22
+ "changelog_uri" => "#{github_root_uri}/blob/#{spec.version}/CHANGELOG.md",
23
+ "bug_tracker_uri" => "#{github_root_uri}/issues",
24
+ "documentation_uri" => "http://www.rubydoc.info/gems/#{spec.name}/#{spec.version}"
25
+ }
20
26
 
21
27
  spec.require_paths = ["lib"]
22
28
 
23
- spec.add_dependency "concurrent-ruby", '~> 1.0', '>= 1.0.2'
29
+ spec.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
30
+ spec.add_dependency "bigdecimal"
24
31
  end