sentry-ruby 5.1.0 → 5.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +313 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +27 -0
- data/Makefile +4 -0
- data/README.md +8 -7
- data/Rakefile +13 -0
- data/bin/console +18 -0
- data/bin/setup +8 -0
- data/lib/sentry/background_worker.rb +72 -0
- data/lib/sentry/backtrace.rb +124 -0
- data/lib/sentry/breadcrumb/sentry_logger.rb +90 -0
- data/lib/sentry/breadcrumb.rb +70 -0
- data/lib/sentry/breadcrumb_buffer.rb +64 -0
- data/lib/sentry/client.rb +190 -0
- data/lib/sentry/configuration.rb +502 -0
- data/lib/sentry/core_ext/object/deep_dup.rb +61 -0
- data/lib/sentry/core_ext/object/duplicable.rb +155 -0
- data/lib/sentry/dsn.rb +53 -0
- data/lib/sentry/envelope.rb +96 -0
- data/lib/sentry/error_event.rb +38 -0
- data/lib/sentry/event.rb +178 -0
- data/lib/sentry/exceptions.rb +9 -0
- data/lib/sentry/hub.rb +220 -0
- data/lib/sentry/integrable.rb +26 -0
- data/lib/sentry/interface.rb +16 -0
- data/lib/sentry/interfaces/exception.rb +43 -0
- data/lib/sentry/interfaces/request.rb +144 -0
- data/lib/sentry/interfaces/single_exception.rb +57 -0
- data/lib/sentry/interfaces/stacktrace.rb +87 -0
- data/lib/sentry/interfaces/stacktrace_builder.rb +79 -0
- data/lib/sentry/interfaces/threads.rb +42 -0
- data/lib/sentry/linecache.rb +47 -0
- data/lib/sentry/logger.rb +20 -0
- data/lib/sentry/net/http.rb +115 -0
- data/lib/sentry/rack/capture_exceptions.rb +80 -0
- data/lib/sentry/rack.rb +5 -0
- data/lib/sentry/rake.rb +41 -0
- data/lib/sentry/redis.rb +90 -0
- data/lib/sentry/release_detector.rb +39 -0
- data/lib/sentry/scope.rb +295 -0
- data/lib/sentry/session.rb +35 -0
- data/lib/sentry/session_flusher.rb +90 -0
- data/lib/sentry/span.rb +226 -0
- data/lib/sentry/test_helper.rb +76 -0
- data/lib/sentry/transaction.rb +206 -0
- data/lib/sentry/transaction_event.rb +29 -0
- data/lib/sentry/transport/configuration.rb +25 -0
- data/lib/sentry/transport/dummy_transport.rb +21 -0
- data/lib/sentry/transport/http_transport.rb +175 -0
- data/lib/sentry/transport.rb +210 -0
- data/lib/sentry/utils/argument_checking_helper.rb +13 -0
- data/lib/sentry/utils/custom_inspection.rb +14 -0
- data/lib/sentry/utils/exception_cause_chain.rb +20 -0
- data/lib/sentry/utils/logging_helper.rb +26 -0
- data/lib/sentry/utils/real_ip.rb +84 -0
- data/lib/sentry/utils/request_id.rb +18 -0
- data/lib/sentry/version.rb +5 -0
- data/lib/sentry-ruby.rb +505 -0
- data/sentry-ruby-core.gemspec +23 -0
- data/sentry-ruby.gemspec +24 -0
- metadata +64 -16
data/lib/sentry/rake.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake"
|
4
|
+
require "rake/task"
|
5
|
+
|
6
|
+
module Sentry
|
7
|
+
module Rake
|
8
|
+
module Application
|
9
|
+
# @api private
|
10
|
+
def display_error_message(ex)
|
11
|
+
Sentry.capture_exception(ex) do |scope|
|
12
|
+
task_name = top_level_tasks.join(' ')
|
13
|
+
scope.set_transaction_name(task_name)
|
14
|
+
scope.set_tag("rake_task", task_name)
|
15
|
+
end if Sentry.initialized? && !Sentry.configuration.skip_rake_integration
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Task
|
22
|
+
# @api private
|
23
|
+
def execute(args=nil)
|
24
|
+
return super unless Sentry.initialized? && Sentry.get_current_hub
|
25
|
+
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
module Rake
|
34
|
+
class Application
|
35
|
+
prepend(Sentry::Rake::Application)
|
36
|
+
end
|
37
|
+
|
38
|
+
class Task
|
39
|
+
prepend(Sentry::Rake::Task)
|
40
|
+
end
|
41
|
+
end
|
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
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
# @api private
|
5
|
+
class ReleaseDetector
|
6
|
+
class << self
|
7
|
+
def detect_release(project_root:, running_on_heroku:)
|
8
|
+
detect_release_from_env ||
|
9
|
+
detect_release_from_git ||
|
10
|
+
detect_release_from_capistrano(project_root) ||
|
11
|
+
detect_release_from_heroku(running_on_heroku)
|
12
|
+
end
|
13
|
+
|
14
|
+
def detect_release_from_heroku(running_on_heroku)
|
15
|
+
return unless running_on_heroku
|
16
|
+
ENV['HEROKU_SLUG_COMMIT']
|
17
|
+
end
|
18
|
+
|
19
|
+
def detect_release_from_capistrano(project_root)
|
20
|
+
revision_file = File.join(project_root, 'REVISION')
|
21
|
+
revision_log = File.join(project_root, '..', 'revisions.log')
|
22
|
+
|
23
|
+
if File.exist?(revision_file)
|
24
|
+
File.read(revision_file).strip
|
25
|
+
elsif File.exist?(revision_log)
|
26
|
+
File.open(revision_log).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def detect_release_from_git
|
31
|
+
Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
|
32
|
+
end
|
33
|
+
|
34
|
+
def detect_release_from_env
|
35
|
+
ENV['SENTRY_RELEASE']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/sentry/scope.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sentry/breadcrumb_buffer"
|
4
|
+
require "etc"
|
5
|
+
|
6
|
+
module Sentry
|
7
|
+
class Scope
|
8
|
+
include ArgumentCheckingHelper
|
9
|
+
|
10
|
+
ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span, :session]
|
11
|
+
|
12
|
+
attr_reader(*ATTRIBUTES)
|
13
|
+
|
14
|
+
# @param max_breadcrumbs [Integer] the maximum number of breadcrumbs to be stored in the scope.
|
15
|
+
def initialize(max_breadcrumbs: nil)
|
16
|
+
@max_breadcrumbs = max_breadcrumbs
|
17
|
+
set_default_value
|
18
|
+
end
|
19
|
+
|
20
|
+
# Resets the scope's attributes to defaults.
|
21
|
+
# @return [void]
|
22
|
+
def clear
|
23
|
+
set_default_value
|
24
|
+
end
|
25
|
+
|
26
|
+
# Applies stored attributes and event processors to the given event.
|
27
|
+
# @param event [Event]
|
28
|
+
# @param hint [Hash] the hint data that'll be passed to event processors.
|
29
|
+
# @return [Event]
|
30
|
+
def apply_to_event(event, hint = nil)
|
31
|
+
event.tags = tags.merge(event.tags)
|
32
|
+
event.user = user.merge(event.user)
|
33
|
+
event.extra = extra.merge(event.extra)
|
34
|
+
event.contexts = contexts.merge(event.contexts)
|
35
|
+
event.transaction = transaction_name if transaction_name
|
36
|
+
|
37
|
+
if span
|
38
|
+
event.contexts[:trace] = span.get_trace_context
|
39
|
+
end
|
40
|
+
|
41
|
+
event.fingerprint = fingerprint
|
42
|
+
event.level = level
|
43
|
+
event.breadcrumbs = breadcrumbs
|
44
|
+
event.rack_env = rack_env if rack_env
|
45
|
+
|
46
|
+
unless @event_processors.empty?
|
47
|
+
@event_processors.each do |processor_block|
|
48
|
+
event = processor_block.call(event, hint)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
event
|
53
|
+
end
|
54
|
+
|
55
|
+
# Adds the breadcrumb to the scope's breadcrumbs buffer.
|
56
|
+
# @param breadcrumb [Breadcrumb]
|
57
|
+
# @return [void]
|
58
|
+
def add_breadcrumb(breadcrumb)
|
59
|
+
breadcrumbs.record(breadcrumb)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Clears the scope's breadcrumbs buffer
|
63
|
+
# @return [void]
|
64
|
+
def clear_breadcrumbs
|
65
|
+
set_new_breadcrumb_buffer
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Scope]
|
69
|
+
def dup
|
70
|
+
copy = super
|
71
|
+
copy.breadcrumbs = breadcrumbs.dup
|
72
|
+
copy.contexts = contexts.deep_dup
|
73
|
+
copy.extra = extra.deep_dup
|
74
|
+
copy.tags = tags.deep_dup
|
75
|
+
copy.user = user.deep_dup
|
76
|
+
copy.transaction_names = transaction_names.deep_dup
|
77
|
+
copy.fingerprint = fingerprint.deep_dup
|
78
|
+
copy.span = span.deep_dup
|
79
|
+
copy.session = session.deep_dup
|
80
|
+
copy
|
81
|
+
end
|
82
|
+
|
83
|
+
# Updates the scope's data from a given scope.
|
84
|
+
# @param scope [Scope]
|
85
|
+
# @return [void]
|
86
|
+
def update_from_scope(scope)
|
87
|
+
self.breadcrumbs = scope.breadcrumbs
|
88
|
+
self.contexts = scope.contexts
|
89
|
+
self.extra = scope.extra
|
90
|
+
self.tags = scope.tags
|
91
|
+
self.user = scope.user
|
92
|
+
self.transaction_names = scope.transaction_names
|
93
|
+
self.fingerprint = scope.fingerprint
|
94
|
+
self.span = scope.span
|
95
|
+
end
|
96
|
+
|
97
|
+
# Updates the scope's data from the given options.
|
98
|
+
# @param contexts [Hash]
|
99
|
+
# @param extras [Hash]
|
100
|
+
# @param tags [Hash]
|
101
|
+
# @param user [Hash]
|
102
|
+
# @param level [String, Symbol]
|
103
|
+
# @param fingerprint [Array]
|
104
|
+
# @return [void]
|
105
|
+
def update_from_options(
|
106
|
+
contexts: nil,
|
107
|
+
extra: nil,
|
108
|
+
tags: nil,
|
109
|
+
user: nil,
|
110
|
+
level: nil,
|
111
|
+
fingerprint: nil
|
112
|
+
)
|
113
|
+
self.contexts.merge!(contexts) if contexts
|
114
|
+
self.extra.merge!(extra) if extra
|
115
|
+
self.tags.merge!(tags) if tags
|
116
|
+
self.user = user if user
|
117
|
+
self.level = level if level
|
118
|
+
self.fingerprint = fingerprint if fingerprint
|
119
|
+
end
|
120
|
+
|
121
|
+
# Sets the scope's rack_env attribute.
|
122
|
+
# @param env [Hash]
|
123
|
+
# @return [Hash]
|
124
|
+
def set_rack_env(env)
|
125
|
+
env = env || {}
|
126
|
+
@rack_env = env
|
127
|
+
end
|
128
|
+
|
129
|
+
# Sets the scope's span attribute.
|
130
|
+
# @param span [Span]
|
131
|
+
# @return [Span]
|
132
|
+
def set_span(span)
|
133
|
+
check_argument_type!(span, Span)
|
134
|
+
@span = span
|
135
|
+
end
|
136
|
+
|
137
|
+
# @!macro set_user
|
138
|
+
def set_user(user_hash)
|
139
|
+
check_argument_type!(user_hash, Hash)
|
140
|
+
@user = user_hash
|
141
|
+
end
|
142
|
+
|
143
|
+
# @!macro set_extras
|
144
|
+
def set_extras(extras_hash)
|
145
|
+
check_argument_type!(extras_hash, Hash)
|
146
|
+
@extra.merge!(extras_hash)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Adds a new key-value pair to current extras.
|
150
|
+
# @param key [String, Symbol]
|
151
|
+
# @param value [Object]
|
152
|
+
# @return [Hash]
|
153
|
+
def set_extra(key, value)
|
154
|
+
set_extras(key => value)
|
155
|
+
end
|
156
|
+
|
157
|
+
# @!macro set_tags
|
158
|
+
def set_tags(tags_hash)
|
159
|
+
check_argument_type!(tags_hash, Hash)
|
160
|
+
@tags.merge!(tags_hash)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Adds a new key-value pair to current tags.
|
164
|
+
# @param key [String, Symbol]
|
165
|
+
# @param value [Object]
|
166
|
+
# @return [Hash]
|
167
|
+
def set_tag(key, value)
|
168
|
+
set_tags(key => value)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Updates the scope's contexts attribute by merging with the old value.
|
172
|
+
# @param contexts [Hash]
|
173
|
+
# @return [Hash]
|
174
|
+
def set_contexts(contexts_hash)
|
175
|
+
check_argument_type!(contexts_hash, Hash)
|
176
|
+
@contexts.merge!(contexts_hash) do |key, old, new|
|
177
|
+
old.merge(new)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# @!macro set_context
|
182
|
+
def set_context(key, value)
|
183
|
+
check_argument_type!(value, Hash)
|
184
|
+
set_contexts(key => value)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Sets the scope's level attribute.
|
188
|
+
# @param level [String, Symbol]
|
189
|
+
# @return [void]
|
190
|
+
def set_level(level)
|
191
|
+
@level = level
|
192
|
+
end
|
193
|
+
|
194
|
+
# Appends a new transaction name to the scope.
|
195
|
+
# The "transaction" here does not refer to `Transaction` objects.
|
196
|
+
# @param transaction_name [String]
|
197
|
+
# @return [void]
|
198
|
+
def set_transaction_name(transaction_name)
|
199
|
+
@transaction_names << transaction_name
|
200
|
+
end
|
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
|
+
|
209
|
+
# Returns current transaction name.
|
210
|
+
# The "transaction" here does not refer to `Transaction` objects.
|
211
|
+
# @return [String, nil]
|
212
|
+
def transaction_name
|
213
|
+
@transaction_names.last
|
214
|
+
end
|
215
|
+
|
216
|
+
# Returns the associated Transaction object.
|
217
|
+
# @return [Transaction, nil]
|
218
|
+
def get_transaction
|
219
|
+
span.transaction if span
|
220
|
+
end
|
221
|
+
|
222
|
+
# Returns the associated Span object.
|
223
|
+
# @return [Span, nil]
|
224
|
+
def get_span
|
225
|
+
span
|
226
|
+
end
|
227
|
+
|
228
|
+
# Sets the scope's fingerprint attribute.
|
229
|
+
# @param fingerprint [Array]
|
230
|
+
# @return [Array]
|
231
|
+
def set_fingerprint(fingerprint)
|
232
|
+
check_argument_type!(fingerprint, Array)
|
233
|
+
|
234
|
+
@fingerprint = fingerprint
|
235
|
+
end
|
236
|
+
|
237
|
+
# Adds a new event processor [Proc] to the scope.
|
238
|
+
# @param block [Proc]
|
239
|
+
# @return [void]
|
240
|
+
def add_event_processor(&block)
|
241
|
+
@event_processors << block
|
242
|
+
end
|
243
|
+
|
244
|
+
protected
|
245
|
+
|
246
|
+
# for duplicating scopes internally
|
247
|
+
attr_writer(*ATTRIBUTES)
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
def set_default_value
|
252
|
+
@contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
|
253
|
+
@extra = {}
|
254
|
+
@tags = {}
|
255
|
+
@user = {}
|
256
|
+
@level = :error
|
257
|
+
@fingerprint = []
|
258
|
+
@transaction_names = []
|
259
|
+
@event_processors = []
|
260
|
+
@rack_env = {}
|
261
|
+
@span = nil
|
262
|
+
@session = nil
|
263
|
+
set_new_breadcrumb_buffer
|
264
|
+
end
|
265
|
+
|
266
|
+
def set_new_breadcrumb_buffer
|
267
|
+
@breadcrumbs = BreadcrumbBuffer.new(@max_breadcrumbs)
|
268
|
+
end
|
269
|
+
|
270
|
+
class << self
|
271
|
+
# @return [Hash]
|
272
|
+
def os_context
|
273
|
+
@os_context ||=
|
274
|
+
begin
|
275
|
+
uname = Etc.uname
|
276
|
+
{
|
277
|
+
name: uname[:sysname] || RbConfig::CONFIG["host_os"],
|
278
|
+
version: uname[:version],
|
279
|
+
build: uname[:release],
|
280
|
+
kernel_version: uname[:version]
|
281
|
+
}
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# @return [Hash]
|
286
|
+
def runtime_context
|
287
|
+
@runtime_context ||= {
|
288
|
+
name: RbConfig::CONFIG["ruby_install_name"],
|
289
|
+
version: RUBY_DESCRIPTION || Sentry.sys_command("ruby -v")
|
290
|
+
}
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
end
|
295
|
+
end
|
@@ -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,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
class SessionFlusher
|
5
|
+
include LoggingHelper
|
6
|
+
|
7
|
+
FLUSH_INTERVAL = 60
|
8
|
+
|
9
|
+
def initialize(configuration, client)
|
10
|
+
@thread = nil
|
11
|
+
@exited = false
|
12
|
+
@client = client
|
13
|
+
@pending_aggregates = {}
|
14
|
+
@release = configuration.release
|
15
|
+
@environment = configuration.environment
|
16
|
+
@logger = configuration.logger
|
17
|
+
|
18
|
+
log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
|
19
|
+
end
|
20
|
+
|
21
|
+
def flush
|
22
|
+
return if @pending_aggregates.empty?
|
23
|
+
envelope = pending_envelope
|
24
|
+
|
25
|
+
Sentry.background_worker.perform do
|
26
|
+
@client.transport.send_envelope(envelope)
|
27
|
+
end
|
28
|
+
|
29
|
+
@pending_aggregates = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_session(session)
|
33
|
+
return if @exited
|
34
|
+
return unless @release
|
35
|
+
|
36
|
+
begin
|
37
|
+
ensure_thread
|
38
|
+
rescue ThreadError
|
39
|
+
log_debug("Session flusher thread creation failed")
|
40
|
+
@exited = true
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
return unless Session::AGGREGATE_STATUSES.include?(session.status)
|
45
|
+
@pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
|
46
|
+
@pending_aggregates[session.aggregation_key][session.status] += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
def kill
|
50
|
+
log_debug("Killing session flusher")
|
51
|
+
|
52
|
+
@exited = true
|
53
|
+
@thread&.kill
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def init_aggregates(aggregation_key)
|
59
|
+
aggregates = { started: aggregation_key.iso8601 }
|
60
|
+
Session::AGGREGATE_STATUSES.each { |k| aggregates[k] = 0 }
|
61
|
+
aggregates
|
62
|
+
end
|
63
|
+
|
64
|
+
def pending_envelope
|
65
|
+
envelope = Envelope.new
|
66
|
+
|
67
|
+
header = { type: 'sessions' }
|
68
|
+
payload = { attrs: attrs, aggregates: @pending_aggregates.values }
|
69
|
+
|
70
|
+
envelope.add_item(header, payload)
|
71
|
+
envelope
|
72
|
+
end
|
73
|
+
|
74
|
+
def attrs
|
75
|
+
{ release: @release, environment: @environment }
|
76
|
+
end
|
77
|
+
|
78
|
+
def ensure_thread
|
79
|
+
return if @thread&.alive?
|
80
|
+
|
81
|
+
@thread = Thread.new do
|
82
|
+
loop do
|
83
|
+
sleep(FLUSH_INTERVAL)
|
84
|
+
flush
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|