sentry-ruby-core 5.1.1 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|