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.
- checksums.yaml +4 -4
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +313 -0
- data/Gemfile +26 -0
- data/Makefile +4 -0
- data/README.md +11 -8
- data/Rakefile +20 -0
- data/bin/console +18 -0
- data/bin/setup +8 -0
- data/lib/sentry/background_worker.rb +79 -0
- data/lib/sentry/backpressure_monitor.rb +75 -0
- data/lib/sentry/backtrace.rb +124 -0
- data/lib/sentry/baggage.rb +70 -0
- data/lib/sentry/breadcrumb/sentry_logger.rb +90 -0
- data/lib/sentry/breadcrumb.rb +76 -0
- data/lib/sentry/breadcrumb_buffer.rb +64 -0
- data/lib/sentry/check_in_event.rb +60 -0
- data/lib/sentry/client.rb +248 -0
- data/lib/sentry/configuration.rb +650 -0
- data/lib/sentry/core_ext/object/deep_dup.rb +61 -0
- data/lib/sentry/core_ext/object/duplicable.rb +155 -0
- data/lib/sentry/cron/configuration.rb +23 -0
- data/lib/sentry/cron/monitor_check_ins.rb +75 -0
- data/lib/sentry/cron/monitor_config.rb +53 -0
- data/lib/sentry/cron/monitor_schedule.rb +42 -0
- data/lib/sentry/dsn.rb +53 -0
- data/lib/sentry/envelope.rb +93 -0
- data/lib/sentry/error_event.rb +38 -0
- data/lib/sentry/event.rb +156 -0
- data/lib/sentry/exceptions.rb +9 -0
- data/lib/sentry/hub.rb +316 -0
- data/lib/sentry/integrable.rb +32 -0
- data/lib/sentry/interface.rb +16 -0
- data/lib/sentry/interfaces/exception.rb +43 -0
- data/lib/sentry/interfaces/request.rb +134 -0
- data/lib/sentry/interfaces/single_exception.rb +67 -0
- data/lib/sentry/interfaces/stacktrace.rb +87 -0
- data/lib/sentry/interfaces/stacktrace_builder.rb +79 -0
- data/lib/sentry/interfaces/threads.rb +42 -0
- data/lib/sentry/linecache.rb +47 -0
- data/lib/sentry/logger.rb +20 -0
- data/lib/sentry/net/http.rb +106 -0
- data/lib/sentry/profiler.rb +233 -0
- data/lib/sentry/propagation_context.rb +134 -0
- data/lib/sentry/puma.rb +32 -0
- data/lib/sentry/rack/capture_exceptions.rb +79 -0
- data/lib/sentry/rack.rb +5 -0
- data/lib/sentry/rake.rb +28 -0
- data/lib/sentry/redis.rb +108 -0
- data/lib/sentry/release_detector.rb +39 -0
- data/lib/sentry/scope.rb +360 -0
- data/lib/sentry/session.rb +33 -0
- data/lib/sentry/session_flusher.rb +90 -0
- data/lib/sentry/span.rb +273 -0
- data/lib/sentry/test_helper.rb +84 -0
- data/lib/sentry/transaction.rb +359 -0
- data/lib/sentry/transaction_event.rb +80 -0
- data/lib/sentry/transport/configuration.rb +98 -0
- data/lib/sentry/transport/dummy_transport.rb +21 -0
- data/lib/sentry/transport/http_transport.rb +206 -0
- data/lib/sentry/transport/spotlight_transport.rb +50 -0
- data/lib/sentry/transport.rb +225 -0
- data/lib/sentry/utils/argument_checking_helper.rb +19 -0
- data/lib/sentry/utils/custom_inspection.rb +14 -0
- data/lib/sentry/utils/encoding_helper.rb +22 -0
- data/lib/sentry/utils/exception_cause_chain.rb +20 -0
- data/lib/sentry/utils/logging_helper.rb +26 -0
- data/lib/sentry/utils/real_ip.rb +84 -0
- data/lib/sentry/utils/request_id.rb +18 -0
- data/lib/sentry/version.rb +5 -0
- data/lib/sentry-ruby.rb +580 -0
- data/sentry-ruby-core.gemspec +23 -0
- data/sentry-ruby.gemspec +24 -0
- 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
|
data/lib/sentry/span.rb
ADDED
@@ -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
|