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