sentry-ruby 5.10.0 → 5.26.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/Gemfile +12 -13
  4. data/README.md +26 -11
  5. data/Rakefile +9 -11
  6. data/bin/console +2 -0
  7. data/lib/sentry/attachment.rb +40 -0
  8. data/lib/sentry/background_worker.rb +11 -5
  9. data/lib/sentry/backpressure_monitor.rb +45 -0
  10. data/lib/sentry/backtrace.rb +12 -9
  11. data/lib/sentry/baggage.rb +7 -7
  12. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  13. data/lib/sentry/breadcrumb.rb +13 -6
  14. data/lib/sentry/check_in_event.rb +61 -0
  15. data/lib/sentry/client.rb +214 -25
  16. data/lib/sentry/configuration.rb +221 -38
  17. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  18. data/lib/sentry/cron/configuration.rb +23 -0
  19. data/lib/sentry/cron/monitor_check_ins.rb +77 -0
  20. data/lib/sentry/cron/monitor_config.rb +53 -0
  21. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  22. data/lib/sentry/dsn.rb +4 -4
  23. data/lib/sentry/envelope/item.rb +88 -0
  24. data/lib/sentry/envelope.rb +2 -68
  25. data/lib/sentry/error_event.rb +2 -2
  26. data/lib/sentry/event.rb +28 -47
  27. data/lib/sentry/excon/middleware.rb +77 -0
  28. data/lib/sentry/excon.rb +10 -0
  29. data/lib/sentry/faraday.rb +77 -0
  30. data/lib/sentry/graphql.rb +9 -0
  31. data/lib/sentry/hub.rb +138 -6
  32. data/lib/sentry/integrable.rb +10 -0
  33. data/lib/sentry/interface.rb +1 -0
  34. data/lib/sentry/interfaces/exception.rb +5 -3
  35. data/lib/sentry/interfaces/mechanism.rb +20 -0
  36. data/lib/sentry/interfaces/request.rb +8 -8
  37. data/lib/sentry/interfaces/single_exception.rb +13 -9
  38. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  39. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  40. data/lib/sentry/linecache.rb +3 -3
  41. data/lib/sentry/log_event.rb +206 -0
  42. data/lib/sentry/log_event_buffer.rb +75 -0
  43. data/lib/sentry/logger.rb +1 -1
  44. data/lib/sentry/metrics/aggregator.rb +248 -0
  45. data/lib/sentry/metrics/configuration.rb +47 -0
  46. data/lib/sentry/metrics/counter_metric.rb +25 -0
  47. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  48. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  49. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  50. data/lib/sentry/metrics/metric.rb +19 -0
  51. data/lib/sentry/metrics/set_metric.rb +28 -0
  52. data/lib/sentry/metrics/timing.rb +51 -0
  53. data/lib/sentry/metrics.rb +56 -0
  54. data/lib/sentry/net/http.rb +27 -44
  55. data/lib/sentry/profiler/helpers.rb +46 -0
  56. data/lib/sentry/profiler.rb +41 -60
  57. data/lib/sentry/propagation_context.rb +135 -0
  58. data/lib/sentry/puma.rb +12 -5
  59. data/lib/sentry/rack/capture_exceptions.rb +17 -8
  60. data/lib/sentry/rack.rb +2 -2
  61. data/lib/sentry/rake.rb +4 -15
  62. data/lib/sentry/redis.rb +10 -4
  63. data/lib/sentry/release_detector.rb +5 -5
  64. data/lib/sentry/rspec.rb +91 -0
  65. data/lib/sentry/scope.rb +75 -39
  66. data/lib/sentry/session.rb +2 -2
  67. data/lib/sentry/session_flusher.rb +15 -43
  68. data/lib/sentry/span.rb +92 -8
  69. data/lib/sentry/std_lib_logger.rb +50 -0
  70. data/lib/sentry/structured_logger.rb +138 -0
  71. data/lib/sentry/test_helper.rb +42 -13
  72. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  73. data/lib/sentry/transaction.rb +44 -43
  74. data/lib/sentry/transaction_event.rb +10 -6
  75. data/lib/sentry/transport/configuration.rb +73 -1
  76. data/lib/sentry/transport/http_transport.rb +71 -41
  77. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  78. data/lib/sentry/transport.rb +53 -49
  79. data/lib/sentry/utils/argument_checking_helper.rb +12 -0
  80. data/lib/sentry/utils/env_helper.rb +21 -0
  81. data/lib/sentry/utils/http_tracing.rb +74 -0
  82. data/lib/sentry/utils/logging_helper.rb +10 -7
  83. data/lib/sentry/utils/real_ip.rb +2 -2
  84. data/lib/sentry/utils/request_id.rb +1 -1
  85. data/lib/sentry/utils/uuid.rb +13 -0
  86. data/lib/sentry/vernier/output.rb +89 -0
  87. data/lib/sentry/vernier/profiler.rb +132 -0
  88. data/lib/sentry/version.rb +1 -1
  89. data/lib/sentry-ruby.rb +206 -35
  90. data/sentry-ruby-core.gemspec +3 -1
  91. data/sentry-ruby.gemspec +15 -6
  92. metadata +61 -11
@@ -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.sdk_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
13
+ @mutex = Mutex.new
17
14
 
18
15
  log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
19
16
  end
20
17
 
21
18
  def flush
22
19
  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
20
 
29
- @pending_aggregates = {}
21
+ @client.capture_envelope(pending_envelope)
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)
@@ -62,11 +42,15 @@ module Sentry
62
42
  end
63
43
 
64
44
  def pending_envelope
65
- envelope = Envelope.new
66
-
67
- header = { type: 'sessions' }
68
- payload = { attrs: attrs, aggregates: @pending_aggregates.values }
45
+ aggregates = @mutex.synchronize do
46
+ aggregates = @pending_aggregates.values
47
+ @pending_aggregates = {}
48
+ aggregates
49
+ end
69
50
 
51
+ envelope = Envelope.new
52
+ header = { type: "sessions" }
53
+ payload = { attrs: attrs, aggregates: aggregates }
70
54
  envelope.add_item(header, payload)
71
55
  envelope
72
56
  end
@@ -74,17 +58,5 @@ module Sentry
74
58
  def attrs
75
59
  { release: @release, environment: @environment }
76
60
  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
-
89
61
  end
90
62
  end
data/lib/sentry/span.rb CHANGED
@@ -1,9 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require "sentry/metrics/local_aggregator"
5
+ require "sentry/utils/uuid"
4
6
 
5
7
  module Sentry
6
8
  class Span
9
+ # We will try to be consistent with OpenTelemetry on this front going forward.
10
+ # https://develop.sentry.dev/sdk/performance/span-data-conventions/
11
+ module DataConventions
12
+ URL = "url"
13
+ HTTP_STATUS_CODE = "http.response.status_code"
14
+ HTTP_QUERY = "http.query"
15
+ HTTP_METHOD = "http.request.method"
16
+
17
+ # An identifier for the database management system (DBMS) product being used.
18
+ # Example: postgresql
19
+ DB_SYSTEM = "db.system"
20
+
21
+ # The name of the database being accessed.
22
+ # For commands that switch the database, this should be set to the target database
23
+ # (even if the command fails).
24
+ # Example: myDatabase
25
+ DB_NAME = "db.name"
26
+
27
+ # Name of the database host.
28
+ # Example: example.com
29
+ SERVER_ADDRESS = "server.address"
30
+
31
+ # Logical server port number
32
+ # Example: 80; 8080; 443
33
+ SERVER_PORT = "server.port"
34
+
35
+ # Physical server IP address or Unix socket address.
36
+ # Example: 10.5.3.2
37
+ SERVER_SOCKET_ADDRESS = "server.socket.address"
38
+
39
+ # Physical server port.
40
+ # Recommended: If different than server.port.
41
+ # Example: 16456
42
+ SERVER_SOCKET_PORT = "server.socket.port"
43
+
44
+ FILEPATH = "code.filepath"
45
+ LINENO = "code.lineno"
46
+ FUNCTION = "code.function"
47
+ NAMESPACE = "code.namespace"
48
+
49
+ MESSAGING_MESSAGE_ID = "messaging.message.id"
50
+ MESSAGING_DESTINATION_NAME = "messaging.destination.name"
51
+ MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency"
52
+ MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count"
53
+ end
54
+
7
55
  STATUS_MAP = {
8
56
  400 => "invalid_argument",
9
57
  401 => "unauthenticated",
@@ -18,6 +66,8 @@ module Sentry
18
66
  504 => "deadline_exceeded"
19
67
  }
20
68
 
69
+ DEFAULT_SPAN_ORIGIN = "manual"
70
+
21
71
  # An uuid that can be used to identify a trace.
22
72
  # @return [String]
23
73
  attr_reader :trace_id
@@ -51,6 +101,9 @@ module Sentry
51
101
  # Span data
52
102
  # @return [Hash]
53
103
  attr_reader :data
104
+ # Span origin that tracks what kind of instrumentation created a span
105
+ # @return [String]
106
+ attr_reader :origin
54
107
 
55
108
  # The SpanRecorder the current span belongs to.
56
109
  # SpanRecorder holds all spans under the same Transaction object (including the Transaction itself).
@@ -72,10 +125,11 @@ module Sentry
72
125
  parent_span_id: nil,
73
126
  sampled: nil,
74
127
  start_timestamp: nil,
75
- timestamp: nil
128
+ timestamp: nil,
129
+ origin: nil
76
130
  )
77
- @trace_id = trace_id || SecureRandom.uuid.delete("-")
78
- @span_id = span_id || SecureRandom.hex(8)
131
+ @trace_id = trace_id || Utils.uuid
132
+ @span_id = span_id || Utils.uuid.slice(0, 16)
79
133
  @parent_span_id = parent_span_id
80
134
  @sampled = sampled
81
135
  @start_timestamp = start_timestamp || Sentry.utc_now.to_f
@@ -86,6 +140,7 @@ module Sentry
86
140
  @status = status
87
141
  @data = {}
88
142
  @tags = {}
143
+ @origin = origin || DEFAULT_SPAN_ORIGIN
89
144
  end
90
145
 
91
146
  # Finishes the span by adding a timestamp.
@@ -111,9 +166,15 @@ module Sentry
111
166
  transaction.get_baggage&.serialize
112
167
  end
113
168
 
169
+ # Returns the Dynamic Sampling Context from the transaction baggage.
170
+ # @return [Hash, nil]
171
+ def get_dynamic_sampling_context
172
+ transaction.get_baggage&.dynamic_sampling_context
173
+ end
174
+
114
175
  # @return [Hash]
115
176
  def to_hash
116
- {
177
+ hash = {
117
178
  trace_id: @trace_id,
118
179
  span_id: @span_id,
119
180
  parent_span_id: @parent_span_id,
@@ -123,8 +184,14 @@ module Sentry
123
184
  op: @op,
124
185
  status: @status,
125
186
  tags: @tags,
126
- data: @data
187
+ data: @data,
188
+ origin: @origin
127
189
  }
190
+
191
+ summary = metrics_summary
192
+ hash[:_metrics_summary] = summary if summary
193
+
194
+ hash
128
195
  end
129
196
 
130
197
  # Returns the span's context that can be used to embed in an Event.
@@ -136,7 +203,9 @@ module Sentry
136
203
  parent_span_id: @parent_span_id,
137
204
  description: @description,
138
205
  op: @op,
139
- status: @status
206
+ status: @status,
207
+ origin: @origin,
208
+ data: @data
140
209
  }
141
210
  end
142
211
 
@@ -193,7 +262,7 @@ module Sentry
193
262
 
194
263
 
195
264
  # Sets the span's status.
196
- # @param satus [String] status of the span.
265
+ # @param status [String] status of the span.
197
266
  def set_status(status)
198
267
  @status = status
199
268
  end
@@ -208,7 +277,7 @@ module Sentry
208
277
  # @param status_code [String] example: "500".
209
278
  def set_http_status(status_code)
210
279
  status_code = status_code.to_i
211
- set_data("status_code", status_code)
280
+ set_data(DataConventions::HTTP_STATUS_CODE, status_code)
212
281
 
213
282
  status =
214
283
  if status_code >= 200 && status_code < 299
@@ -232,5 +301,20 @@ module Sentry
232
301
  def set_tag(key, value)
233
302
  @tags[key] = value
234
303
  end
304
+
305
+ # Sets the origin of the span.
306
+ # @param origin [String]
307
+ def set_origin(origin)
308
+ @origin = origin
309
+ end
310
+
311
+ # Collects gauge metrics on the span for metric summaries.
312
+ def metrics_local_aggregator
313
+ @metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
314
+ end
315
+
316
+ def metrics_summary
317
+ @metrics_local_aggregator&.to_hash
318
+ end
235
319
  end
236
320
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # Ruby Logger support Add commentMore actions
5
+ # intercepts any logger instance and send the log to Sentry too.
6
+ module StdLibLogger
7
+ SEVERITY_MAP = {
8
+ 0 => :debug,
9
+ 1 => :info,
10
+ 2 => :warn,
11
+ 3 => :error,
12
+ 4 => :fatal
13
+ }.freeze
14
+
15
+ def add(severity, message = nil, progname = nil, &block)
16
+ result = super
17
+
18
+ return unless Sentry.initialized? && Sentry.get_current_hub
19
+
20
+ # exclude sentry SDK logs -- to prevent recursive log action,
21
+ # do not process internal logs again
22
+ if message.nil? && progname != Sentry::Logger::PROGNAME
23
+
24
+ # handle different nature of Ruby Logger class:
25
+ # inspo from Sentry::Breadcrumb::SentryLogger
26
+ if block_given?
27
+ message = yield
28
+ else
29
+ message = progname
30
+ end
31
+
32
+ message = message.to_s.strip
33
+
34
+ if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
35
+ Sentry.logger.send(method, message)
36
+ end
37
+ end
38
+
39
+ result
40
+ end
41
+ end
42
+ end
43
+
44
+ Sentry.register_patch(:logger) do |config|
45
+ if config.enable_logs
46
+ ::Logger.prepend(Sentry::StdLibLogger)
47
+ else
48
+ config.sdk_logger.warn(":logger patch enabled but `enable_logs` is turned off - skipping applying patch")
49
+ end
50
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # The StructuredLogger class implements Sentry's SDK telemetry logs protocol.
5
+ # It provides methods for logging messages at different severity levels and
6
+ # sending them to Sentry with structured data.
7
+ #
8
+ # This class follows the Sentry Logs Protocol as defined in:
9
+ # https://develop.sentry.dev/sdk/telemetry/logs/
10
+ #
11
+ # @example Basic usage
12
+ # Sentry.logger.info("User logged in", user_id: 123)
13
+ #
14
+ # @example With structured data
15
+ # Sentry.logger.warn("API request failed",
16
+ # status_code: 404,
17
+ # endpoint: "/api/users",
18
+ # request_id: "abc-123"
19
+ # )
20
+ #
21
+ # @example With a message template
22
+ # # Using positional parameters
23
+ # Sentry.logger.info("User %s logged in", ["Jane Doe"])
24
+ #
25
+ # # Using hash parameters
26
+ # Sentry.logger.info("User %{name} logged in", name: "Jane Doe")
27
+ #
28
+ # # Using hash parameters and extra attributes
29
+ # Sentry.logger.info("User %{name} logged in", name: "Jane Doe", user_id: 312)
30
+ #
31
+ # @see https://develop.sentry.dev/sdk/telemetry/logs/ Sentry SDK Telemetry Logs Protocol
32
+ class StructuredLogger
33
+ # Severity number mapping for log levels according to the Sentry Logs Protocol
34
+ # @see https://develop.sentry.dev/sdk/telemetry/logs/#log-severity-number
35
+ LEVELS = {
36
+ trace: 1,
37
+ debug: 5,
38
+ info: 9,
39
+ warn: 13,
40
+ error: 17,
41
+ fatal: 21
42
+ }.freeze
43
+
44
+ # @return [Configuration] The Sentry configuration
45
+ # @!visibility private
46
+ attr_reader :config
47
+
48
+ # Initializes a new StructuredLogger instance
49
+ #
50
+ # @param config [Configuration] The Sentry configuration
51
+ def initialize(config)
52
+ @config = config
53
+ end
54
+
55
+ # Logs a message at TRACE level
56
+ #
57
+ # @param message [String] The log message
58
+ # @param parameters [Array] Array of values to replace template parameters in the message
59
+ # @param attributes [Hash] Additional attributes to include with the log
60
+ #
61
+ # @return [LogEvent, nil] The created log event or nil if logging is disabled
62
+ def trace(message, parameters = [], **attributes)
63
+ log(__method__, message, parameters: parameters, **attributes)
64
+ end
65
+
66
+ # Logs a message at DEBUG level
67
+ #
68
+ # @param message [String] The log message
69
+ # @param parameters [Array] Array of values to replace template parameters in the message
70
+ # @param attributes [Hash] Additional attributes to include with the log
71
+ #
72
+ # @return [LogEvent, nil] The created log event or nil if logging is disabled
73
+ def debug(message, parameters = [], **attributes)
74
+ log(__method__, message, parameters: parameters, **attributes)
75
+ end
76
+
77
+ # Logs a message at INFO level
78
+ #
79
+ # @param message [String] The log message
80
+ # @param parameters [Array] Array of values to replace template parameters in the message
81
+ # @param attributes [Hash] Additional attributes to include with the log
82
+ #
83
+ # @return [LogEvent, nil] The created log event or nil if logging is disabled
84
+ def info(message, parameters = [], **attributes)
85
+ log(__method__, message, parameters: parameters, **attributes)
86
+ end
87
+
88
+ # Logs a message at WARN level
89
+ #
90
+ # @param message [String] The log message
91
+ # @param parameters [Array] Array of values to replace template parameters in the message
92
+ # @param attributes [Hash] Additional attributes to include with the log
93
+ #
94
+ # @return [LogEvent, nil] The created log event or nil if logging is disabled
95
+ def warn(message, parameters = [], **attributes)
96
+ log(__method__, message, parameters: parameters, **attributes)
97
+ end
98
+
99
+ # Logs a message at ERROR level
100
+ #
101
+ # @param message [String] The log message
102
+ # @param parameters [Array] Array of values to replace template parameters in the message
103
+ # @param attributes [Hash] Additional attributes to include with the log
104
+ #
105
+ # @return [LogEvent, nil] The created log event or nil if logging is disabled
106
+ def error(message, parameters = [], **attributes)
107
+ log(__method__, message, parameters: parameters, **attributes)
108
+ end
109
+
110
+ # Logs a message at FATAL level
111
+ #
112
+ # @param message [String] The log message
113
+ # @param parameters [Array] Array of values to replace template parameters in the message
114
+ # @param attributes [Hash] Additional attributes to include with the log
115
+ #
116
+ # @return [LogEvent, nil] The created log event or nil if logging is disabled
117
+ def fatal(message, parameters = [], **attributes)
118
+ log(__method__, message, parameters: parameters, **attributes)
119
+ end
120
+
121
+ # Logs a message at the specified level
122
+ #
123
+ # @param level [Symbol] The log level (:trace, :debug, :info, :warn, :error, :fatal)
124
+ # @param message [String] The log message
125
+ # @param parameters [Array, Hash] Array or Hash of values to replace template parameters in the message
126
+ # @param attributes [Hash] Additional attributes to include with the log
127
+ #
128
+ # @return [LogEvent, nil] The created log event or nil if logging is disabled
129
+ def log(level, message, parameters:, **attributes)
130
+ case parameters
131
+ when Array then
132
+ Sentry.capture_log(message, level: level, severity: LEVELS[level], parameters: parameters, **attributes)
133
+ else
134
+ Sentry.capture_log(message, level: level, severity: LEVELS[level], **parameters)
135
+ end
136
+ end
137
+ end
138
+ end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module TestHelper
3
- DUMMY_DSN = 'http://12345:67890@sentry.localdomain/sentry/42'
5
+ DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
4
6
 
5
7
  # Alters the existing SDK configuration with test-suitable options. Mainly:
6
8
  # - Sets a dummy DSN instead of `nil` or an actual DSN.
@@ -14,24 +16,28 @@ module Sentry
14
16
  # @return [void]
15
17
  def setup_sentry_test(&block)
16
18
  raise "please make sure the SDK is initialized for testing" unless Sentry.initialized?
17
- copied_config = Sentry.configuration.dup
19
+ dummy_config = Sentry.configuration.dup
18
20
  # configure dummy DSN, so the events will not be sent to the actual service
19
- copied_config.dsn = DUMMY_DSN
21
+ dummy_config.dsn = DUMMY_DSN
20
22
  # set transport to DummyTransport, so we can easily intercept the captured events
21
- copied_config.transport.transport_class = Sentry::DummyTransport
23
+ dummy_config.transport.transport_class = Sentry::DummyTransport
22
24
  # make sure SDK allows sending under the current environment
23
- copied_config.enabled_environments << copied_config.environment unless copied_config.enabled_environments.include?(copied_config.environment)
25
+ dummy_config.enabled_environments += [dummy_config.environment] unless dummy_config.enabled_environments.include?(dummy_config.environment)
24
26
  # disble async event sending
25
- copied_config.background_worker_threads = 0
27
+ dummy_config.background_worker_threads = 0
26
28
 
27
29
  # user can overwrite some of the configs, with a few exceptions like:
28
30
  # - include_local_variables
29
31
  # - auto_session_tracking
30
- block&.call(copied_config)
32
+ block&.call(dummy_config)
31
33
 
32
- test_client = Sentry::Client.new(copied_config)
34
+ # the base layer's client should already use the dummy config so nothing will be sent by accident
35
+ base_client = Sentry::Client.new(dummy_config)
36
+ Sentry.get_current_hub.bind_client(base_client)
37
+ # create a new layer so mutations made to the testing scope or configuration could be simply popped later
38
+ Sentry.get_current_hub.push_scope
39
+ test_client = Sentry::Client.new(dummy_config.dup)
33
40
  Sentry.get_current_hub.bind_client(test_client)
34
- Sentry.get_current_scope.clear
35
41
  end
36
42
 
37
43
  # Clears all stored events and envelopes.
@@ -40,9 +46,13 @@ module Sentry
40
46
  def teardown_sentry_test
41
47
  return unless Sentry.initialized?
42
48
 
43
- sentry_transport.events = []
44
- sentry_transport.envelopes = []
45
- Sentry.get_current_scope.clear
49
+ # pop testing layer created by `setup_sentry_test`
50
+ # but keep the base layer to avoid nil-pointer errors
51
+ # TODO: find a way to notify users if they somehow popped the test layer before calling this method
52
+ if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
53
+ Sentry.get_current_hub.pop_scope
54
+ end
55
+ Sentry::Scope.global_event_processors.clear
46
56
  end
47
57
 
48
58
  # @return [Transport]
@@ -62,6 +72,13 @@ module Sentry
62
72
  sentry_transport.envelopes
63
73
  end
64
74
 
75
+ def sentry_logs
76
+ sentry_envelopes
77
+ .flat_map(&:items)
78
+ .select { |item| item.headers[:type] == "log" }
79
+ .flat_map { |item| item.payload[:items] }
80
+ end
81
+
65
82
  # Returns the last captured event object.
66
83
  # @return [Event, nil]
67
84
  def last_sentry_event
@@ -73,6 +90,18 @@ module Sentry
73
90
  def extract_sentry_exceptions(event)
74
91
  event&.exception&.values || []
75
92
  end
93
+
94
+ def reset_sentry_globals!
95
+ Sentry::MUTEX.synchronize do
96
+ # Don't check initialized? because sometimes we stub it in tests
97
+ if Sentry.instance_variable_defined?(:@main_hub)
98
+ Sentry::GLOBALS.each do |var|
99
+ Sentry.instance_variable_set(:"@#{var}", nil)
100
+ end
101
+
102
+ Thread.current.thread_variable_set(Sentry::THREAD_LOCAL, nil)
103
+ end
104
+ end
105
+ end
76
106
  end
77
107
  end
78
-
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class ThreadedPeriodicWorker
5
+ include LoggingHelper
6
+
7
+ def initialize(sdk_logger, interval)
8
+ @thread = nil
9
+ @exited = false
10
+ @interval = interval
11
+ @sdk_logger = sdk_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