airbrake-ruby 3.2.2-java
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 +7 -0
- data/lib/airbrake-ruby.rb +554 -0
- data/lib/airbrake-ruby/async_sender.rb +119 -0
- data/lib/airbrake-ruby/backtrace.rb +194 -0
- data/lib/airbrake-ruby/code_hunk.rb +53 -0
- data/lib/airbrake-ruby/config.rb +238 -0
- data/lib/airbrake-ruby/config/validator.rb +63 -0
- data/lib/airbrake-ruby/deploy_notifier.rb +47 -0
- data/lib/airbrake-ruby/file_cache.rb +48 -0
- data/lib/airbrake-ruby/filter_chain.rb +95 -0
- data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
- data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +45 -0
- data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +90 -0
- data/lib/airbrake-ruby/filters/git_repository_filter.rb +42 -0
- data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
- data/lib/airbrake-ruby/filters/keys_blacklist.rb +50 -0
- data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
- data/lib/airbrake-ruby/filters/keys_whitelist.rb +49 -0
- data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
- data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
- data/lib/airbrake-ruby/hash_keyable.rb +37 -0
- data/lib/airbrake-ruby/ignorable.rb +44 -0
- data/lib/airbrake-ruby/nested_exception.rb +39 -0
- data/lib/airbrake-ruby/notice.rb +165 -0
- data/lib/airbrake-ruby/notice_notifier.rb +228 -0
- data/lib/airbrake-ruby/performance_notifier.rb +161 -0
- data/lib/airbrake-ruby/promise.rb +99 -0
- data/lib/airbrake-ruby/response.rb +71 -0
- data/lib/airbrake-ruby/stat.rb +56 -0
- data/lib/airbrake-ruby/sync_sender.rb +111 -0
- data/lib/airbrake-ruby/tdigest.rb +393 -0
- data/lib/airbrake-ruby/time_truncate.rb +17 -0
- data/lib/airbrake-ruby/truncator.rb +115 -0
- data/lib/airbrake-ruby/version.rb +6 -0
- data/spec/airbrake_spec.rb +171 -0
- data/spec/async_sender_spec.rb +154 -0
- data/spec/backtrace_spec.rb +438 -0
- data/spec/code_hunk_spec.rb +118 -0
- data/spec/config/validator_spec.rb +189 -0
- data/spec/config_spec.rb +281 -0
- data/spec/deploy_notifier_spec.rb +41 -0
- data/spec/file_cache.rb +36 -0
- data/spec/filter_chain_spec.rb +83 -0
- data/spec/filters/context_filter_spec.rb +25 -0
- data/spec/filters/dependency_filter_spec.rb +14 -0
- data/spec/filters/exception_attributes_filter_spec.rb +63 -0
- data/spec/filters/gem_root_filter_spec.rb +44 -0
- data/spec/filters/git_last_checkout_filter_spec.rb +48 -0
- data/spec/filters/git_repository_filter.rb +53 -0
- data/spec/filters/git_revision_filter_spec.rb +126 -0
- data/spec/filters/keys_blacklist_spec.rb +236 -0
- data/spec/filters/keys_whitelist_spec.rb +205 -0
- data/spec/filters/root_directory_filter_spec.rb +42 -0
- data/spec/filters/sql_filter_spec.rb +219 -0
- data/spec/filters/system_exit_filter_spec.rb +14 -0
- data/spec/filters/thread_filter_spec.rb +279 -0
- data/spec/fixtures/notroot.txt +7 -0
- data/spec/fixtures/project_root/code.rb +221 -0
- data/spec/fixtures/project_root/empty_file.rb +0 -0
- data/spec/fixtures/project_root/long_line.txt +1 -0
- data/spec/fixtures/project_root/short_file.rb +3 -0
- data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
- data/spec/helpers.rb +9 -0
- data/spec/ignorable_spec.rb +14 -0
- data/spec/nested_exception_spec.rb +75 -0
- data/spec/notice_notifier_spec.rb +436 -0
- data/spec/notice_notifier_spec/options_spec.rb +266 -0
- data/spec/notice_spec.rb +297 -0
- data/spec/performance_notifier_spec.rb +287 -0
- data/spec/promise_spec.rb +165 -0
- data/spec/response_spec.rb +82 -0
- data/spec/spec_helper.rb +102 -0
- data/spec/stat_spec.rb +35 -0
- data/spec/sync_sender_spec.rb +140 -0
- data/spec/tdigest_spec.rb +230 -0
- data/spec/time_truncate_spec.rb +13 -0
- data/spec/truncator_spec.rb +238 -0
- metadata +278 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Responsible for sending notices to Airbrake asynchronously. The class
|
3
|
+
# supports an unlimited number of worker threads and an unlimited queue size
|
4
|
+
# (both values are configurable).
|
5
|
+
#
|
6
|
+
# @see SyncSender
|
7
|
+
# @api private
|
8
|
+
# @since v1.0.0
|
9
|
+
class AsyncSender
|
10
|
+
# @param [Airbrake::Config] config
|
11
|
+
def initialize(config)
|
12
|
+
@config = config
|
13
|
+
@unsent = SizedQueue.new(config.queue_size)
|
14
|
+
@sender = SyncSender.new(config)
|
15
|
+
@closed = false
|
16
|
+
@workers = ThreadGroup.new
|
17
|
+
@mutex = Mutex.new
|
18
|
+
@pid = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# Asynchronously sends a notice to Airbrake.
|
22
|
+
#
|
23
|
+
# @param [Airbrake::Notice] notice A notice that was generated by the
|
24
|
+
# library
|
25
|
+
# @return [Airbrake::Promise]
|
26
|
+
def send(notice, promise)
|
27
|
+
return will_not_deliver(notice) if @unsent.size >= @unsent.max
|
28
|
+
|
29
|
+
@unsent << [notice, promise]
|
30
|
+
promise
|
31
|
+
end
|
32
|
+
|
33
|
+
# Closes the instance making it a no-op (it shut downs all worker
|
34
|
+
# threads). Before closing, waits on all unsent notices to be sent.
|
35
|
+
#
|
36
|
+
# @return [void]
|
37
|
+
# @raise [Airbrake::Error] when invoked more than one time
|
38
|
+
def close
|
39
|
+
threads = @mutex.synchronize do
|
40
|
+
raise Airbrake::Error, 'attempted to close already closed sender' if closed?
|
41
|
+
|
42
|
+
unless @unsent.empty?
|
43
|
+
msg = "#{LOG_LABEL} waiting to send #{@unsent.size} unsent notice(s)..."
|
44
|
+
@config.logger.debug(msg + ' (Ctrl-C to abort)')
|
45
|
+
end
|
46
|
+
|
47
|
+
@config.workers.times { @unsent << [:stop, Airbrake::Promise.new] }
|
48
|
+
@closed = true
|
49
|
+
@workers.list.dup
|
50
|
+
end
|
51
|
+
|
52
|
+
threads.each(&:join)
|
53
|
+
@config.logger.debug("#{LOG_LABEL} closed")
|
54
|
+
end
|
55
|
+
|
56
|
+
# Checks whether the sender is closed and thus usable.
|
57
|
+
# @return [Boolean]
|
58
|
+
def closed?
|
59
|
+
@closed
|
60
|
+
end
|
61
|
+
|
62
|
+
# Checks if an active sender has any workers. A sender doesn't have any
|
63
|
+
# workers only in two cases: when it was closed or when all workers
|
64
|
+
# crashed. An *active* sender doesn't have any workers only when something
|
65
|
+
# went wrong.
|
66
|
+
#
|
67
|
+
# Workers are expected to crash when you +fork+ the process the workers are
|
68
|
+
# living in. In this case we detect a +fork+ and try to revive them here.
|
69
|
+
#
|
70
|
+
# Another possible scenario that crashes workers is when you close the
|
71
|
+
# instance on +at_exit+, but some other +at_exit+ hook prevents the process
|
72
|
+
# from exiting.
|
73
|
+
#
|
74
|
+
# @return [Boolean] true if an instance wasn't closed, but has no workers
|
75
|
+
# @see https://goo.gl/oydz8h Example of at_exit that prevents exit
|
76
|
+
def has_workers?
|
77
|
+
@mutex.synchronize do
|
78
|
+
return false if @closed
|
79
|
+
|
80
|
+
if @pid != Process.pid && @workers.list.empty?
|
81
|
+
@pid = Process.pid
|
82
|
+
spawn_workers
|
83
|
+
end
|
84
|
+
|
85
|
+
!@closed && @workers.list.any?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def spawn_workers
|
92
|
+
@workers = ThreadGroup.new
|
93
|
+
@config.workers.times { @workers.add(spawn_worker) }
|
94
|
+
@workers.enclose
|
95
|
+
end
|
96
|
+
|
97
|
+
def spawn_worker
|
98
|
+
Thread.new do
|
99
|
+
while (message = @unsent.pop)
|
100
|
+
break if message.first == :stop
|
101
|
+
@sender.send(*message)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def will_not_deliver(notice)
|
107
|
+
backtrace = notice[:errors][0][:backtrace].map do |line|
|
108
|
+
"#{line[:file]}:#{line[:line]} in `#{line[:function]}'"
|
109
|
+
end
|
110
|
+
@config.logger.error(
|
111
|
+
"#{LOG_LABEL} AsyncSender has reached its capacity of " \
|
112
|
+
"#{@unsent.max} and the following notice will not be delivered " \
|
113
|
+
"Error: #{notice[:errors][0][:type]} - #{notice[:errors][0][:message]}\n" \
|
114
|
+
"Backtrace: \n" + backtrace.join("\n")
|
115
|
+
)
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Represents a cross-Ruby backtrace from exceptions (including JRuby Java
|
3
|
+
# exceptions). Provides information about stack frames (such as line number,
|
4
|
+
# file and method) in convenient for Airbrake format.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# begin
|
8
|
+
# raise 'Oops!'
|
9
|
+
# rescue
|
10
|
+
# Backtrace.parse($!, Logger.new(STDOUT))
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
# @since v1.0.0
|
15
|
+
module Backtrace
|
16
|
+
module Patterns
|
17
|
+
# @return [Regexp] the pattern that matches standard Ruby stack frames,
|
18
|
+
# such as ./spec/notice_spec.rb:43:in `block (3 levels) in <top (required)>'
|
19
|
+
RUBY = %r{\A
|
20
|
+
(?<file>.+) # Matches './spec/notice_spec.rb'
|
21
|
+
:
|
22
|
+
(?<line>\d+) # Matches '43'
|
23
|
+
:in\s
|
24
|
+
`(?<function>.*)' # Matches "`block (3 levels) in <top (required)>'"
|
25
|
+
\z}x
|
26
|
+
|
27
|
+
# @return [Regexp] the pattern that matches JRuby Java stack frames, such
|
28
|
+
# as org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105)
|
29
|
+
JAVA = %r{\A
|
30
|
+
(?<function>.+) # Matches 'org.jruby.ast.NewlineNode.interpret'
|
31
|
+
\(
|
32
|
+
(?<file>
|
33
|
+
(?:uri:classloader:/.+(?=:)) # Matches '/META-INF/jruby.home/protocol.rb'
|
34
|
+
|
|
35
|
+
(?:uri_3a_classloader_3a_.+(?=:)) # Matches 'uri_3a_classloader_3a_/gems/...'
|
36
|
+
|
|
37
|
+
[^:]+ # Matches 'NewlineNode.java'
|
38
|
+
)
|
39
|
+
:?
|
40
|
+
(?<line>\d+)? # Matches '105'
|
41
|
+
\)
|
42
|
+
\z}x
|
43
|
+
|
44
|
+
# @return [Regexp] the pattern that tries to assume what a generic stack
|
45
|
+
# frame might look like, when exception's backtrace is set manually.
|
46
|
+
GENERIC = %r{\A
|
47
|
+
(?:from\s)?
|
48
|
+
(?<file>.+) # Matches '/foo/bar/baz.ext'
|
49
|
+
:
|
50
|
+
(?<line>\d+)? # Matches '43' or nothing
|
51
|
+
(?:
|
52
|
+
in\s`(?<function>.+)' # Matches "in `func'"
|
53
|
+
|
|
54
|
+
:in\s(?<function>.+) # Matches ":in func"
|
55
|
+
)? # ... or nothing
|
56
|
+
\z}x
|
57
|
+
|
58
|
+
# @return [Regexp] the pattern that matches exceptions from PL/SQL such as
|
59
|
+
# ORA-06512: at "STORE.LI_LICENSES_PACK", line 1945
|
60
|
+
# @note This is raised by https://github.com/kubo/ruby-oci8
|
61
|
+
OCI = /\A
|
62
|
+
(?:
|
63
|
+
ORA-\d{5}
|
64
|
+
:\sat\s
|
65
|
+
(?:"(?<function>.+)",\s)?
|
66
|
+
line\s(?<line>\d+)
|
67
|
+
|
|
68
|
+
#{GENERIC}
|
69
|
+
)
|
70
|
+
\z/x
|
71
|
+
|
72
|
+
# @return [Regexp] the pattern that matches CoffeeScript backtraces
|
73
|
+
# usually coming from Rails & ExecJS
|
74
|
+
EXECJS = /\A
|
75
|
+
(?:
|
76
|
+
# Matches 'compile ((execjs):6692:19)'
|
77
|
+
(?<function>.+)\s\((?<file>.+):(?<line>\d+):\d+\)
|
78
|
+
|
|
79
|
+
# Matches 'bootstrap_node.js:467:3'
|
80
|
+
(?<file>.+):(?<line>\d+):\d+(?<function>)
|
81
|
+
|
|
82
|
+
# Matches the Ruby part of the backtrace
|
83
|
+
#{RUBY}
|
84
|
+
)
|
85
|
+
\z/x
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [Integer] how many first frames should include code hunks
|
89
|
+
CODE_FRAME_LIMIT = 10
|
90
|
+
|
91
|
+
# Parses an exception's backtrace.
|
92
|
+
#
|
93
|
+
# @param [Exception] exception The exception, which contains a backtrace to
|
94
|
+
# parse
|
95
|
+
# @return [Array<Hash{Symbol=>String,Integer}>] the parsed backtrace
|
96
|
+
def self.parse(config, exception)
|
97
|
+
return [] if exception.backtrace.nil? || exception.backtrace.none?
|
98
|
+
parse_backtrace(config, exception)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Checks whether the given exception was generated by JRuby's VM.
|
102
|
+
#
|
103
|
+
# @param [Exception] exception
|
104
|
+
# @return [Boolean]
|
105
|
+
def self.java_exception?(exception)
|
106
|
+
if defined?(Java::JavaLang::Throwable) &&
|
107
|
+
exception.is_a?(Java::JavaLang::Throwable)
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
|
111
|
+
return false unless exception.respond_to?(:backtrace)
|
112
|
+
|
113
|
+
(Patterns::JAVA =~ exception.backtrace.first) != nil
|
114
|
+
end
|
115
|
+
|
116
|
+
class << self
|
117
|
+
private
|
118
|
+
|
119
|
+
def best_regexp_for(exception)
|
120
|
+
if java_exception?(exception)
|
121
|
+
Patterns::JAVA
|
122
|
+
elsif oci_exception?(exception)
|
123
|
+
Patterns::OCI
|
124
|
+
elsif execjs_exception?(exception)
|
125
|
+
Patterns::EXECJS
|
126
|
+
else
|
127
|
+
Patterns::RUBY
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def oci_exception?(exception)
|
132
|
+
defined?(OCIError) && exception.is_a?(OCIError)
|
133
|
+
end
|
134
|
+
|
135
|
+
def execjs_exception?(exception)
|
136
|
+
return false unless defined?(ExecJS::RuntimeError)
|
137
|
+
return true if exception.is_a?(ExecJS::RuntimeError)
|
138
|
+
return true if exception.cause && exception.cause.is_a?(ExecJS::RuntimeError)
|
139
|
+
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
def stack_frame(config, regexp, stackframe)
|
144
|
+
if (match = match_frame(regexp, stackframe))
|
145
|
+
return {
|
146
|
+
file: match[:file],
|
147
|
+
line: (Integer(match[:line]) if match[:line]),
|
148
|
+
function: match[:function]
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
config.logger.error(
|
153
|
+
"can't parse '#{stackframe}' (please file an issue so we can fix " \
|
154
|
+
"it: https://github.com/airbrake/airbrake-ruby/issues/new)"
|
155
|
+
)
|
156
|
+
{ file: nil, line: nil, function: stackframe }
|
157
|
+
end
|
158
|
+
|
159
|
+
def match_frame(regexp, stackframe)
|
160
|
+
match = regexp.match(stackframe)
|
161
|
+
return match if match
|
162
|
+
|
163
|
+
Patterns::GENERIC.match(stackframe)
|
164
|
+
end
|
165
|
+
|
166
|
+
def parse_backtrace(config, exception)
|
167
|
+
regexp = best_regexp_for(exception)
|
168
|
+
root_directory = config.root_directory.to_s
|
169
|
+
|
170
|
+
exception.backtrace.map.with_index do |stackframe, i|
|
171
|
+
frame = stack_frame(config, regexp, stackframe)
|
172
|
+
next(frame) if !config.code_hunks || frame[:file].nil?
|
173
|
+
|
174
|
+
if !root_directory.empty?
|
175
|
+
populate_code(config, frame) if frame_in_root?(frame, root_directory)
|
176
|
+
elsif i < CODE_FRAME_LIMIT
|
177
|
+
populate_code(config, frame)
|
178
|
+
end
|
179
|
+
|
180
|
+
frame
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def populate_code(config, frame)
|
185
|
+
code = Airbrake::CodeHunk.new(config).get(frame[:file], frame[:line])
|
186
|
+
frame[:code] = code if code
|
187
|
+
end
|
188
|
+
|
189
|
+
def frame_in_root?(frame, root_directory)
|
190
|
+
frame[:file].start_with?(root_directory) && frame[:file] !~ %r{vendor/bundle}
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Represents a small hunk of code consisting of a base line and a couple lines
|
3
|
+
# around it
|
4
|
+
# @api private
|
5
|
+
class CodeHunk
|
6
|
+
# @return [Integer] the maximum length of a line
|
7
|
+
MAX_LINE_LEN = 200
|
8
|
+
|
9
|
+
# @return [Integer] how many lines should be read around the base line
|
10
|
+
NLINES = 2
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
@config = config
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [String] file The file to read
|
17
|
+
# @param [Integer] line The base line in the file
|
18
|
+
# @return [Hash{Integer=>String}, nil] lines of code around the base line
|
19
|
+
def get(file, line)
|
20
|
+
return unless File.exist?(file)
|
21
|
+
return unless line
|
22
|
+
|
23
|
+
lines = get_lines(file, [line - NLINES, 1].max, line + NLINES) || {}
|
24
|
+
return { 1 => '' } if lines.empty?
|
25
|
+
|
26
|
+
lines
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def get_from_cache(file)
|
32
|
+
Airbrake::FileCache[file] ||= File.foreach(file)
|
33
|
+
rescue StandardError => ex
|
34
|
+
@config.logger.error(
|
35
|
+
"#{self.class.name}: can't read code hunk for #{file}: #{ex}"
|
36
|
+
)
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_lines(file, start_line, end_line)
|
41
|
+
return unless (cached_file = get_from_cache(file))
|
42
|
+
|
43
|
+
lines = {}
|
44
|
+
cached_file.with_index(1) do |l, i|
|
45
|
+
next if i < start_line
|
46
|
+
break if i > end_line
|
47
|
+
|
48
|
+
lines[i] = l[0...MAX_LINE_LEN].rstrip
|
49
|
+
end
|
50
|
+
lines
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Represents the Airbrake config. A config contains all the options that you
|
3
|
+
# can use to configure an Airbrake instance.
|
4
|
+
#
|
5
|
+
# @api public
|
6
|
+
# @since v1.0.0
|
7
|
+
class Config
|
8
|
+
# @return [Integer] the project identificator. This value *must* be set.
|
9
|
+
# @api public
|
10
|
+
attr_accessor :project_id
|
11
|
+
|
12
|
+
# @return [String] the project key. This value *must* be set.
|
13
|
+
# @api public
|
14
|
+
attr_accessor :project_key
|
15
|
+
|
16
|
+
# @return [Hash] the proxy parameters such as (:host, :port, :user and
|
17
|
+
# :password)
|
18
|
+
# @api public
|
19
|
+
attr_accessor :proxy
|
20
|
+
|
21
|
+
# @return [Logger] the default logger used for debug output
|
22
|
+
# @api public
|
23
|
+
attr_reader :logger
|
24
|
+
|
25
|
+
# @return [String] the version of the user's application
|
26
|
+
# @api public
|
27
|
+
attr_accessor :app_version
|
28
|
+
|
29
|
+
# @return [Hash{String=>String}] arbitrary versions that your app wants to
|
30
|
+
# track
|
31
|
+
# @api public
|
32
|
+
# @since v2.10.0
|
33
|
+
attr_accessor :versions
|
34
|
+
|
35
|
+
# @return [Integer] the max number of notices that can be queued up
|
36
|
+
# @api public
|
37
|
+
attr_accessor :queue_size
|
38
|
+
|
39
|
+
# @return [Integer] the number of worker threads that process the notice
|
40
|
+
# queue
|
41
|
+
# @api public
|
42
|
+
attr_accessor :workers
|
43
|
+
|
44
|
+
# @return [String] the host, which provides the API endpoint to which
|
45
|
+
# exceptions should be sent
|
46
|
+
# @api public
|
47
|
+
attr_accessor :host
|
48
|
+
|
49
|
+
# @return [String, Pathname] the working directory of your project
|
50
|
+
# @api public
|
51
|
+
attr_accessor :root_directory
|
52
|
+
|
53
|
+
# @return [String, Symbol] the environment the application is running in
|
54
|
+
# @api public
|
55
|
+
attr_accessor :environment
|
56
|
+
|
57
|
+
# @return [Array<String,Symbol,Regexp>] the array of environments that
|
58
|
+
# forbids sending exceptions when the application is running in them.
|
59
|
+
# Other possible environments not listed in the array will allow sending
|
60
|
+
# occurring exceptions.
|
61
|
+
# @api public
|
62
|
+
attr_accessor :ignore_environments
|
63
|
+
|
64
|
+
# @return [Integer] The HTTP timeout in seconds.
|
65
|
+
# @api public
|
66
|
+
attr_accessor :timeout
|
67
|
+
|
68
|
+
# @return [Array<String, Symbol, Regexp>] the keys, which should be
|
69
|
+
# filtered
|
70
|
+
# @api public
|
71
|
+
# @since v1.2.0
|
72
|
+
attr_accessor :blacklist_keys
|
73
|
+
|
74
|
+
# @return [Array<String, Symbol, Regexp>] the keys, which shouldn't be
|
75
|
+
# filtered
|
76
|
+
# @api public
|
77
|
+
# @since v1.2.0
|
78
|
+
attr_accessor :whitelist_keys
|
79
|
+
|
80
|
+
# @return [Boolean] true if the library should attach code hunks to each
|
81
|
+
# frame in a backtrace, false otherwise
|
82
|
+
# @api public
|
83
|
+
# @since v2.5.0
|
84
|
+
attr_accessor :code_hunks
|
85
|
+
|
86
|
+
# @return [Boolean] true if the library should send performance stats
|
87
|
+
# information to Airbrake (routes, SQL queries), false otherwise
|
88
|
+
# @api public
|
89
|
+
# @since v3.2.0
|
90
|
+
attr_accessor :performance_stats
|
91
|
+
|
92
|
+
# @return [Integer] how many seconds to wait before sending collected route
|
93
|
+
# stats
|
94
|
+
# @api public
|
95
|
+
# @since v3.2.0
|
96
|
+
attr_accessor :performance_stats_flush_period
|
97
|
+
|
98
|
+
# @param [Hash{Symbol=>Object}] user_config the hash to be used to build the
|
99
|
+
# config
|
100
|
+
# rubocop:disable Metrics/AbcSize
|
101
|
+
def initialize(user_config = {})
|
102
|
+
@validator = Config::Validator.new(self)
|
103
|
+
|
104
|
+
self.proxy = {}
|
105
|
+
self.queue_size = 100
|
106
|
+
self.workers = 1
|
107
|
+
self.code_hunks = true
|
108
|
+
|
109
|
+
self.logger = Logger.new(STDOUT)
|
110
|
+
logger.level = Logger::WARN
|
111
|
+
|
112
|
+
self.project_id = user_config[:project_id]
|
113
|
+
self.project_key = user_config[:project_key]
|
114
|
+
self.host = 'https://api.airbrake.io'
|
115
|
+
|
116
|
+
self.ignore_environments = []
|
117
|
+
|
118
|
+
self.timeout = user_config[:timeout]
|
119
|
+
|
120
|
+
self.blacklist_keys = []
|
121
|
+
self.whitelist_keys = []
|
122
|
+
|
123
|
+
self.root_directory = File.realpath(
|
124
|
+
(defined?(Bundler) && Bundler.root) ||
|
125
|
+
Dir.pwd
|
126
|
+
)
|
127
|
+
|
128
|
+
self.versions = {}
|
129
|
+
self.performance_stats = false
|
130
|
+
self.performance_stats_flush_period = 15
|
131
|
+
|
132
|
+
merge(user_config)
|
133
|
+
end
|
134
|
+
# rubocop:enable Metrics/AbcSize
|
135
|
+
|
136
|
+
# The full URL to the Airbrake Notice API. Based on the +:host+ option.
|
137
|
+
# @return [URI] the endpoint address
|
138
|
+
def endpoint
|
139
|
+
@endpoint ||=
|
140
|
+
begin
|
141
|
+
self.host = ('https://' << host) if host !~ %r{\Ahttps?://}
|
142
|
+
api = "api/v3/projects/#{project_id}/notices"
|
143
|
+
URI.join(host, api)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Sets the logger. Never allows to assign `nil` as the logger.
|
148
|
+
# @return [Logger] the logger
|
149
|
+
def logger=(logger)
|
150
|
+
@logger = logger || @logger
|
151
|
+
end
|
152
|
+
|
153
|
+
# Merges the given +config_hash+ with itself.
|
154
|
+
#
|
155
|
+
# @example
|
156
|
+
# config.merge(host: 'localhost:8080')
|
157
|
+
#
|
158
|
+
# @return [self] the merged config
|
159
|
+
def merge(config_hash)
|
160
|
+
config_hash.each_pair { |option, value| set_option(option, value) }
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
# @return [Boolean] true if the config meets the requirements, false
|
165
|
+
# otherwise
|
166
|
+
def valid?
|
167
|
+
return true if ignored_environment?
|
168
|
+
|
169
|
+
return false unless @validator.valid_project_id?
|
170
|
+
return false unless @validator.valid_project_key?
|
171
|
+
return false unless @validator.valid_environment?
|
172
|
+
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
def validation_error_message
|
177
|
+
@validator.error_message
|
178
|
+
end
|
179
|
+
|
180
|
+
# @return [Boolean] true if the config ignores current environment, false
|
181
|
+
# otherwise
|
182
|
+
def ignored_environment?
|
183
|
+
if ignore_environments.any? && environment.nil?
|
184
|
+
logger.warn("#{LOG_LABEL} the 'environment' option is not set, " \
|
185
|
+
"'ignore_environments' has no effect")
|
186
|
+
end
|
187
|
+
|
188
|
+
env = environment.to_s
|
189
|
+
ignore_environments.any? do |pattern|
|
190
|
+
if pattern.is_a?(Regexp)
|
191
|
+
env.match(pattern)
|
192
|
+
else
|
193
|
+
env == pattern.to_s
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def route_stats
|
199
|
+
logger.warn(
|
200
|
+
"#{LOG_LABEL} the 'route_stats' option is deprecated. " \
|
201
|
+
"Use 'performance_stats' instead"
|
202
|
+
)
|
203
|
+
@performance_stats
|
204
|
+
end
|
205
|
+
|
206
|
+
def route_stats=(value)
|
207
|
+
logger.warn(
|
208
|
+
"#{LOG_LABEL} the 'route_stats' option is deprecated. " \
|
209
|
+
"Use 'performance_stats_flush_period' instead"
|
210
|
+
)
|
211
|
+
@performance_stats = value
|
212
|
+
end
|
213
|
+
|
214
|
+
def route_stats_flush_period
|
215
|
+
logger.warn(
|
216
|
+
"#{LOG_LABEL} the 'route_stats_flush_period' option is deprecated. " \
|
217
|
+
"Use 'performance_stats_flush_period' instead"
|
218
|
+
)
|
219
|
+
@performance_stats_flush_period
|
220
|
+
end
|
221
|
+
|
222
|
+
def route_stats_flush_period=(value)
|
223
|
+
logger.warn(
|
224
|
+
"#{LOG_LABEL} the 'route_stats_flush_period' option is deprecated. " \
|
225
|
+
"Use 'performance_stats' instead"
|
226
|
+
)
|
227
|
+
@performance_stats_flush_period = value
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def set_option(option, value)
|
233
|
+
__send__("#{option}=", value)
|
234
|
+
rescue NoMethodError
|
235
|
+
raise Airbrake::Error, "unknown option '#{option}'"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|