sentry-ruby 5.16.1 → 5.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -0
  3. data/README.md +20 -10
  4. data/Rakefile +3 -1
  5. data/bin/console +2 -0
  6. data/lib/sentry/attachment.rb +40 -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 +10 -8
  10. data/lib/sentry/baggage.rb +7 -7
  11. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  12. data/lib/sentry/check_in_event.rb +5 -5
  13. data/lib/sentry/client.rb +61 -11
  14. data/lib/sentry/configuration.rb +77 -31
  15. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  16. data/lib/sentry/cron/monitor_check_ins.rb +3 -1
  17. data/lib/sentry/cron/monitor_config.rb +1 -1
  18. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  19. data/lib/sentry/dsn.rb +4 -4
  20. data/lib/sentry/envelope/item.rb +88 -0
  21. data/lib/sentry/envelope.rb +2 -68
  22. data/lib/sentry/error_event.rb +2 -2
  23. data/lib/sentry/event.rb +20 -18
  24. data/lib/sentry/faraday.rb +77 -0
  25. data/lib/sentry/graphql.rb +9 -0
  26. data/lib/sentry/hub.rb +23 -3
  27. data/lib/sentry/integrable.rb +4 -0
  28. data/lib/sentry/interface.rb +1 -0
  29. data/lib/sentry/interfaces/exception.rb +5 -3
  30. data/lib/sentry/interfaces/mechanism.rb +20 -0
  31. data/lib/sentry/interfaces/request.rb +7 -7
  32. data/lib/sentry/interfaces/single_exception.rb +9 -7
  33. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  34. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  35. data/lib/sentry/logger.rb +1 -1
  36. data/lib/sentry/metrics/aggregator.rb +248 -0
  37. data/lib/sentry/metrics/configuration.rb +47 -0
  38. data/lib/sentry/metrics/counter_metric.rb +25 -0
  39. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  40. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  41. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  42. data/lib/sentry/metrics/metric.rb +19 -0
  43. data/lib/sentry/metrics/set_metric.rb +28 -0
  44. data/lib/sentry/metrics/timing.rb +43 -0
  45. data/lib/sentry/metrics.rb +56 -0
  46. data/lib/sentry/net/http.rb +18 -39
  47. data/lib/sentry/profiler/helpers.rb +46 -0
  48. data/lib/sentry/profiler.rb +25 -56
  49. data/lib/sentry/propagation_context.rb +10 -9
  50. data/lib/sentry/puma.rb +1 -1
  51. data/lib/sentry/rack/capture_exceptions.rb +16 -4
  52. data/lib/sentry/rack.rb +2 -2
  53. data/lib/sentry/rake.rb +4 -2
  54. data/lib/sentry/redis.rb +2 -1
  55. data/lib/sentry/release_detector.rb +4 -4
  56. data/lib/sentry/scope.rb +36 -26
  57. data/lib/sentry/session.rb +2 -2
  58. data/lib/sentry/session_flusher.rb +7 -39
  59. data/lib/sentry/span.rb +46 -5
  60. data/lib/sentry/test_helper.rb +5 -2
  61. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  62. data/lib/sentry/transaction.rb +19 -17
  63. data/lib/sentry/transaction_event.rb +6 -2
  64. data/lib/sentry/transport/configuration.rb +0 -1
  65. data/lib/sentry/transport/http_transport.rb +12 -12
  66. data/lib/sentry/transport.rb +18 -26
  67. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  68. data/lib/sentry/utils/env_helper.rb +21 -0
  69. data/lib/sentry/utils/http_tracing.rb +41 -0
  70. data/lib/sentry/utils/logging_helper.rb +0 -4
  71. data/lib/sentry/utils/real_ip.rb +2 -2
  72. data/lib/sentry/utils/request_id.rb +1 -1
  73. data/lib/sentry/vernier/output.rb +89 -0
  74. data/lib/sentry/vernier/profiler.rb +125 -0
  75. data/lib/sentry/version.rb +1 -1
  76. data/lib/sentry-ruby.rb +38 -6
  77. data/sentry-ruby-core.gemspec +3 -1
  78. data/sentry-ruby.gemspec +15 -6
  79. metadata +44 -7
@@ -7,7 +7,7 @@ module Sentry
7
7
  class HTTPTransport < Transport
8
8
  GZIP_ENCODING = "gzip"
9
9
  GZIP_THRESHOLD = 1024 * 30
10
- CONTENT_TYPE = 'application/x-sentry-envelope'
10
+ CONTENT_TYPE = "application/x-sentry-envelope"
11
11
 
12
12
  DEFAULT_DELAY = 60
13
13
  RETRY_AFTER_HEADER = "retry-after"
@@ -38,13 +38,13 @@ module Sentry
38
38
  end
39
39
 
40
40
  headers = {
41
- 'Content-Type' => CONTENT_TYPE,
42
- 'Content-Encoding' => encoding,
43
- 'User-Agent' => USER_AGENT
41
+ "Content-Type" => CONTENT_TYPE,
42
+ "Content-Encoding" => encoding,
43
+ "User-Agent" => USER_AGENT
44
44
  }
45
45
 
46
46
  auth_header = generate_auth_header
47
- headers['X-Sentry-Auth'] = auth_header if auth_header
47
+ headers["X-Sentry-Auth"] = auth_header if auth_header
48
48
 
49
49
  response = conn.start do |http|
50
50
  request = ::Net::HTTP::Post.new(endpoint, headers)
@@ -60,7 +60,7 @@ module Sentry
60
60
  else
61
61
  error_info = "the server responded with status #{response.code}"
62
62
  error_info += "\nbody: #{response.body}"
63
- error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
63
+ error_info += " Error in headers is: #{response['x-sentry-error']}" if response["x-sentry-error"]
64
64
 
65
65
  raise Sentry::ExternalError, error_info
66
66
  end
@@ -78,13 +78,13 @@ module Sentry
78
78
 
79
79
  now = Sentry.utc_now.to_i
80
80
  fields = {
81
- 'sentry_version' => PROTOCOL_VERSION,
82
- 'sentry_client' => USER_AGENT,
83
- 'sentry_timestamp' => now,
84
- 'sentry_key' => @dsn.public_key
81
+ "sentry_version" => PROTOCOL_VERSION,
82
+ "sentry_client" => USER_AGENT,
83
+ "sentry_timestamp" => now,
84
+ "sentry_key" => @dsn.public_key
85
85
  }
86
- fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
87
- 'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
86
+ fields["sentry_secret"] = @dsn.secret_key if @dsn.secret_key
87
+ "Sentry " + fields.map { |key, value| "#{key}=#{value}" }.join(", ")
88
88
  end
89
89
 
90
90
  def conn
@@ -1,12 +1,11 @@
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
8
7
  class Transport
9
- PROTOCOL_VERSION = '7'
8
+ PROTOCOL_VERSION = "7"
10
9
  USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
11
10
  CLIENT_REPORT_INTERVAL = 30
12
11
 
@@ -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
 
@@ -144,28 +134,34 @@ module Sentry
144
134
  envelope = Envelope.new(envelope_headers)
145
135
 
146
136
  envelope.add_item(
147
- { type: item_type, content_type: 'application/json' },
137
+ { type: item_type, content_type: "application/json" },
148
138
  event_payload
149
139
  )
150
140
 
151
141
  if event.is_a?(TransactionEvent) && event.profile
152
142
  envelope.add_item(
153
- { type: 'profile', content_type: 'application/json' },
143
+ { type: "profile", content_type: "application/json" },
154
144
  event.profile
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,15 +181,11 @@ 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
 
196
- item_header = { type: 'client_report' }
188
+ item_header = { type: "client_report" }
197
189
  item_payload = {
198
190
  timestamp: Sentry.utc_now.iso8601,
199
191
  discarded_events: discarded_events_hash
@@ -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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Utils
5
+ module EnvHelper
6
+ TRUTHY_ENV_VALUES = %w[t true yes y 1 on].freeze
7
+ FALSY_ENV_VALUES = %w[f false no n 0 off].freeze
8
+
9
+ def self.env_to_bool(value, strict: false)
10
+ value = value.to_s
11
+ normalized = value.downcase
12
+
13
+ return false if FALSY_ENV_VALUES.include?(normalized)
14
+
15
+ return true if TRUTHY_ENV_VALUES.include?(normalized)
16
+
17
+ strict ? nil : !(value.nil? || value.empty?)
18
+ end
19
+ end
20
+ end
21
+ 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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ipaddr'
3
+ require "ipaddr"
4
4
 
5
5
  # Based on ActionDispatch::RemoteIp. All security-related precautions from that
6
6
  # middleware have been removed, because the Event IP just needs to be accurate,
@@ -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)
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rbconfig"
5
+
6
+ module Sentry
7
+ module Vernier
8
+ class Output
9
+ include Profiler::Helpers
10
+
11
+ attr_reader :profile
12
+
13
+ def initialize(profile, project_root:, in_app_pattern:, app_dirs_pattern:)
14
+ @profile = profile
15
+ @project_root = project_root
16
+ @in_app_pattern = in_app_pattern
17
+ @app_dirs_pattern = app_dirs_pattern
18
+ end
19
+
20
+ def to_h
21
+ @to_h ||= {
22
+ frames: frames,
23
+ stacks: stacks,
24
+ samples: samples,
25
+ thread_metadata: thread_metadata
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def thread_metadata
32
+ profile.threads.map { |thread_id, thread_info|
33
+ [thread_id, { name: thread_info[:name] }]
34
+ }.to_h
35
+ end
36
+
37
+ def samples
38
+ profile.threads.flat_map { |thread_id, thread_info|
39
+ started_at = thread_info[:started_at]
40
+ samples, timestamps = thread_info.values_at(:samples, :timestamps)
41
+
42
+ samples.zip(timestamps).map { |stack_id, timestamp|
43
+ elapsed_since_start_ns = timestamp - started_at
44
+
45
+ next if elapsed_since_start_ns < 0
46
+
47
+ {
48
+ thread_id: thread_id.to_s,
49
+ stack_id: stack_id,
50
+ elapsed_since_start_ns: elapsed_since_start_ns.to_s
51
+ }
52
+ }.compact
53
+ }
54
+ end
55
+
56
+ def frames
57
+ funcs = stack_table_hash[:frame_table].fetch(:func)
58
+ lines = stack_table_hash[:func_table].fetch(:first_line)
59
+
60
+ funcs.map do |idx|
61
+ function, mod = split_module(stack_table_hash[:func_table][:name][idx])
62
+
63
+ abs_path = stack_table_hash[:func_table][:filename][idx]
64
+ in_app = in_app?(abs_path)
65
+ filename = compute_filename(abs_path, in_app)
66
+
67
+ {
68
+ function: function,
69
+ module: mod,
70
+ filename: filename,
71
+ abs_path: abs_path,
72
+ lineno: (lineno = lines[idx]) > 0 ? lineno : nil,
73
+ in_app: in_app
74
+ }.compact
75
+ end
76
+ end
77
+
78
+ def stacks
79
+ profile._stack_table.stack_count.times.map do |stack_id|
80
+ profile.stack(stack_id).frames.map(&:idx)
81
+ end
82
+ end
83
+
84
+ def stack_table_hash
85
+ @stack_table_hash ||= profile._stack_table.to_h
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require_relative "../profiler/helpers"
5
+ require_relative "output"
6
+
7
+ module Sentry
8
+ module Vernier
9
+ class Profiler
10
+ EMPTY_RESULT = {}.freeze
11
+
12
+ attr_reader :started, :event_id, :result
13
+
14
+ def initialize(configuration)
15
+ @event_id = SecureRandom.uuid.delete("-")
16
+
17
+ @started = false
18
+ @sampled = nil
19
+
20
+ @profiling_enabled = defined?(Vernier) && configuration.profiling_enabled?
21
+ @profiles_sample_rate = configuration.profiles_sample_rate
22
+ @project_root = configuration.project_root
23
+ @app_dirs_pattern = configuration.app_dirs_pattern
24
+ @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
25
+ end
26
+
27
+ def set_initial_sample_decision(transaction_sampled)
28
+ unless @profiling_enabled
29
+ @sampled = false
30
+ return
31
+ end
32
+
33
+ unless transaction_sampled
34
+ @sampled = false
35
+ log("Discarding profile because transaction not sampled")
36
+ return
37
+ end
38
+
39
+ case @profiles_sample_rate
40
+ when 0.0
41
+ @sampled = false
42
+ log("Discarding profile because sample_rate is 0")
43
+ return
44
+ when 1.0
45
+ @sampled = true
46
+ return
47
+ else
48
+ @sampled = Random.rand < @profiles_sample_rate
49
+ end
50
+
51
+ log("Discarding profile due to sampling decision") unless @sampled
52
+ end
53
+
54
+ def start
55
+ return unless @sampled
56
+ return if @started
57
+
58
+ ::Vernier.start_profile
59
+ @started = true
60
+
61
+ log("Started")
62
+
63
+ @started
64
+ rescue RuntimeError => e
65
+ # TODO: once Vernier raises something more dedicated, we should catch that instead
66
+ if e.message.include?("Profile already started")
67
+ log("Not started since running elsewhere")
68
+ else
69
+ log("Failed to start: #{e.message}")
70
+ end
71
+ end
72
+
73
+ def stop
74
+ return unless @sampled
75
+ return unless @started
76
+
77
+ @result = ::Vernier.stop_profile
78
+
79
+ log("Stopped")
80
+ end
81
+
82
+ def active_thread_id
83
+ Thread.current.object_id
84
+ end
85
+
86
+ def to_hash
87
+ return EMPTY_RESULT unless @started
88
+
89
+ unless @sampled
90
+ record_lost_event(:sample_rate)
91
+ return EMPTY_RESULT
92
+ end
93
+
94
+ { **profile_meta, profile: output.to_h }
95
+ end
96
+
97
+ private
98
+
99
+ def log(message)
100
+ Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler::Vernier] #{message}" }
101
+ end
102
+
103
+ def record_lost_event(reason)
104
+ Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
105
+ end
106
+
107
+ def profile_meta
108
+ {
109
+ event_id: @event_id,
110
+ version: "1",
111
+ platform: "ruby"
112
+ }
113
+ end
114
+
115
+ def output
116
+ @output ||= Output.new(
117
+ result,
118
+ project_root: @project_root,
119
+ app_dirs_pattern: @app_dirs_pattern,
120
+ in_app_pattern: @in_app_pattern
121
+ )
122
+ end
123
+ end
124
+ end
125
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.16.1"
4
+ VERSION = "5.21.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -20,13 +20,16 @@ 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"
28
+ require "sentry/vernier/profiler"
26
29
 
27
30
  [
28
31
  "sentry/rake",
29
- "sentry/rack",
32
+ "sentry/rack"
30
33
  ].each do |lib|
31
34
  begin
32
35
  require lib
@@ -39,11 +42,11 @@ module Sentry
39
42
 
40
43
  CAPTURED_SIGNATURE = :@__sentry_captured
41
44
 
42
- LOGGER_PROGNAME = "sentry".freeze
45
+ LOGGER_PROGNAME = "sentry"
43
46
 
44
- SENTRY_TRACE_HEADER_NAME = "sentry-trace".freeze
47
+ SENTRY_TRACE_HEADER_NAME = "sentry-trace"
45
48
 
46
- BAGGAGE_HEADER_NAME = "baggage".freeze
49
+ BAGGAGE_HEADER_NAME = "baggage"
47
50
 
48
51
  THREAD_LOCAL = :sentry_hub
49
52
 
@@ -77,6 +80,10 @@ module Sentry
77
80
  # @return [BackpressureMonitor, nil]
78
81
  attr_reader :backpressure_monitor
79
82
 
83
+ # @!attribute [r] metrics_aggregator
84
+ # @return [Metrics::Aggregator, nil]
85
+ attr_reader :metrics_aggregator
86
+
80
87
  ##### Patch Registration #####
81
88
 
82
89
  # @!visibility private
@@ -205,6 +212,13 @@ module Sentry
205
212
  get_current_scope.set_context(*args)
206
213
  end
207
214
 
215
+ # @!method add_attachment
216
+ # @!macro add_attachment
217
+ def add_attachment(**opts)
218
+ return unless initialized?
219
+ get_current_scope.add_attachment(**opts)
220
+ end
221
+
208
222
  ##### Main APIs #####
209
223
 
210
224
  # Initializes the SDK with given configuration.
@@ -222,8 +236,9 @@ module Sentry
222
236
  Thread.current.thread_variable_set(THREAD_LOCAL, hub)
223
237
  @main_hub = hub
224
238
  @background_worker = Sentry::BackgroundWorker.new(config)
225
- @session_flusher = config.auto_session_tracking ? Sentry::SessionFlusher.new(config, client) : nil
239
+ @session_flusher = config.session_tracking? ? Sentry::SessionFlusher.new(config, client) : nil
226
240
  @backpressure_monitor = config.enable_backpressure_handling ? Sentry::BackpressureMonitor.new(config, client) : nil
241
+ @metrics_aggregator = config.metrics.enabled ? Sentry::Metrics::Aggregator.new(config, client) : nil
227
242
  exception_locals_tp.enable if config.include_local_variables
228
243
  at_exit { close }
229
244
  end
@@ -244,8 +259,14 @@ module Sentry
244
259
  @backpressure_monitor = nil
245
260
  end
246
261
 
262
+ if @metrics_aggregator
263
+ @metrics_aggregator.flush(force: true)
264
+ @metrics_aggregator.kill
265
+ @metrics_aggregator = nil
266
+ end
267
+
247
268
  if client = get_current_client
248
- client.transport.flush
269
+ client.flush
249
270
 
250
271
  if client.configuration.include_local_variables
251
272
  exception_locals_tp.disable
@@ -538,6 +559,15 @@ module Sentry
538
559
  get_current_hub.get_trace_propagation_headers
539
560
  end
540
561
 
562
+ # Returns the a Hash containing sentry-trace and baggage.
563
+ # Can be either from the currently active span or the propagation context.
564
+ #
565
+ # @return [String]
566
+ def get_trace_propagation_meta
567
+ return "" unless initialized?
568
+ get_current_hub.get_trace_propagation_meta
569
+ end
570
+
541
571
  # Continue an incoming trace from a rack env like hash.
542
572
  #
543
573
  # @param env [Hash]
@@ -578,3 +608,5 @@ end
578
608
  require "sentry/net/http"
579
609
  require "sentry/redis"
580
610
  require "sentry/puma"
611
+ require "sentry/graphql"
612
+ require "sentry/faraday"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "lib/sentry/version"
2
4
 
3
5
  Gem::Specification.new do |spec|
@@ -12,7 +14,7 @@ Gem::Specification.new do |spec|
12
14
  spec.platform = Gem::Platform::RUBY
13
15
  spec.required_ruby_version = '>= 2.4'
14
16
  spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
15
- spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples)'`.split("\n")
17
+ spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples|\.rubocop\.yml)'`.split("\n")
16
18
 
17
19
  spec.metadata["homepage_uri"] = spec.homepage
18
20
  spec.metadata["source_code_uri"] = spec.homepage
data/sentry-ruby.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "lib/sentry/version"
2
4
 
3
5
  Gem::Specification.new do |spec|
@@ -7,18 +9,25 @@ Gem::Specification.new do |spec|
7
9
  spec.description = spec.summary = "A gem that provides a client interface for the Sentry error logger"
8
10
  spec.email = "accounts@sentry.io"
9
11
  spec.license = 'MIT'
10
- spec.homepage = "https://github.com/getsentry/sentry-ruby"
11
12
 
12
13
  spec.platform = Gem::Platform::RUBY
13
14
  spec.required_ruby_version = '>= 2.4'
14
15
  spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
15
- spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples)'`.split("\n")
16
+ spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples|\.rubocop\.yml)'`.split("\n")
17
+
18
+ github_root_uri = 'https://github.com/getsentry/sentry-ruby'
19
+ spec.homepage = "#{github_root_uri}/tree/#{spec.version}/#{spec.name}"
16
20
 
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"
21
+ spec.metadata = {
22
+ "homepage_uri" => spec.homepage,
23
+ "source_code_uri" => spec.homepage,
24
+ "changelog_uri" => "#{github_root_uri}/blob/#{spec.version}/CHANGELOG.md",
25
+ "bug_tracker_uri" => "#{github_root_uri}/issues",
26
+ "documentation_uri" => "http://www.rubydoc.info/gems/#{spec.name}/#{spec.version}"
27
+ }
20
28
 
21
29
  spec.require_paths = ["lib"]
22
30
 
23
- spec.add_dependency "concurrent-ruby", '~> 1.0', '>= 1.0.2'
31
+ spec.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
32
+ spec.add_dependency "bigdecimal"
24
33
  end