sentry-ruby-core 5.0.2 → 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/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
|