sentry-ruby-core 5.0.2 → 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/Gemfile +1 -0
- data/lib/sentry/configuration.rb +6 -0
- data/lib/sentry/envelope.rb +29 -10
- data/lib/sentry/event.rb +1 -0
- data/lib/sentry/hub.rb +33 -2
- data/lib/sentry/interfaces/exception.rb +1 -0
- data/lib/sentry/interfaces/request.rb +3 -2
- data/lib/sentry/net/http.rb +3 -2
- data/lib/sentry/rack/capture_exceptions.rb +26 -24
- data/lib/sentry/redis.rb +90 -0
- data/lib/sentry/scope.rb +11 -2
- data/lib/sentry/session.rb +35 -0
- data/lib/sentry/session_flusher.rb +79 -0
- data/lib/sentry/transaction.rb +4 -5
- data/lib/sentry/transport/dummy_transport.rb +6 -1
- data/lib/sentry/transport.rb +58 -14
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +90 -12
- metadata +5 -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/Gemfile
CHANGED
data/lib/sentry/configuration.rb
CHANGED
@@ -75,6 +75,7 @@ module Sentry
|
|
75
75
|
# An array of breadcrumbs loggers to be used. Available options are:
|
76
76
|
# - :sentry_logger
|
77
77
|
# - :http_logger
|
78
|
+
# - :redis_logger
|
78
79
|
#
|
79
80
|
# And if you also use sentry-rails:
|
80
81
|
# - :active_support_logger
|
@@ -206,6 +207,10 @@ module Sentry
|
|
206
207
|
# @return [Boolean]
|
207
208
|
attr_accessor :send_client_reports
|
208
209
|
|
210
|
+
# Track sessions in request/response cycles automatically
|
211
|
+
# @return [Boolean]
|
212
|
+
attr_accessor :auto_session_tracking
|
213
|
+
|
209
214
|
# these are not config options
|
210
215
|
# @!visibility private
|
211
216
|
attr_reader :errors, :gem_specs
|
@@ -260,6 +265,7 @@ module Sentry
|
|
260
265
|
self.send_default_pii = false
|
261
266
|
self.skip_rake_integration = false
|
262
267
|
self.send_client_reports = true
|
268
|
+
self.auto_session_tracking = true
|
263
269
|
self.trusted_proxies = []
|
264
270
|
self.dsn = ENV['SENTRY_DSN']
|
265
271
|
self.server_name = server_name_from_env
|
data/lib/sentry/envelope.rb
CHANGED
@@ -3,24 +3,43 @@
|
|
3
3
|
module Sentry
|
4
4
|
# @api private
|
5
5
|
class Envelope
|
6
|
-
|
6
|
+
class Item
|
7
|
+
attr_accessor :headers, :payload
|
8
|
+
|
9
|
+
def initialize(headers, payload)
|
10
|
+
@headers = headers
|
11
|
+
@payload = payload
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
@headers[:type] || 'event'
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
<<~ITEM
|
20
|
+
#{JSON.generate(@headers)}
|
21
|
+
#{JSON.generate(@payload)}
|
22
|
+
ITEM
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :headers, :items
|
27
|
+
|
28
|
+
def initialize(headers = {})
|
7
29
|
@headers = headers
|
8
30
|
@items = []
|
9
31
|
end
|
10
32
|
|
11
33
|
def add_item(headers, payload)
|
12
|
-
@items <<
|
34
|
+
@items << Item.new(headers, payload)
|
13
35
|
end
|
14
36
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
#{JSON.generate(item_headers)}
|
19
|
-
#{JSON.generate(item_payload)}
|
20
|
-
ENVELOPE
|
21
|
-
end.join("\n")
|
37
|
+
def item_types
|
38
|
+
@items.map(&:type)
|
39
|
+
end
|
22
40
|
|
23
|
-
|
41
|
+
def event_id
|
42
|
+
@headers[:event_id]
|
24
43
|
end
|
25
44
|
end
|
26
45
|
end
|
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
|
@@ -94,6 +95,8 @@ module Sentry
|
|
94
95
|
def capture_exception(exception, **options, &block)
|
95
96
|
check_argument_type!(exception, ::Exception)
|
96
97
|
|
98
|
+
return if Sentry.exception_captured?(exception)
|
99
|
+
|
97
100
|
return unless current_client
|
98
101
|
|
99
102
|
options[:hint] ||= {}
|
@@ -102,7 +105,12 @@ module Sentry
|
|
102
105
|
|
103
106
|
return unless event
|
104
107
|
|
105
|
-
|
108
|
+
current_scope.session&.update_from_exception(event.exception)
|
109
|
+
|
110
|
+
capture_event(event, **options, &block).tap do
|
111
|
+
# mark the exception as captured so we can use this information to avoid duplicated capturing
|
112
|
+
exception.instance_variable_set(Sentry::CAPTURED_SIGNATURE, true)
|
113
|
+
end
|
106
114
|
end
|
107
115
|
|
108
116
|
def capture_message(message, **options, &block)
|
@@ -138,7 +146,6 @@ module Sentry
|
|
138
146
|
|
139
147
|
event = current_client.capture_event(event, scope, hint)
|
140
148
|
|
141
|
-
|
142
149
|
if event && configuration.debug
|
143
150
|
configuration.log_debug(event.to_json_compatible)
|
144
151
|
end
|
@@ -170,6 +177,30 @@ module Sentry
|
|
170
177
|
configuration.background_worker_threads = original_background_worker_threads
|
171
178
|
end
|
172
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
|
+
|
173
204
|
private
|
174
205
|
|
175
206
|
def current_layer
|
@@ -62,7 +62,7 @@ module Sentry
|
|
62
62
|
self.url = request.scheme && request.url.split('?').first
|
63
63
|
self.method = request.request_method
|
64
64
|
|
65
|
-
self.headers = filter_and_format_headers(env)
|
65
|
+
self.headers = filter_and_format_headers(env, send_default_pii)
|
66
66
|
self.env = filter_and_format_env(env, rack_env_whitelist)
|
67
67
|
end
|
68
68
|
|
@@ -81,13 +81,14 @@ module Sentry
|
|
81
81
|
e.message
|
82
82
|
end
|
83
83
|
|
84
|
-
def filter_and_format_headers(env)
|
84
|
+
def filter_and_format_headers(env, send_default_pii)
|
85
85
|
env.each_with_object({}) do |(key, value), memo|
|
86
86
|
begin
|
87
87
|
key = key.to_s # rack env can contain symbols
|
88
88
|
next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
|
89
89
|
next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
|
90
90
|
next if is_skippable_header?(key)
|
91
|
+
next if key == "HTTP_AUTHORIZATION" && !send_default_pii
|
91
92
|
|
92
93
|
# Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
|
93
94
|
key = key.sub(/^HTTP_/, "")
|
data/lib/sentry/net/http.rb
CHANGED
@@ -6,7 +6,8 @@ module Sentry
|
|
6
6
|
# @api private
|
7
7
|
module Net
|
8
8
|
module HTTP
|
9
|
-
OP_NAME = "
|
9
|
+
OP_NAME = "http.client"
|
10
|
+
BREADCRUMB_CATEGORY = "net.http"
|
10
11
|
|
11
12
|
# To explain how the entire thing works, we need to know how the original Net::HTTP#request works
|
12
13
|
# Here's part of its definition. As you can see, it usually calls itself inside a #start block
|
@@ -53,7 +54,7 @@ module Sentry
|
|
53
54
|
|
54
55
|
crumb = Sentry::Breadcrumb.new(
|
55
56
|
level: :info,
|
56
|
-
category:
|
57
|
+
category: BREADCRUMB_CATEGORY,
|
57
58
|
type: :info,
|
58
59
|
data: {
|
59
60
|
status: res.code.to_i,
|
@@ -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
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
# @api private
|
5
|
+
class Redis
|
6
|
+
OP_NAME = "db.redis.command"
|
7
|
+
LOGGER_NAME = :redis_logger
|
8
|
+
|
9
|
+
def initialize(commands, host, port, db)
|
10
|
+
@commands, @host, @port, @db = commands, host, port, db
|
11
|
+
end
|
12
|
+
|
13
|
+
def instrument
|
14
|
+
return yield unless Sentry.initialized?
|
15
|
+
|
16
|
+
record_span do
|
17
|
+
yield.tap do
|
18
|
+
record_breadcrumb
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :commands, :host, :port, :db
|
26
|
+
|
27
|
+
def record_span
|
28
|
+
return yield unless (transaction = Sentry.get_current_scope.get_transaction) && transaction.sampled
|
29
|
+
|
30
|
+
sentry_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
|
31
|
+
|
32
|
+
yield.tap do
|
33
|
+
sentry_span.set_description(commands_description)
|
34
|
+
sentry_span.set_data(:server, server_description)
|
35
|
+
sentry_span.set_timestamp(Sentry.utc_now.to_f)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def record_breadcrumb
|
40
|
+
return unless Sentry.configuration.breadcrumbs_logger.include?(LOGGER_NAME)
|
41
|
+
|
42
|
+
Sentry.add_breadcrumb(
|
43
|
+
Sentry::Breadcrumb.new(
|
44
|
+
level: :info,
|
45
|
+
category: OP_NAME,
|
46
|
+
type: :info,
|
47
|
+
data: {
|
48
|
+
commands: parsed_commands,
|
49
|
+
server: server_description
|
50
|
+
}
|
51
|
+
)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def commands_description
|
56
|
+
parsed_commands.map do |statement|
|
57
|
+
statement.values.join(" ").strip
|
58
|
+
end.join(", ")
|
59
|
+
end
|
60
|
+
|
61
|
+
def parsed_commands
|
62
|
+
commands.map do |statement|
|
63
|
+
command, key, *arguments = statement
|
64
|
+
|
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
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def server_description
|
72
|
+
"#{host}:#{port}/#{db}"
|
73
|
+
end
|
74
|
+
|
75
|
+
module Client
|
76
|
+
def logging(commands, &block)
|
77
|
+
Sentry::Redis.new(commands, host, port, db).instrument do
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
if defined?(::Redis::Client)
|
86
|
+
Sentry.register_patch do
|
87
|
+
patch = Sentry::Redis::Client
|
88
|
+
Redis::Client.prepend(patch) unless Redis::Client.ancestors.include?(patch)
|
89
|
+
end
|
90
|
+
end
|
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
|
|
@@ -173,7 +174,7 @@ module Sentry
|
|
173
174
|
def set_contexts(contexts_hash)
|
174
175
|
check_argument_type!(contexts_hash, Hash)
|
175
176
|
@contexts.merge!(contexts_hash) do |key, old, new|
|
176
|
-
|
177
|
+
old.merge(new)
|
177
178
|
end
|
178
179
|
end
|
179
180
|
|
@@ -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
|
data/lib/sentry/transaction.rb
CHANGED
@@ -164,13 +164,12 @@ module Sentry
|
|
164
164
|
@name = UNLABELD_NAME
|
165
165
|
end
|
166
166
|
|
167
|
-
|
167
|
+
if @sampled
|
168
|
+
event = hub.current_client.event_from_transaction(self)
|
169
|
+
hub.capture_event(event)
|
170
|
+
else
|
168
171
|
hub.current_client.transport.record_lost_event(:sample_rate, 'transaction')
|
169
|
-
return
|
170
172
|
end
|
171
|
-
|
172
|
-
event = hub.current_client.event_from_transaction(self)
|
173
|
-
hub.capture_event(event)
|
174
173
|
end
|
175
174
|
|
176
175
|
protected
|
@@ -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
@@ -46,23 +46,54 @@ module Sentry
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def send_event(event)
|
49
|
-
|
50
|
-
|
49
|
+
envelope = envelope_from_event(event)
|
50
|
+
send_envelope(envelope)
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
event
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_envelope(envelope)
|
56
|
+
reject_rate_limited_items(envelope)
|
55
57
|
|
56
|
-
|
58
|
+
return if envelope.items.empty?
|
59
|
+
|
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)
|
57
65
|
end
|
66
|
+
end
|
58
67
|
|
59
|
-
|
68
|
+
def serialize_envelope(envelope)
|
69
|
+
serialized_items = []
|
70
|
+
serialized_results = []
|
60
71
|
|
61
|
-
|
72
|
+
envelope.items.each do |item|
|
73
|
+
result = item.to_s
|
62
74
|
|
63
|
-
|
75
|
+
if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
|
76
|
+
item.payload.delete(:breadcrumbs)
|
77
|
+
result = item.to_s
|
78
|
+
end
|
64
79
|
|
65
|
-
|
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]
|
66
97
|
end
|
67
98
|
|
68
99
|
def is_rate_limited?(item_type)
|
@@ -71,6 +102,8 @@ module Sentry
|
|
71
102
|
case item_type
|
72
103
|
when "transaction"
|
73
104
|
@rate_limits["transaction"]
|
105
|
+
when "sessions"
|
106
|
+
@rate_limits["session"]
|
74
107
|
else
|
75
108
|
@rate_limits["error"]
|
76
109
|
end
|
@@ -106,7 +139,7 @@ module Sentry
|
|
106
139
|
'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
|
107
140
|
end
|
108
141
|
|
109
|
-
def
|
142
|
+
def envelope_from_event(event)
|
110
143
|
# Convert to hash
|
111
144
|
event_payload = event.to_hash
|
112
145
|
event_id = event_payload[:event_id] || event_payload["event_id"]
|
@@ -129,9 +162,7 @@ module Sentry
|
|
129
162
|
client_report_headers, client_report_payload = fetch_pending_client_report
|
130
163
|
envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
|
131
164
|
|
132
|
-
|
133
|
-
|
134
|
-
envelope.to_s
|
165
|
+
envelope
|
135
166
|
end
|
136
167
|
|
137
168
|
def record_lost_event(reason, item_type)
|
@@ -173,6 +204,19 @@ module Sentry
|
|
173
204
|
|
174
205
|
[item_header, item_payload]
|
175
206
|
end
|
207
|
+
|
208
|
+
def reject_rate_limited_items(envelope)
|
209
|
+
envelope.items.reject! do |item|
|
210
|
+
if is_rate_limited?(item.type)
|
211
|
+
log_info("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
|
212
|
+
record_lost_event(:ratelimit_backoff, item.type)
|
213
|
+
|
214
|
+
true
|
215
|
+
else
|
216
|
+
false
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
176
220
|
end
|
177
221
|
end
|
178
222
|
|
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",
|
@@ -31,6 +32,8 @@ end
|
|
31
32
|
module Sentry
|
32
33
|
META = { "name" => "sentry.ruby", "version" => Sentry::VERSION }.freeze
|
33
34
|
|
35
|
+
CAPTURED_SIGNATURE = :@__sentry_captured
|
36
|
+
|
34
37
|
LOGGER_PROGNAME = "sentry".freeze
|
35
38
|
|
36
39
|
SENTRY_TRACE_HEADER_NAME = "sentry-trace".freeze
|
@@ -59,6 +62,10 @@ module Sentry
|
|
59
62
|
# @return [BackgroundWorker]
|
60
63
|
attr_accessor :background_worker
|
61
64
|
|
65
|
+
# @!attribute [r] session_flusher
|
66
|
+
# @return [SessionFlusher]
|
67
|
+
attr_reader :session_flusher
|
68
|
+
|
62
69
|
##### Patch Registration #####
|
63
70
|
|
64
71
|
# @!visibility private
|
@@ -111,9 +118,17 @@ module Sentry
|
|
111
118
|
|
112
119
|
# @!method configuration
|
113
120
|
# @!macro configuration
|
121
|
+
def configuration
|
122
|
+
return unless initialized?
|
123
|
+
get_current_client.configuration
|
124
|
+
end
|
125
|
+
|
114
126
|
# @!method send_event
|
115
127
|
# @!macro send_event
|
116
|
-
|
128
|
+
def send_event(*args)
|
129
|
+
return unless initialized?
|
130
|
+
get_current_client.send_event(*args)
|
131
|
+
end
|
117
132
|
|
118
133
|
# @!macro [new] set_extras
|
119
134
|
# Updates the scope's extras attribute by merging with the old value.
|
@@ -135,13 +150,31 @@ module Sentry
|
|
135
150
|
|
136
151
|
# @!method set_tags
|
137
152
|
# @!macro set_tags
|
153
|
+
def set_tags(*args)
|
154
|
+
return unless initialized?
|
155
|
+
get_current_scope.set_tags(*args)
|
156
|
+
end
|
157
|
+
|
138
158
|
# @!method set_extras
|
139
159
|
# @!macro set_extras
|
160
|
+
def set_extras(*args)
|
161
|
+
return unless initialized?
|
162
|
+
get_current_scope.set_extras(*args)
|
163
|
+
end
|
164
|
+
|
140
165
|
# @!method set_user
|
141
166
|
# @!macro set_user
|
167
|
+
def set_user(*args)
|
168
|
+
return unless initialized?
|
169
|
+
get_current_scope.set_user(*args)
|
170
|
+
end
|
171
|
+
|
142
172
|
# @!method set_context
|
143
173
|
# @!macro set_context
|
144
|
-
|
174
|
+
def set_context(*args)
|
175
|
+
return unless initialized?
|
176
|
+
get_current_scope.set_context(*args)
|
177
|
+
end
|
145
178
|
|
146
179
|
##### Main APIs #####
|
147
180
|
|
@@ -161,11 +194,18 @@ module Sentry
|
|
161
194
|
@main_hub = hub
|
162
195
|
@background_worker = Sentry::BackgroundWorker.new(config)
|
163
196
|
|
197
|
+
@session_flusher = if config.auto_session_tracking
|
198
|
+
Sentry::SessionFlusher.new(config, client)
|
199
|
+
else
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
|
164
203
|
if config.capture_exception_frame_locals
|
165
204
|
exception_locals_tp.enable
|
166
205
|
end
|
167
206
|
|
168
207
|
at_exit do
|
208
|
+
@session_flusher&.kill
|
169
209
|
@background_worker.shutdown
|
170
210
|
end
|
171
211
|
end
|
@@ -201,7 +241,8 @@ module Sentry
|
|
201
241
|
#
|
202
242
|
# @return [Breadcrumb, nil]
|
203
243
|
def add_breadcrumb(breadcrumb, **options)
|
204
|
-
|
244
|
+
return unless initialized?
|
245
|
+
get_current_hub.add_breadcrumb(breadcrumb, **options)
|
205
246
|
end
|
206
247
|
|
207
248
|
# Returns the current active hub.
|
@@ -221,14 +262,16 @@ module Sentry
|
|
221
262
|
# Returns the current active client.
|
222
263
|
# @return [Client, nil]
|
223
264
|
def get_current_client
|
224
|
-
|
265
|
+
return unless initialized?
|
266
|
+
get_current_hub.current_client
|
225
267
|
end
|
226
268
|
|
227
269
|
# Returns the current active scope.
|
228
270
|
#
|
229
271
|
# @return [Scope, nil]
|
230
272
|
def get_current_scope
|
231
|
-
|
273
|
+
return unless initialized?
|
274
|
+
get_current_hub.current_scope
|
232
275
|
end
|
233
276
|
|
234
277
|
# Clones the main thread's active hub and stores it to the current thread.
|
@@ -250,7 +293,8 @@ module Sentry
|
|
250
293
|
# @yieldparam scope [Scope]
|
251
294
|
# @return [void]
|
252
295
|
def configure_scope(&block)
|
253
|
-
|
296
|
+
return unless initialized?
|
297
|
+
get_current_hub.configure_scope(&block)
|
254
298
|
end
|
255
299
|
|
256
300
|
# Takes a block and yields a temporary scope.
|
@@ -274,7 +318,28 @@ module Sentry
|
|
274
318
|
# @yieldparam scope [Scope]
|
275
319
|
# @return [void]
|
276
320
|
def with_scope(&block)
|
277
|
-
|
321
|
+
return unless initialized?
|
322
|
+
get_current_hub.with_scope(&block)
|
323
|
+
end
|
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)
|
278
343
|
end
|
279
344
|
|
280
345
|
# Takes an exception and reports it to Sentry via the currently active hub.
|
@@ -282,7 +347,8 @@ module Sentry
|
|
282
347
|
# @yieldparam scope [Scope]
|
283
348
|
# @return [Event, nil]
|
284
349
|
def capture_exception(exception, **options, &block)
|
285
|
-
|
350
|
+
return unless initialized?
|
351
|
+
get_current_hub.capture_exception(exception, **options, &block)
|
286
352
|
end
|
287
353
|
|
288
354
|
# Takes a message string and reports it to Sentry via the currently active hub.
|
@@ -290,30 +356,41 @@ module Sentry
|
|
290
356
|
# @yieldparam scope [Scope]
|
291
357
|
# @return [Event, nil]
|
292
358
|
def capture_message(message, **options, &block)
|
293
|
-
|
359
|
+
return unless initialized?
|
360
|
+
get_current_hub.capture_message(message, **options, &block)
|
294
361
|
end
|
295
362
|
|
296
363
|
# Takes an instance of Sentry::Event and dispatches it to the currently active hub.
|
297
364
|
#
|
298
365
|
# @return [Event, nil]
|
299
366
|
def capture_event(event)
|
300
|
-
|
367
|
+
return unless initialized?
|
368
|
+
get_current_hub.capture_event(event)
|
301
369
|
end
|
302
370
|
|
303
371
|
# Takes or initializes a new Sentry::Transaction and makes a sampling decision for it.
|
304
372
|
#
|
305
373
|
# @return [Transaction, nil]
|
306
374
|
def start_transaction(**options)
|
307
|
-
|
375
|
+
return unless initialized?
|
376
|
+
get_current_hub.start_transaction(**options)
|
308
377
|
end
|
309
378
|
|
310
379
|
# Returns the id of the lastly reported Sentry::Event.
|
311
380
|
#
|
312
381
|
# @return [String, nil]
|
313
382
|
def last_event_id
|
314
|
-
|
383
|
+
return unless initialized?
|
384
|
+
get_current_hub.last_event_id
|
315
385
|
end
|
316
386
|
|
387
|
+
# Checks if the exception object has been captured by the SDK.
|
388
|
+
#
|
389
|
+
# @return [Boolean]
|
390
|
+
def exception_captured?(exc)
|
391
|
+
return false unless initialized?
|
392
|
+
!!exc.instance_variable_get(CAPTURED_SIGNATURE)
|
393
|
+
end
|
317
394
|
|
318
395
|
##### Helpers #####
|
319
396
|
|
@@ -344,3 +421,4 @@ end
|
|
344
421
|
|
345
422
|
# patches
|
346
423
|
require "sentry/net/http"
|
424
|
+
require "sentry/redis"
|
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.0
|
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
|
@@ -73,8 +73,11 @@ files:
|
|
73
73
|
- lib/sentry/rack.rb
|
74
74
|
- lib/sentry/rack/capture_exceptions.rb
|
75
75
|
- lib/sentry/rake.rb
|
76
|
+
- lib/sentry/redis.rb
|
76
77
|
- lib/sentry/release_detector.rb
|
77
78
|
- lib/sentry/scope.rb
|
79
|
+
- lib/sentry/session.rb
|
80
|
+
- lib/sentry/session_flusher.rb
|
78
81
|
- lib/sentry/span.rb
|
79
82
|
- lib/sentry/transaction.rb
|
80
83
|
- lib/sentry/transaction_event.rb
|