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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +554 -0
  3. data/lib/airbrake-ruby/async_sender.rb +119 -0
  4. data/lib/airbrake-ruby/backtrace.rb +194 -0
  5. data/lib/airbrake-ruby/code_hunk.rb +53 -0
  6. data/lib/airbrake-ruby/config.rb +238 -0
  7. data/lib/airbrake-ruby/config/validator.rb +63 -0
  8. data/lib/airbrake-ruby/deploy_notifier.rb +47 -0
  9. data/lib/airbrake-ruby/file_cache.rb +48 -0
  10. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  11. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  12. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  13. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +45 -0
  14. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  15. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +90 -0
  16. data/lib/airbrake-ruby/filters/git_repository_filter.rb +42 -0
  17. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  18. data/lib/airbrake-ruby/filters/keys_blacklist.rb +50 -0
  19. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  20. data/lib/airbrake-ruby/filters/keys_whitelist.rb +49 -0
  21. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  22. data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
  23. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  24. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  25. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  26. data/lib/airbrake-ruby/ignorable.rb +44 -0
  27. data/lib/airbrake-ruby/nested_exception.rb +39 -0
  28. data/lib/airbrake-ruby/notice.rb +165 -0
  29. data/lib/airbrake-ruby/notice_notifier.rb +228 -0
  30. data/lib/airbrake-ruby/performance_notifier.rb +161 -0
  31. data/lib/airbrake-ruby/promise.rb +99 -0
  32. data/lib/airbrake-ruby/response.rb +71 -0
  33. data/lib/airbrake-ruby/stat.rb +56 -0
  34. data/lib/airbrake-ruby/sync_sender.rb +111 -0
  35. data/lib/airbrake-ruby/tdigest.rb +393 -0
  36. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  37. data/lib/airbrake-ruby/truncator.rb +115 -0
  38. data/lib/airbrake-ruby/version.rb +6 -0
  39. data/spec/airbrake_spec.rb +171 -0
  40. data/spec/async_sender_spec.rb +154 -0
  41. data/spec/backtrace_spec.rb +438 -0
  42. data/spec/code_hunk_spec.rb +118 -0
  43. data/spec/config/validator_spec.rb +189 -0
  44. data/spec/config_spec.rb +281 -0
  45. data/spec/deploy_notifier_spec.rb +41 -0
  46. data/spec/file_cache.rb +36 -0
  47. data/spec/filter_chain_spec.rb +83 -0
  48. data/spec/filters/context_filter_spec.rb +25 -0
  49. data/spec/filters/dependency_filter_spec.rb +14 -0
  50. data/spec/filters/exception_attributes_filter_spec.rb +63 -0
  51. data/spec/filters/gem_root_filter_spec.rb +44 -0
  52. data/spec/filters/git_last_checkout_filter_spec.rb +48 -0
  53. data/spec/filters/git_repository_filter.rb +53 -0
  54. data/spec/filters/git_revision_filter_spec.rb +126 -0
  55. data/spec/filters/keys_blacklist_spec.rb +236 -0
  56. data/spec/filters/keys_whitelist_spec.rb +205 -0
  57. data/spec/filters/root_directory_filter_spec.rb +42 -0
  58. data/spec/filters/sql_filter_spec.rb +219 -0
  59. data/spec/filters/system_exit_filter_spec.rb +14 -0
  60. data/spec/filters/thread_filter_spec.rb +279 -0
  61. data/spec/fixtures/notroot.txt +7 -0
  62. data/spec/fixtures/project_root/code.rb +221 -0
  63. data/spec/fixtures/project_root/empty_file.rb +0 -0
  64. data/spec/fixtures/project_root/long_line.txt +1 -0
  65. data/spec/fixtures/project_root/short_file.rb +3 -0
  66. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  67. data/spec/helpers.rb +9 -0
  68. data/spec/ignorable_spec.rb +14 -0
  69. data/spec/nested_exception_spec.rb +75 -0
  70. data/spec/notice_notifier_spec.rb +436 -0
  71. data/spec/notice_notifier_spec/options_spec.rb +266 -0
  72. data/spec/notice_spec.rb +297 -0
  73. data/spec/performance_notifier_spec.rb +287 -0
  74. data/spec/promise_spec.rb +165 -0
  75. data/spec/response_spec.rb +82 -0
  76. data/spec/spec_helper.rb +102 -0
  77. data/spec/stat_spec.rb +35 -0
  78. data/spec/sync_sender_spec.rb +140 -0
  79. data/spec/tdigest_spec.rb +230 -0
  80. data/spec/time_truncate_spec.rb +13 -0
  81. data/spec/truncator_spec.rb +238 -0
  82. metadata +278 -0
@@ -0,0 +1,63 @@
1
+ module Airbrake
2
+ class Config
3
+ # Validates values of {Airbrake::Config} options.
4
+ #
5
+ # @api private
6
+ # @since v1.5.0
7
+ class Validator
8
+ # @return [String]
9
+ REQUIRED_KEY_MSG = ':project_key is required'.freeze
10
+
11
+ # @return [String]
12
+ REQUIRED_ID_MSG = ':project_id is required'.freeze
13
+
14
+ # @return [String]
15
+ WRONG_ENV_TYPE_MSG = "the 'environment' option must be configured " \
16
+ "with a Symbol (or String), but '%s' was provided: " \
17
+ '%s'.freeze
18
+
19
+ # @return [Array<Class>] the list of allowed types to configure the
20
+ # environment option
21
+ VALID_ENV_TYPES = [NilClass, String, Symbol].freeze
22
+
23
+ # @return [String] error message, if validator was able to find any errors
24
+ # in the config
25
+ attr_reader :error_message
26
+
27
+ # Validates given config and stores error message, if any errors were
28
+ # found.
29
+ #
30
+ # @param config [Airbrake::Config] the config to validate
31
+ def initialize(config)
32
+ @config = config
33
+ @error_message = nil
34
+ end
35
+
36
+ # @return [Boolean]
37
+ def valid_project_id?
38
+ valid = @config.project_id.to_i > 0
39
+ @error_message = REQUIRED_ID_MSG unless valid
40
+ valid
41
+ end
42
+
43
+ # @return [Boolean]
44
+ def valid_project_key?
45
+ valid = @config.project_key.is_a?(String) && !@config.project_key.empty?
46
+ @error_message = REQUIRED_KEY_MSG unless valid
47
+ valid
48
+ end
49
+
50
+ # @return [Boolean]
51
+ def valid_environment?
52
+ environment = @config.environment
53
+ valid = VALID_ENV_TYPES.any? { |type| environment.is_a?(type) }
54
+
55
+ unless valid
56
+ @error_message = format(WRONG_ENV_TYPE_MSG, environment.class, environment)
57
+ end
58
+
59
+ valid
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,47 @@
1
+ module Airbrake
2
+ # DeployNotifier sends deploy information to Airbrake. The information
3
+ # consists of:
4
+ # - environment
5
+ # - username
6
+ # - repository
7
+ # - revision
8
+ # - version
9
+ #
10
+ # @api public
11
+ # @since v3.2.0
12
+ class DeployNotifier
13
+ # @param [Airbrake::Config] config
14
+ def initialize(config)
15
+ @config =
16
+ if config.is_a?(Config)
17
+ config
18
+ else
19
+ loc = caller_locations(1..1).first
20
+ signature = "#{self.class.name}##{__method__}"
21
+ warn(
22
+ "#{loc.path}:#{loc.lineno}: warning: passing a Hash to #{signature} " \
23
+ 'is deprecated. Pass `Airbrake::Config` instead'
24
+ )
25
+ Config.new(config)
26
+ end
27
+
28
+ @sender = SyncSender.new(@config)
29
+ end
30
+
31
+ # @see Airbrake.create_deploy
32
+ def notify(deploy_info, promise = Airbrake::Promise.new)
33
+ if @config.ignored_environment?
34
+ return promise.reject("The '#{@config.environment}' environment is ignored")
35
+ end
36
+
37
+ deploy_info[:environment] ||= @config.environment
38
+ @sender.send(
39
+ deploy_info,
40
+ promise,
41
+ URI.join(@config.host, "api/v4/projects/#{@config.project_id}/deploys")
42
+ )
43
+
44
+ promise
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ module Airbrake
2
+ # Extremely simple global cache.
3
+ #
4
+ # @api private
5
+ # @since v2.4.1
6
+ module FileCache
7
+ # @return [Integer]
8
+ MAX_SIZE = 50
9
+
10
+ # @return [Mutex]
11
+ MUTEX = Mutex.new
12
+
13
+ # Associates the value given by +value+ with the key given by +key+. Deletes
14
+ # entries that exceed +MAX_SIZE+.
15
+ #
16
+ # @param [Object] key
17
+ # @param [Object] value
18
+ # @return [Object] the corresponding value
19
+ def self.[]=(key, value)
20
+ MUTEX.synchronize do
21
+ data[key] = value
22
+ data.delete(data.keys.first) if data.size > MAX_SIZE
23
+ end
24
+ end
25
+
26
+ # Retrieve an object from the cache.
27
+ #
28
+ # @param [Object] key
29
+ # @return [Object] the corresponding value
30
+ def self.[](key)
31
+ MUTEX.synchronize do
32
+ data[key]
33
+ end
34
+ end
35
+
36
+ # Checks whether the cache is empty. Needed only for the test suite.
37
+ #
38
+ # @return [Boolean]
39
+ def self.empty?
40
+ data.empty?
41
+ end
42
+
43
+ def self.data
44
+ @data ||= {}
45
+ end
46
+ private_class_method :data
47
+ end
48
+ end
@@ -0,0 +1,95 @@
1
+ module Airbrake
2
+ # FilterChain represents an ordered array of filters.
3
+ #
4
+ # A filter is an object that responds to <b>#call</b> (typically a Proc or a
5
+ # class that implements the call method). The <b>#call</b> method must accept
6
+ # exactly one argument: an object to be filtered.
7
+ #
8
+ # When you add a new filter to the chain, it gets inserted according to its
9
+ # <b>weight</b>. Smaller weight means the filter will be somewhere in the
10
+ # beginning of the array. Larger - in the end. If a filter doesn't implement
11
+ # weight, the chain assumes it's equal to 0.
12
+ #
13
+ # @example
14
+ # class MyFilter
15
+ # attr_reader :weight
16
+ #
17
+ # def initialize
18
+ # @weight = 1
19
+ # end
20
+ #
21
+ # def call(obj)
22
+ # puts 'Filtering...'
23
+ # obj[:data] = '[Filtered]'
24
+ # end
25
+ # end
26
+ #
27
+ # filter_chain = FilterChain.new
28
+ # filter_chain.add_filter(MyFilter)
29
+ #
30
+ # filter_chain.refine(obj)
31
+ # #=> Filtering...
32
+ #
33
+ # @see Airbrake.add_filter
34
+ # @api private
35
+ # @since v1.0.0
36
+ class FilterChain
37
+ # @return [Integer]
38
+ DEFAULT_WEIGHT = 0
39
+
40
+ def initialize
41
+ @filters = []
42
+ end
43
+
44
+ # Adds a filter to the filter chain. Sorts filters by weight.
45
+ #
46
+ # @param [#call] filter The filter object (proc, class, module, etc)
47
+ # @return [void]
48
+ def add_filter(filter)
49
+ @filters = (@filters << filter).sort_by do |f|
50
+ f.respond_to?(:weight) ? f.weight : DEFAULT_WEIGHT
51
+ end.reverse!
52
+ end
53
+
54
+ # Deletes a filter from the the filter chain.
55
+ #
56
+ # @param [Class] filter_class The class of the filter you want to delete
57
+ # @return [void]
58
+ # @since v3.1.0
59
+ def delete_filter(filter_class)
60
+ index = @filters.index { |f| f.class.name == filter_class.name }
61
+ @filters.delete_at(index) if index
62
+ end
63
+
64
+ # Applies all the filters in the filter chain to the given notice. Does not
65
+ # filter ignored notices.
66
+ #
67
+ # @param [Airbrake::Notice] notice The notice to be filtered
68
+ # @return [void]
69
+ # @todo Make it work with anything, not only notices
70
+ def refine(notice)
71
+ @filters.each do |filter|
72
+ break if notice.ignored?
73
+ filter.call(notice)
74
+ end
75
+ end
76
+
77
+ # @return [String] customized inspect to lessen the amount of clutter
78
+ def inspect
79
+ @filters.map(&:class)
80
+ end
81
+
82
+ # @return [String] {#inspect} for PrettyPrint
83
+ def pretty_print(q)
84
+ q.text('[')
85
+
86
+ # Make nesting of the first element consistent on JRuby and MRI.
87
+ q.nest(2) { q.breakable }
88
+
89
+ q.nest(2) do
90
+ q.seplist(@filters) { |f| q.pp(f.class) }
91
+ end
92
+ q.text(']')
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,29 @@
1
+ module Airbrake
2
+ module Filters
3
+ # Adds user context to the notice object. Clears the context after it's
4
+ # attached.
5
+ #
6
+ # @api private
7
+ # @since v2.9.0
8
+ class ContextFilter
9
+ # @return [Integer]
10
+ attr_reader :weight
11
+
12
+ def initialize(context)
13
+ @context = context
14
+ @weight = 119
15
+ @mutex = Mutex.new
16
+ end
17
+
18
+ # @macro call_filter
19
+ def call(notice)
20
+ @mutex.synchronize do
21
+ return if @context.empty?
22
+
23
+ notice[:params][:airbrake_context] = @context.dup
24
+ @context.clear
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ module Airbrake
2
+ module Filters
3
+ # Attaches loaded dependencies to the notice object.
4
+ #
5
+ # @api private
6
+ # @since v2.10.0
7
+ class DependencyFilter
8
+ def initialize
9
+ @weight = 117
10
+ end
11
+
12
+ # @macro call_filter
13
+ def call(notice)
14
+ deps = {}
15
+ Gem.loaded_specs.map.with_object(deps) do |(name, spec), h|
16
+ h[name] = "#{spec.version}#{git_version(spec)}"
17
+ end
18
+
19
+ notice[:context][:versions] = {} unless notice[:context].key?(:versions)
20
+ notice[:context][:versions][:dependencies] = deps
21
+ end
22
+
23
+ private
24
+
25
+ def git_version(spec)
26
+ return unless spec.respond_to?(:git_version) || spec.git_version
27
+ spec.git_version.to_s
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ module Airbrake
2
+ module Filters
3
+ # ExceptionAttributesFilter attempts to call `#to_airbrake` on the stashed
4
+ # exception and attaches returned data to the notice object.
5
+ #
6
+ # @api private
7
+ # @since v2.10.0
8
+ class ExceptionAttributesFilter
9
+ def initialize(logger)
10
+ @logger = logger
11
+ @weight = 118
12
+ end
13
+
14
+ # @macro call_filter
15
+ def call(notice)
16
+ exception = notice.stash[:exception]
17
+ return unless exception.respond_to?(:to_airbrake)
18
+
19
+ attributes = nil
20
+ begin
21
+ attributes = exception.to_airbrake
22
+ rescue StandardError => ex
23
+ @logger.error(
24
+ "#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}"
25
+ )
26
+ end
27
+
28
+ unless attributes.is_a?(Hash)
29
+ @logger.error(
30
+ "#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}"
31
+ )
32
+ return
33
+ end
34
+
35
+ attributes.each do |key, attrs|
36
+ if notice[key]
37
+ notice[key].merge!(attrs)
38
+ else
39
+ notice[key] = attrs
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ module Airbrake
2
+ module Filters
3
+ # Replaces paths to gems with a placeholder.
4
+ # @api private
5
+ class GemRootFilter
6
+ # @return [String]
7
+ GEM_ROOT_LABEL = '/GEM_ROOT'.freeze
8
+
9
+ # @return [Integer]
10
+ attr_reader :weight
11
+
12
+ def initialize
13
+ @weight = 120
14
+ end
15
+
16
+ # @macro call_filter
17
+ def call(notice)
18
+ return unless defined?(Gem)
19
+
20
+ notice[:errors].each do |error|
21
+ Gem.path.each do |gem_path|
22
+ error[:backtrace].each do |frame|
23
+ # If the frame is unparseable, then 'file' is nil, thus nothing to
24
+ # filter (all frame's data is in 'function' instead).
25
+ next unless (file = frame[:file])
26
+ frame[:file] = file.sub(/\A#{gem_path}/, GEM_ROOT_LABEL)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,90 @@
1
+ require 'date'
2
+
3
+ module Airbrake
4
+ module Filters
5
+ # Attaches git checkout info to `context`. The info includes:
6
+ # * username
7
+ # * email
8
+ # * revision
9
+ # * time
10
+ #
11
+ # This information is used to track deploys automatically.
12
+ #
13
+ # @api private
14
+ # @since v2.12.0
15
+ class GitLastCheckoutFilter
16
+ # @return [Integer]
17
+ attr_reader :weight
18
+
19
+ # @return [Integer] least possible amount of columns in git's `logs/HEAD`
20
+ # file (checkout information is omitted)
21
+ MIN_HEAD_COLS = 6
22
+
23
+ # @param [Logger] logger
24
+ # @param [String] root_directory
25
+ def initialize(logger, root_directory)
26
+ @git_path = File.join(root_directory, '.git')
27
+ @logger = logger
28
+ @weight = 116
29
+ @last_checkout = nil
30
+ end
31
+
32
+ # @macro call_filter
33
+ def call(notice)
34
+ return if notice[:context].key?(:lastCheckout)
35
+
36
+ if @last_checkout
37
+ notice[:context][:lastCheckout] = @last_checkout
38
+ return
39
+ end
40
+
41
+ return unless File.exist?(@git_path)
42
+ return unless (checkout = last_checkout)
43
+ notice[:context][:lastCheckout] = checkout
44
+ end
45
+
46
+ private
47
+
48
+ def last_checkout
49
+ return unless (line = last_checkout_line)
50
+
51
+ parts = line.chomp.split("\t").first.split(' ')
52
+ if parts.size < MIN_HEAD_COLS
53
+ @logger.error(
54
+ "#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}"
55
+ )
56
+ return
57
+ end
58
+
59
+ author = parts[2..-4]
60
+ @last_checkout = {
61
+ username: author[0..1].join(' '),
62
+ email: parts[-3][1..-2],
63
+ revision: parts[1],
64
+ time: timestamp(parts[-2].to_i)
65
+ }
66
+ end
67
+
68
+ def last_checkout_line
69
+ head_path = File.join(@git_path, 'logs', 'HEAD')
70
+ return unless File.exist?(head_path)
71
+
72
+ last_line = nil
73
+ IO.foreach(head_path) do |line|
74
+ last_line = line if checkout_line?(line)
75
+ end
76
+ last_line
77
+ end
78
+
79
+ def checkout_line?(line)
80
+ line.include?("\tclone:") ||
81
+ line.include?("\tpull:") ||
82
+ line.include?("\tcheckout:")
83
+ end
84
+
85
+ def timestamp(utime)
86
+ Time.at(utime).to_datetime.rfc3339
87
+ end
88
+ end
89
+ end
90
+ end