sentry-ruby 5.3.1 → 5.16.1

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +2 -0
  5. data/CHANGELOG.md +313 -0
  6. data/Gemfile +26 -0
  7. data/Makefile +4 -0
  8. data/README.md +11 -8
  9. data/Rakefile +20 -0
  10. data/bin/console +18 -0
  11. data/bin/setup +8 -0
  12. data/lib/sentry/background_worker.rb +79 -0
  13. data/lib/sentry/backpressure_monitor.rb +75 -0
  14. data/lib/sentry/backtrace.rb +124 -0
  15. data/lib/sentry/baggage.rb +70 -0
  16. data/lib/sentry/breadcrumb/sentry_logger.rb +90 -0
  17. data/lib/sentry/breadcrumb.rb +76 -0
  18. data/lib/sentry/breadcrumb_buffer.rb +64 -0
  19. data/lib/sentry/check_in_event.rb +60 -0
  20. data/lib/sentry/client.rb +248 -0
  21. data/lib/sentry/configuration.rb +650 -0
  22. data/lib/sentry/core_ext/object/deep_dup.rb +61 -0
  23. data/lib/sentry/core_ext/object/duplicable.rb +155 -0
  24. data/lib/sentry/cron/configuration.rb +23 -0
  25. data/lib/sentry/cron/monitor_check_ins.rb +75 -0
  26. data/lib/sentry/cron/monitor_config.rb +53 -0
  27. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  28. data/lib/sentry/dsn.rb +53 -0
  29. data/lib/sentry/envelope.rb +93 -0
  30. data/lib/sentry/error_event.rb +38 -0
  31. data/lib/sentry/event.rb +156 -0
  32. data/lib/sentry/exceptions.rb +9 -0
  33. data/lib/sentry/hub.rb +316 -0
  34. data/lib/sentry/integrable.rb +32 -0
  35. data/lib/sentry/interface.rb +16 -0
  36. data/lib/sentry/interfaces/exception.rb +43 -0
  37. data/lib/sentry/interfaces/request.rb +134 -0
  38. data/lib/sentry/interfaces/single_exception.rb +67 -0
  39. data/lib/sentry/interfaces/stacktrace.rb +87 -0
  40. data/lib/sentry/interfaces/stacktrace_builder.rb +79 -0
  41. data/lib/sentry/interfaces/threads.rb +42 -0
  42. data/lib/sentry/linecache.rb +47 -0
  43. data/lib/sentry/logger.rb +20 -0
  44. data/lib/sentry/net/http.rb +106 -0
  45. data/lib/sentry/profiler.rb +233 -0
  46. data/lib/sentry/propagation_context.rb +134 -0
  47. data/lib/sentry/puma.rb +32 -0
  48. data/lib/sentry/rack/capture_exceptions.rb +79 -0
  49. data/lib/sentry/rack.rb +5 -0
  50. data/lib/sentry/rake.rb +28 -0
  51. data/lib/sentry/redis.rb +108 -0
  52. data/lib/sentry/release_detector.rb +39 -0
  53. data/lib/sentry/scope.rb +360 -0
  54. data/lib/sentry/session.rb +33 -0
  55. data/lib/sentry/session_flusher.rb +90 -0
  56. data/lib/sentry/span.rb +273 -0
  57. data/lib/sentry/test_helper.rb +84 -0
  58. data/lib/sentry/transaction.rb +359 -0
  59. data/lib/sentry/transaction_event.rb +80 -0
  60. data/lib/sentry/transport/configuration.rb +98 -0
  61. data/lib/sentry/transport/dummy_transport.rb +21 -0
  62. data/lib/sentry/transport/http_transport.rb +206 -0
  63. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  64. data/lib/sentry/transport.rb +225 -0
  65. data/lib/sentry/utils/argument_checking_helper.rb +19 -0
  66. data/lib/sentry/utils/custom_inspection.rb +14 -0
  67. data/lib/sentry/utils/encoding_helper.rb +22 -0
  68. data/lib/sentry/utils/exception_cause_chain.rb +20 -0
  69. data/lib/sentry/utils/logging_helper.rb +26 -0
  70. data/lib/sentry/utils/real_ip.rb +84 -0
  71. data/lib/sentry/utils/request_id.rb +18 -0
  72. data/lib/sentry/version.rb +5 -0
  73. data/lib/sentry-ruby.rb +580 -0
  74. data/sentry-ruby-core.gemspec +23 -0
  75. data/sentry-ruby.gemspec +24 -0
  76. metadata +75 -16
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class SessionFlusher
5
+ include LoggingHelper
6
+
7
+ FLUSH_INTERVAL = 60
8
+
9
+ def initialize(configuration, client)
10
+ @thread = nil
11
+ @exited = false
12
+ @client = client
13
+ @pending_aggregates = {}
14
+ @release = configuration.release
15
+ @environment = configuration.environment
16
+ @logger = configuration.logger
17
+
18
+ log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
19
+ end
20
+
21
+ def flush
22
+ 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
+
29
+ @pending_aggregates = {}
30
+ end
31
+
32
+ def add_session(session)
33
+ return if @exited
34
+ return unless @release
35
+
36
+ begin
37
+ ensure_thread
38
+ rescue ThreadError
39
+ log_debug("Session flusher thread creation failed")
40
+ @exited = true
41
+ return
42
+ end
43
+
44
+ return unless Session::AGGREGATE_STATUSES.include?(session.status)
45
+ @pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
46
+ @pending_aggregates[session.aggregation_key][session.status] += 1
47
+ end
48
+
49
+ def kill
50
+ log_debug("Killing session flusher")
51
+
52
+ @exited = true
53
+ @thread&.kill
54
+ end
55
+
56
+ private
57
+
58
+ def init_aggregates(aggregation_key)
59
+ aggregates = { started: aggregation_key.iso8601 }
60
+ Session::AGGREGATE_STATUSES.each { |k| aggregates[k] = 0 }
61
+ aggregates
62
+ end
63
+
64
+ def pending_envelope
65
+ envelope = Envelope.new
66
+
67
+ header = { type: 'sessions' }
68
+ payload = { attrs: attrs, aggregates: @pending_aggregates.values }
69
+
70
+ envelope.add_item(header, payload)
71
+ envelope
72
+ end
73
+
74
+ def attrs
75
+ { release: @release, environment: @environment }
76
+ 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
+ end
90
+ end
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Sentry
6
+ class Span
7
+
8
+ # We will try to be consistent with OpenTelemetry on this front going forward.
9
+ # https://develop.sentry.dev/sdk/performance/span-data-conventions/
10
+ module DataConventions
11
+ URL = "url"
12
+ HTTP_STATUS_CODE = "http.response.status_code"
13
+ HTTP_QUERY = "http.query"
14
+ HTTP_METHOD = "http.request.method"
15
+
16
+ # An identifier for the database management system (DBMS) product being used.
17
+ # Example: postgresql
18
+ DB_SYSTEM = "db.system"
19
+
20
+ # The name of the database being accessed.
21
+ # For commands that switch the database, this should be set to the target database
22
+ # (even if the command fails).
23
+ # Example: myDatabase
24
+ DB_NAME = "db.name"
25
+
26
+ # Name of the database host.
27
+ # Example: example.com
28
+ SERVER_ADDRESS = "server.address"
29
+
30
+ # Logical server port number
31
+ # Example: 80; 8080; 443
32
+ SERVER_PORT = "server.port"
33
+
34
+ # Physical server IP address or Unix socket address.
35
+ # Example: 10.5.3.2
36
+ SERVER_SOCKET_ADDRESS = "server.socket.address"
37
+
38
+ # Physical server port.
39
+ # Recommended: If different than server.port.
40
+ # Example: 16456
41
+ SERVER_SOCKET_PORT = "server.socket.port"
42
+ end
43
+
44
+ STATUS_MAP = {
45
+ 400 => "invalid_argument",
46
+ 401 => "unauthenticated",
47
+ 403 => "permission_denied",
48
+ 404 => "not_found",
49
+ 409 => "already_exists",
50
+ 429 => "resource_exhausted",
51
+ 499 => "cancelled",
52
+ 500 => "internal_error",
53
+ 501 => "unimplemented",
54
+ 503 => "unavailable",
55
+ 504 => "deadline_exceeded"
56
+ }
57
+
58
+ # An uuid that can be used to identify a trace.
59
+ # @return [String]
60
+ attr_reader :trace_id
61
+ # An uuid that can be used to identify the span.
62
+ # @return [String]
63
+ attr_reader :span_id
64
+ # Span parent's span_id.
65
+ # @return [String]
66
+ attr_reader :parent_span_id
67
+ # Sampling result of the span.
68
+ # @return [Boolean, nil]
69
+ attr_reader :sampled
70
+ # Starting timestamp of the span.
71
+ # @return [Float]
72
+ attr_reader :start_timestamp
73
+ # Finishing timestamp of the span.
74
+ # @return [Float]
75
+ attr_reader :timestamp
76
+ # Span description
77
+ # @return [String]
78
+ attr_reader :description
79
+ # Span operation
80
+ # @return [String]
81
+ attr_reader :op
82
+ # Span status
83
+ # @return [String]
84
+ attr_reader :status
85
+ # Span tags
86
+ # @return [Hash]
87
+ attr_reader :tags
88
+ # Span data
89
+ # @return [Hash]
90
+ attr_reader :data
91
+
92
+ # The SpanRecorder the current span belongs to.
93
+ # SpanRecorder holds all spans under the same Transaction object (including the Transaction itself).
94
+ # @return [SpanRecorder]
95
+ attr_accessor :span_recorder
96
+
97
+ # The Transaction object the Span belongs to.
98
+ # Every span needs to be attached to a Transaction and their child spans will also inherit the same transaction.
99
+ # @return [Transaction]
100
+ attr_reader :transaction
101
+
102
+ def initialize(
103
+ transaction:,
104
+ description: nil,
105
+ op: nil,
106
+ status: nil,
107
+ trace_id: nil,
108
+ span_id: nil,
109
+ parent_span_id: nil,
110
+ sampled: nil,
111
+ start_timestamp: nil,
112
+ timestamp: nil
113
+ )
114
+ @trace_id = trace_id || SecureRandom.uuid.delete("-")
115
+ @span_id = span_id || SecureRandom.uuid.delete("-").slice(0, 16)
116
+ @parent_span_id = parent_span_id
117
+ @sampled = sampled
118
+ @start_timestamp = start_timestamp || Sentry.utc_now.to_f
119
+ @timestamp = timestamp
120
+ @description = description
121
+ @transaction = transaction
122
+ @op = op
123
+ @status = status
124
+ @data = {}
125
+ @tags = {}
126
+ end
127
+
128
+ # Finishes the span by adding a timestamp.
129
+ # @return [self]
130
+ def finish(end_timestamp: nil)
131
+ @timestamp = end_timestamp || @timestamp || Sentry.utc_now.to_f
132
+ self
133
+ end
134
+
135
+ # Generates a trace string that can be used to connect other transactions.
136
+ # @return [String]
137
+ def to_sentry_trace
138
+ sampled_flag = ""
139
+ sampled_flag = @sampled ? 1 : 0 unless @sampled.nil?
140
+
141
+ "#{@trace_id}-#{@span_id}-#{sampled_flag}"
142
+ end
143
+
144
+ # Generates a W3C Baggage header string for distributed tracing
145
+ # from the incoming baggage stored on the transaction.
146
+ # @return [String, nil]
147
+ def to_baggage
148
+ transaction.get_baggage&.serialize
149
+ end
150
+
151
+ # @return [Hash]
152
+ def to_hash
153
+ {
154
+ trace_id: @trace_id,
155
+ span_id: @span_id,
156
+ parent_span_id: @parent_span_id,
157
+ start_timestamp: @start_timestamp,
158
+ timestamp: @timestamp,
159
+ description: @description,
160
+ op: @op,
161
+ status: @status,
162
+ tags: @tags,
163
+ data: @data
164
+ }
165
+ end
166
+
167
+ # Returns the span's context that can be used to embed in an Event.
168
+ # @return [Hash]
169
+ def get_trace_context
170
+ {
171
+ trace_id: @trace_id,
172
+ span_id: @span_id,
173
+ parent_span_id: @parent_span_id,
174
+ description: @description,
175
+ op: @op,
176
+ status: @status
177
+ }
178
+ end
179
+
180
+ # Starts a child span with given attributes.
181
+ # @param attributes [Hash] the attributes for the child span.
182
+ def start_child(**attributes)
183
+ attributes = attributes.dup.merge(transaction: @transaction, trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
184
+ new_span = Span.new(**attributes)
185
+ new_span.span_recorder = span_recorder
186
+
187
+ if span_recorder
188
+ span_recorder.add(new_span)
189
+ end
190
+
191
+ new_span
192
+ end
193
+
194
+ # Starts a child span, yield it to the given block, and then finish the span after the block is executed.
195
+ # @example
196
+ # span.with_child_span do |child_span|
197
+ # # things happen here will be recorded in a child span
198
+ # end
199
+ #
200
+ # @param attributes [Hash] the attributes for the child span.
201
+ # @param block [Proc] the action to be recorded in the child span.
202
+ # @yieldparam child_span [Span]
203
+ def with_child_span(**attributes, &block)
204
+ child_span = start_child(**attributes)
205
+
206
+ yield(child_span)
207
+
208
+ child_span.finish
209
+ rescue
210
+ child_span.set_http_status(500)
211
+ child_span.finish
212
+ raise
213
+ end
214
+
215
+ def deep_dup
216
+ dup
217
+ end
218
+
219
+ # Sets the span's operation.
220
+ # @param op [String] operation of the span.
221
+ def set_op(op)
222
+ @op = op
223
+ end
224
+
225
+ # Sets the span's description.
226
+ # @param description [String] description of the span.
227
+ def set_description(description)
228
+ @description = description
229
+ end
230
+
231
+
232
+ # Sets the span's status.
233
+ # @param satus [String] status of the span.
234
+ def set_status(status)
235
+ @status = status
236
+ end
237
+
238
+ # Sets the span's finish timestamp.
239
+ # @param timestamp [Float] finished time in float format (most precise).
240
+ def set_timestamp(timestamp)
241
+ @timestamp = timestamp
242
+ end
243
+
244
+ # Sets the span's status with given http status code.
245
+ # @param status_code [String] example: "500".
246
+ def set_http_status(status_code)
247
+ status_code = status_code.to_i
248
+ set_data(DataConventions::HTTP_STATUS_CODE, status_code)
249
+
250
+ status =
251
+ if status_code >= 200 && status_code < 299
252
+ "ok"
253
+ else
254
+ STATUS_MAP[status_code]
255
+ end
256
+ set_status(status)
257
+ end
258
+
259
+ # Inserts a key-value pair to the span's data payload.
260
+ # @param key [String, Symbol]
261
+ # @param value [Object]
262
+ def set_data(key, value)
263
+ @data[key] = value
264
+ end
265
+
266
+ # Sets a tag to the span.
267
+ # @param key [String, Symbol]
268
+ # @param value [String]
269
+ def set_tag(key, value)
270
+ @tags[key] = value
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,84 @@
1
+ module Sentry
2
+ module TestHelper
3
+ DUMMY_DSN = 'http://12345:67890@sentry.localdomain/sentry/42'
4
+
5
+ # Alters the existing SDK configuration with test-suitable options. Mainly:
6
+ # - Sets a dummy DSN instead of `nil` or an actual DSN.
7
+ # - Sets the transport to DummyTransport, which allows easy access to the captured events.
8
+ # - Disables background worker.
9
+ # - Makes sure the SDK is enabled under the current environment ("test" in most cases).
10
+ #
11
+ # It should be called **before** every test case.
12
+ #
13
+ # @yieldparam config [Configuration]
14
+ # @return [void]
15
+ def setup_sentry_test(&block)
16
+ raise "please make sure the SDK is initialized for testing" unless Sentry.initialized?
17
+ dummy_config = Sentry.configuration.dup
18
+ # configure dummy DSN, so the events will not be sent to the actual service
19
+ dummy_config.dsn = DUMMY_DSN
20
+ # set transport to DummyTransport, so we can easily intercept the captured events
21
+ dummy_config.transport.transport_class = Sentry::DummyTransport
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)
24
+ # disble async event sending
25
+ dummy_config.background_worker_threads = 0
26
+
27
+ # user can overwrite some of the configs, with a few exceptions like:
28
+ # - include_local_variables
29
+ # - auto_session_tracking
30
+ block&.call(dummy_config)
31
+
32
+ # the base layer's client should already use the dummy config so nothing will be sent by accident
33
+ base_client = Sentry::Client.new(dummy_config)
34
+ Sentry.get_current_hub.bind_client(base_client)
35
+ # create a new layer so mutations made to the testing scope or configuration could be simply popped later
36
+ Sentry.get_current_hub.push_scope
37
+ test_client = Sentry::Client.new(dummy_config.dup)
38
+ Sentry.get_current_hub.bind_client(test_client)
39
+ end
40
+
41
+ # Clears all stored events and envelopes.
42
+ # It should be called **after** every test case.
43
+ # @return [void]
44
+ def teardown_sentry_test
45
+ return unless Sentry.initialized?
46
+
47
+ # pop testing layer created by `setup_sentry_test`
48
+ # but keep the base layer to avoid nil-pointer errors
49
+ # TODO: find a way to notify users if they somehow popped the test layer before calling this method
50
+ if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
51
+ Sentry.get_current_hub.pop_scope
52
+ end
53
+ end
54
+
55
+ # @return [Transport]
56
+ def sentry_transport
57
+ Sentry.get_current_client.transport
58
+ end
59
+
60
+ # Returns the captured event objects.
61
+ # @return [Array<Event>]
62
+ def sentry_events
63
+ sentry_transport.events
64
+ end
65
+
66
+ # Returns the captured envelope objects.
67
+ # @return [Array<Envelope>]
68
+ def sentry_envelopes
69
+ sentry_transport.envelopes
70
+ end
71
+
72
+ # Returns the last captured event object.
73
+ # @return [Event, nil]
74
+ def last_sentry_event
75
+ sentry_events.last
76
+ end
77
+
78
+ # Extracts SDK's internal exception container (not actual exception objects) from an given event.
79
+ # @return [Array<Sentry::SingleExceptionInterface>]
80
+ def extract_sentry_exceptions(event)
81
+ event&.exception&.values || []
82
+ end
83
+ end
84
+ end