sentry-ruby-core 5.1.1 → 5.2.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.
- checksums.yaml +4 -4
- data/lib/sentry/configuration.rb +5 -0
- data/lib/sentry/envelope.rb +0 -4
- data/lib/sentry/event.rb +1 -0
- data/lib/sentry/hub.rb +27 -1
- data/lib/sentry/interfaces/exception.rb +1 -0
- data/lib/sentry/rack/capture_exceptions.rb +26 -24
- data/lib/sentry/redis.rb +6 -4
- data/lib/sentry/scope.rb +10 -1
- data/lib/sentry/session.rb +35 -0
- data/lib/sentry/session_flusher.rb +79 -0
- data/lib/sentry/transport/dummy_transport.rb +6 -1
- data/lib/sentry/transport.rb +39 -3
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +32 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6a629d8aa998cef5638cd40e3dc9e0ad24770d0c31540b327c669e9fb84aedd
|
4
|
+
data.tar.gz: e373b601b401fddca9307a32ea1dda36bc4994209c68b19748e89f128a598e80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cfd2f1fe582578b74bc23610931d95bf7a86c35c696c658d55fb4f40ddfdd129c36bae415bc73efef20ecfb435f33ba72e3b46a96f536c0c61692d8c8cb34e2
|
7
|
+
data.tar.gz: f10f71edc8bef7a57426de73e0cf2a1aea855c89ec2900a065986d33f064216f7da74c0cf9f7dd41c08dcb18224a61c6c43bad67548a327ca593b73b526e3d8a
|
data/lib/sentry/configuration.rb
CHANGED
@@ -207,6 +207,10 @@ module Sentry
|
|
207
207
|
# @return [Boolean]
|
208
208
|
attr_accessor :send_client_reports
|
209
209
|
|
210
|
+
# Track sessions in request/response cycles automatically
|
211
|
+
# @return [Boolean]
|
212
|
+
attr_accessor :auto_session_tracking
|
213
|
+
|
210
214
|
# these are not config options
|
211
215
|
# @!visibility private
|
212
216
|
attr_reader :errors, :gem_specs
|
@@ -261,6 +265,7 @@ module Sentry
|
|
261
265
|
self.send_default_pii = false
|
262
266
|
self.skip_rake_integration = false
|
263
267
|
self.send_client_reports = true
|
268
|
+
self.auto_session_tracking = true
|
264
269
|
self.trusted_proxies = []
|
265
270
|
self.dsn = ENV['SENTRY_DSN']
|
266
271
|
self.server_name = server_name_from_env
|
data/lib/sentry/envelope.rb
CHANGED
data/lib/sentry/event.rb
CHANGED
@@ -23,6 +23,7 @@ module Sentry
|
|
23
23
|
WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
|
24
24
|
|
25
25
|
MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
|
26
|
+
MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 200
|
26
27
|
|
27
28
|
SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@send_default_pii, :@trusted_proxies, :@rack_env_whitelist]
|
28
29
|
|
data/lib/sentry/hub.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "sentry/scope"
|
4
4
|
require "sentry/client"
|
5
|
+
require "sentry/session"
|
5
6
|
|
6
7
|
module Sentry
|
7
8
|
class Hub
|
@@ -104,6 +105,8 @@ module Sentry
|
|
104
105
|
|
105
106
|
return unless event
|
106
107
|
|
108
|
+
current_scope.session&.update_from_exception(event.exception)
|
109
|
+
|
107
110
|
capture_event(event, **options, &block).tap do
|
108
111
|
# mark the exception as captured so we can use this information to avoid duplicated capturing
|
109
112
|
exception.instance_variable_set(Sentry::CAPTURED_SIGNATURE, true)
|
@@ -143,7 +146,6 @@ module Sentry
|
|
143
146
|
|
144
147
|
event = current_client.capture_event(event, scope, hint)
|
145
148
|
|
146
|
-
|
147
149
|
if event && configuration.debug
|
148
150
|
configuration.log_debug(event.to_json_compatible)
|
149
151
|
end
|
@@ -175,6 +177,30 @@ module Sentry
|
|
175
177
|
configuration.background_worker_threads = original_background_worker_threads
|
176
178
|
end
|
177
179
|
|
180
|
+
def start_session
|
181
|
+
return unless current_scope
|
182
|
+
current_scope.set_session(Session.new)
|
183
|
+
end
|
184
|
+
|
185
|
+
def end_session
|
186
|
+
return unless current_scope
|
187
|
+
session = current_scope.session
|
188
|
+
current_scope.set_session(nil)
|
189
|
+
|
190
|
+
return unless session
|
191
|
+
session.close
|
192
|
+
Sentry.session_flusher.add_session(session)
|
193
|
+
end
|
194
|
+
|
195
|
+
def with_session_tracking(&block)
|
196
|
+
return yield unless configuration.auto_session_tracking
|
197
|
+
|
198
|
+
start_session
|
199
|
+
yield
|
200
|
+
ensure
|
201
|
+
end_session
|
202
|
+
end
|
203
|
+
|
178
204
|
private
|
179
205
|
|
180
206
|
def current_layer
|
@@ -14,30 +14,32 @@ module Sentry
|
|
14
14
|
Sentry.clone_hub_to_current_thread
|
15
15
|
|
16
16
|
Sentry.with_scope do |scope|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
17
|
+
Sentry.with_session_tracking do
|
18
|
+
scope.clear_breadcrumbs
|
19
|
+
scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
|
20
|
+
scope.set_rack_env(env)
|
21
|
+
|
22
|
+
transaction = start_transaction(env, scope)
|
23
|
+
scope.set_span(transaction) if transaction
|
24
|
+
|
25
|
+
begin
|
26
|
+
response = @app.call(env)
|
27
|
+
rescue Sentry::Error
|
28
|
+
finish_transaction(transaction, 500)
|
29
|
+
raise # Don't capture Sentry errors
|
30
|
+
rescue Exception => e
|
31
|
+
capture_exception(e)
|
32
|
+
finish_transaction(transaction, 500)
|
33
|
+
raise
|
34
|
+
end
|
35
|
+
|
36
|
+
exception = collect_exception(env)
|
37
|
+
capture_exception(exception) if exception
|
38
|
+
|
39
|
+
finish_transaction(transaction, response[0])
|
40
|
+
|
41
|
+
response
|
33
42
|
end
|
34
|
-
|
35
|
-
exception = collect_exception(env)
|
36
|
-
capture_exception(exception) if exception
|
37
|
-
|
38
|
-
finish_transaction(transaction, response[0])
|
39
|
-
|
40
|
-
response
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
@@ -59,7 +61,7 @@ module Sentry
|
|
59
61
|
sentry_trace = env["HTTP_SENTRY_TRACE"]
|
60
62
|
options = { name: scope.transaction_name, op: transaction_op }
|
61
63
|
transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
|
62
|
-
Sentry.start_transaction(transaction: transaction, **options)
|
64
|
+
Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
|
63
65
|
end
|
64
66
|
|
65
67
|
|
data/lib/sentry/redis.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
module Sentry
|
4
4
|
# @api private
|
5
5
|
class Redis
|
6
|
-
OP_NAME
|
7
|
-
LOGGER_NAME
|
6
|
+
OP_NAME = "db.redis.command"
|
7
|
+
LOGGER_NAME = :redis_logger
|
8
8
|
|
9
9
|
def initialize(commands, host, port, db)
|
10
10
|
@commands, @host, @port, @db = commands, host, port, db
|
@@ -60,9 +60,11 @@ module Sentry
|
|
60
60
|
|
61
61
|
def parsed_commands
|
62
62
|
commands.map do |statement|
|
63
|
-
command, key, *
|
63
|
+
command, key, *arguments = statement
|
64
64
|
|
65
|
-
{ command: command.to_s.upcase, key: key }
|
65
|
+
{ command: command.to_s.upcase, key: key }.tap do |command_set|
|
66
|
+
command_set[:arguments] = arguments.join(" ") if Sentry.configuration.send_default_pii
|
67
|
+
end
|
66
68
|
end
|
67
69
|
end
|
68
70
|
|
data/lib/sentry/scope.rb
CHANGED
@@ -7,7 +7,7 @@ module Sentry
|
|
7
7
|
class Scope
|
8
8
|
include ArgumentCheckingHelper
|
9
9
|
|
10
|
-
ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span]
|
10
|
+
ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span, :session]
|
11
11
|
|
12
12
|
attr_reader(*ATTRIBUTES)
|
13
13
|
|
@@ -76,6 +76,7 @@ module Sentry
|
|
76
76
|
copy.transaction_names = transaction_names.deep_dup
|
77
77
|
copy.fingerprint = fingerprint.deep_dup
|
78
78
|
copy.span = span.deep_dup
|
79
|
+
copy.session = session.deep_dup
|
79
80
|
copy
|
80
81
|
end
|
81
82
|
|
@@ -198,6 +199,13 @@ module Sentry
|
|
198
199
|
@transaction_names << transaction_name
|
199
200
|
end
|
200
201
|
|
202
|
+
# Sets the currently active session on the scope.
|
203
|
+
# @param session [Session, nil]
|
204
|
+
# @return [void]
|
205
|
+
def set_session(session)
|
206
|
+
@session = session
|
207
|
+
end
|
208
|
+
|
201
209
|
# Returns current transaction name.
|
202
210
|
# The "transaction" here does not refer to `Transaction` objects.
|
203
211
|
# @return [String, nil]
|
@@ -251,6 +259,7 @@ module Sentry
|
|
251
259
|
@event_processors = []
|
252
260
|
@rack_env = {}
|
253
261
|
@span = nil
|
262
|
+
@session = nil
|
254
263
|
set_new_breadcrumb_buffer
|
255
264
|
end
|
256
265
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
class Session
|
5
|
+
attr_reader :started, :status
|
6
|
+
|
7
|
+
# TODO-neel add :crashed after adding handled mechanism
|
8
|
+
STATUSES = %i(ok errored exited)
|
9
|
+
AGGREGATE_STATUSES = %i(errored exited)
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@started = Sentry.utc_now
|
13
|
+
@status = :ok
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO-neel add :crashed after adding handled mechanism
|
17
|
+
def update_from_exception(_exception = nil)
|
18
|
+
@status = :errored
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
@status = :exited if @status == :ok
|
23
|
+
end
|
24
|
+
|
25
|
+
# truncate seconds from the timestamp since we only care about
|
26
|
+
# minute level granularity for aggregation
|
27
|
+
def aggregation_key
|
28
|
+
Time.utc(started.year, started.month, started.day, started.hour, started.min)
|
29
|
+
end
|
30
|
+
|
31
|
+
def deep_dup
|
32
|
+
dup
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,79 @@
|
|
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
|
+
@client = client
|
12
|
+
@pending_aggregates = {}
|
13
|
+
@release = configuration.release
|
14
|
+
@environment = configuration.environment
|
15
|
+
@logger = configuration.logger
|
16
|
+
|
17
|
+
log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
|
18
|
+
end
|
19
|
+
|
20
|
+
def flush
|
21
|
+
return if @pending_aggregates.empty?
|
22
|
+
envelope = pending_envelope
|
23
|
+
|
24
|
+
Sentry.background_worker.perform do
|
25
|
+
@client.transport.send_envelope(envelope)
|
26
|
+
end
|
27
|
+
|
28
|
+
@pending_aggregates = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_session(session)
|
32
|
+
return unless @release
|
33
|
+
|
34
|
+
ensure_thread
|
35
|
+
|
36
|
+
return unless Session::AGGREGATE_STATUSES.include?(session.status)
|
37
|
+
@pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
|
38
|
+
@pending_aggregates[session.aggregation_key][session.status] += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
def kill
|
42
|
+
@thread&.kill
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def init_aggregates(aggregation_key)
|
48
|
+
aggregates = { started: aggregation_key.iso8601 }
|
49
|
+
Session::AGGREGATE_STATUSES.each { |k| aggregates[k] = 0 }
|
50
|
+
aggregates
|
51
|
+
end
|
52
|
+
|
53
|
+
def pending_envelope
|
54
|
+
envelope = Envelope.new
|
55
|
+
|
56
|
+
header = { type: 'sessions' }
|
57
|
+
payload = { attrs: attrs, aggregates: @pending_aggregates.values }
|
58
|
+
|
59
|
+
envelope.add_item(header, payload)
|
60
|
+
envelope
|
61
|
+
end
|
62
|
+
|
63
|
+
def attrs
|
64
|
+
{ release: @release, environment: @environment }
|
65
|
+
end
|
66
|
+
|
67
|
+
def ensure_thread
|
68
|
+
return if @thread&.alive?
|
69
|
+
|
70
|
+
@thread = Thread.new do
|
71
|
+
loop do
|
72
|
+
sleep(FLUSH_INTERVAL)
|
73
|
+
flush
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -2,15 +2,20 @@
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class DummyTransport < Transport
|
5
|
-
attr_accessor :events
|
5
|
+
attr_accessor :events, :envelopes
|
6
6
|
|
7
7
|
def initialize(*)
|
8
8
|
super
|
9
9
|
@events = []
|
10
|
+
@envelopes = []
|
10
11
|
end
|
11
12
|
|
12
13
|
def send_event(event)
|
13
14
|
@events << event
|
14
15
|
end
|
16
|
+
|
17
|
+
def send_envelope(envelope)
|
18
|
+
@envelopes << envelope
|
19
|
+
end
|
15
20
|
end
|
16
21
|
end
|
data/lib/sentry/transport.rb
CHANGED
@@ -57,8 +57,43 @@ module Sentry
|
|
57
57
|
|
58
58
|
return if envelope.items.empty?
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
data, serialized_items = serialize_envelope(envelope)
|
61
|
+
|
62
|
+
if data
|
63
|
+
log_info("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
|
64
|
+
send_data(data)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def serialize_envelope(envelope)
|
69
|
+
serialized_items = []
|
70
|
+
serialized_results = []
|
71
|
+
|
72
|
+
envelope.items.each do |item|
|
73
|
+
result = item.to_s
|
74
|
+
|
75
|
+
if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
|
76
|
+
item.payload.delete(:breadcrumbs)
|
77
|
+
result = item.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
|
81
|
+
size_breakdown = item.payload.map do |key, value|
|
82
|
+
"#{key}: #{JSON.generate(value).bytesize}"
|
83
|
+
end.join(", ")
|
84
|
+
|
85
|
+
log_debug("Envelope item [#{item.type}] is still oversized without breadcrumbs: {#{size_breakdown}}")
|
86
|
+
|
87
|
+
next
|
88
|
+
end
|
89
|
+
|
90
|
+
serialized_results << result
|
91
|
+
serialized_items << item
|
92
|
+
end
|
93
|
+
|
94
|
+
data = [JSON.generate(envelope.headers), *serialized_results].join("\n") unless serialized_results.empty?
|
95
|
+
|
96
|
+
[data, serialized_items]
|
62
97
|
end
|
63
98
|
|
64
99
|
def is_rate_limited?(item_type)
|
@@ -67,6 +102,8 @@ module Sentry
|
|
67
102
|
case item_type
|
68
103
|
when "transaction"
|
69
104
|
@rate_limits["transaction"]
|
105
|
+
when "sessions"
|
106
|
+
@rate_limits["session"]
|
70
107
|
else
|
71
108
|
@rate_limits["error"]
|
72
109
|
end
|
@@ -125,7 +162,6 @@ module Sentry
|
|
125
162
|
client_report_headers, client_report_payload = fetch_pending_client_report
|
126
163
|
envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
|
127
164
|
|
128
|
-
|
129
165
|
envelope
|
130
166
|
end
|
131
167
|
|
data/lib/sentry/version.rb
CHANGED
data/lib/sentry-ruby.rb
CHANGED
@@ -17,6 +17,7 @@ require "sentry/span"
|
|
17
17
|
require "sentry/transaction"
|
18
18
|
require "sentry/hub"
|
19
19
|
require "sentry/background_worker"
|
20
|
+
require "sentry/session_flusher"
|
20
21
|
|
21
22
|
[
|
22
23
|
"sentry/rake",
|
@@ -61,6 +62,10 @@ module Sentry
|
|
61
62
|
# @return [BackgroundWorker]
|
62
63
|
attr_accessor :background_worker
|
63
64
|
|
65
|
+
# @!attribute [r] session_flusher
|
66
|
+
# @return [SessionFlusher]
|
67
|
+
attr_reader :session_flusher
|
68
|
+
|
64
69
|
##### Patch Registration #####
|
65
70
|
|
66
71
|
# @!visibility private
|
@@ -189,11 +194,18 @@ module Sentry
|
|
189
194
|
@main_hub = hub
|
190
195
|
@background_worker = Sentry::BackgroundWorker.new(config)
|
191
196
|
|
197
|
+
@session_flusher = if config.auto_session_tracking
|
198
|
+
Sentry::SessionFlusher.new(config, client)
|
199
|
+
else
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
|
192
203
|
if config.capture_exception_frame_locals
|
193
204
|
exception_locals_tp.enable
|
194
205
|
end
|
195
206
|
|
196
207
|
at_exit do
|
208
|
+
@session_flusher&.kill
|
197
209
|
@background_worker.shutdown
|
198
210
|
end
|
199
211
|
end
|
@@ -310,6 +322,26 @@ module Sentry
|
|
310
322
|
get_current_hub.with_scope(&block)
|
311
323
|
end
|
312
324
|
|
325
|
+
# Wrap a given block with session tracking.
|
326
|
+
# Aggregate sessions in minutely buckets will be recorded
|
327
|
+
# around this block and flushed every minute.
|
328
|
+
#
|
329
|
+
# @example
|
330
|
+
# Sentry.with_session_tracking do
|
331
|
+
# a = 1 + 1 # new session recorded with :exited status
|
332
|
+
# end
|
333
|
+
#
|
334
|
+
# Sentry.with_session_tracking do
|
335
|
+
# 1 / 0
|
336
|
+
# rescue => e
|
337
|
+
# Sentry.capture_exception(e) # new session recorded with :errored status
|
338
|
+
# end
|
339
|
+
# @return [void]
|
340
|
+
def with_session_tracking(&block)
|
341
|
+
return yield unless initialized?
|
342
|
+
get_current_hub.with_session_tracking(&block)
|
343
|
+
end
|
344
|
+
|
313
345
|
# Takes an exception and reports it to Sentry via the currently active hub.
|
314
346
|
#
|
315
347
|
# @yieldparam scope [Scope]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentry-ruby-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -76,6 +76,8 @@ files:
|
|
76
76
|
- lib/sentry/redis.rb
|
77
77
|
- lib/sentry/release_detector.rb
|
78
78
|
- lib/sentry/scope.rb
|
79
|
+
- lib/sentry/session.rb
|
80
|
+
- lib/sentry/session_flusher.rb
|
79
81
|
- lib/sentry/span.rb
|
80
82
|
- lib/sentry/transaction.rb
|
81
83
|
- lib/sentry/transaction_event.rb
|