sentry-raven 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +6 -0
  4. data/lib/raven/configuration.rb +10 -0
  5. data/lib/raven/instance.rb +5 -0
  6. data/lib/raven/integrations/delayed_job.rb +2 -1
  7. data/lib/raven/integrations/rack.rb +15 -2
  8. data/lib/raven/integrations/sidekiq/error_handler.rb +2 -2
  9. data/lib/raven/transports/http.rb +0 -2
  10. data/lib/raven/{integrations/sidekiq → utils}/context_filter.rb +3 -3
  11. data/lib/raven/version.rb +1 -1
  12. data/sentry-ruby/.gitignore +11 -0
  13. data/sentry-ruby/.rspec +3 -0
  14. data/sentry-ruby/.travis.yml +6 -0
  15. data/sentry-ruby/CODE_OF_CONDUCT.md +74 -0
  16. data/sentry-ruby/Gemfile +9 -0
  17. data/sentry-ruby/LICENSE.txt +21 -0
  18. data/sentry-ruby/README.md +44 -0
  19. data/sentry-ruby/Rakefile +6 -0
  20. data/sentry-ruby/bin/console +14 -0
  21. data/sentry-ruby/bin/setup +8 -0
  22. data/sentry-ruby/examples/rails-6.0/.browserslistrc +1 -0
  23. data/sentry-ruby/examples/rails-6.0/.gitignore +35 -0
  24. data/sentry-ruby/examples/rails-6.0/Gemfile +58 -0
  25. data/sentry-ruby/examples/rails-6.0/README.md +23 -0
  26. data/sentry-ruby/examples/rails-6.0/Rakefile +6 -0
  27. data/sentry-ruby/examples/rails-6.0/app/assets/config/manifest.js +2 -0
  28. data/sentry-ruby/examples/rails-6.0/app/assets/images/.keep +0 -0
  29. data/sentry-ruby/examples/rails-6.0/app/assets/stylesheets/application.css +15 -0
  30. data/sentry-ruby/examples/rails-6.0/app/channels/application_cable/channel.rb +4 -0
  31. data/sentry-ruby/examples/rails-6.0/app/channels/application_cable/connection.rb +4 -0
  32. data/sentry-ruby/examples/rails-6.0/app/controllers/application_controller.rb +2 -0
  33. data/sentry-ruby/examples/rails-6.0/app/controllers/concerns/.keep +0 -0
  34. data/sentry-ruby/examples/rails-6.0/app/controllers/welcome_controller.rb +23 -0
  35. data/sentry-ruby/examples/rails-6.0/app/helpers/application_helper.rb +2 -0
  36. data/sentry-ruby/examples/rails-6.0/app/javascript/channels/consumer.js +6 -0
  37. data/sentry-ruby/examples/rails-6.0/app/javascript/channels/index.js +5 -0
  38. data/sentry-ruby/examples/rails-6.0/app/javascript/packs/application.js +17 -0
  39. data/sentry-ruby/examples/rails-6.0/app/jobs/application_job.rb +7 -0
  40. data/sentry-ruby/examples/rails-6.0/app/mailers/application_mailer.rb +4 -0
  41. data/sentry-ruby/examples/rails-6.0/app/models/application_record.rb +3 -0
  42. data/sentry-ruby/examples/rails-6.0/app/models/concerns/.keep +0 -0
  43. data/sentry-ruby/examples/rails-6.0/app/views/layouts/application.html.erb +15 -0
  44. data/sentry-ruby/examples/rails-6.0/app/views/layouts/mailer.html.erb +13 -0
  45. data/sentry-ruby/examples/rails-6.0/app/views/layouts/mailer.text.erb +1 -0
  46. data/sentry-ruby/examples/rails-6.0/app/views/welcome/report_demo.html.erb +22 -0
  47. data/sentry-ruby/examples/rails-6.0/app/views/welcome/view_error.html.erb +1 -0
  48. data/sentry-ruby/examples/rails-6.0/app/workers/error_worker.rb +7 -0
  49. data/sentry-ruby/examples/rails-6.0/babel.config.js +72 -0
  50. data/sentry-ruby/examples/rails-6.0/bin/bundle +114 -0
  51. data/sentry-ruby/examples/rails-6.0/bin/rails +9 -0
  52. data/sentry-ruby/examples/rails-6.0/bin/rake +9 -0
  53. data/sentry-ruby/examples/rails-6.0/bin/setup +36 -0
  54. data/sentry-ruby/examples/rails-6.0/bin/spring +17 -0
  55. data/sentry-ruby/examples/rails-6.0/bin/webpack +18 -0
  56. data/sentry-ruby/examples/rails-6.0/bin/webpack-dev-server +18 -0
  57. data/sentry-ruby/examples/rails-6.0/bin/yarn +11 -0
  58. data/sentry-ruby/examples/rails-6.0/config.ru +5 -0
  59. data/sentry-ruby/examples/rails-6.0/config/application.rb +28 -0
  60. data/sentry-ruby/examples/rails-6.0/config/boot.rb +4 -0
  61. data/sentry-ruby/examples/rails-6.0/config/cable.yml +10 -0
  62. data/sentry-ruby/examples/rails-6.0/config/credentials.yml.enc +1 -0
  63. data/sentry-ruby/examples/rails-6.0/config/database.yml +25 -0
  64. data/sentry-ruby/examples/rails-6.0/config/environment.rb +5 -0
  65. data/sentry-ruby/examples/rails-6.0/config/environments/development.rb +62 -0
  66. data/sentry-ruby/examples/rails-6.0/config/environments/production.rb +112 -0
  67. data/sentry-ruby/examples/rails-6.0/config/environments/test.rb +48 -0
  68. data/sentry-ruby/examples/rails-6.0/config/initializers/application_controller_renderer.rb +8 -0
  69. data/sentry-ruby/examples/rails-6.0/config/initializers/assets.rb +14 -0
  70. data/sentry-ruby/examples/rails-6.0/config/initializers/backtrace_silencers.rb +7 -0
  71. data/sentry-ruby/examples/rails-6.0/config/initializers/content_security_policy.rb +30 -0
  72. data/sentry-ruby/examples/rails-6.0/config/initializers/cookies_serializer.rb +5 -0
  73. data/sentry-ruby/examples/rails-6.0/config/initializers/filter_parameter_logging.rb +4 -0
  74. data/sentry-ruby/examples/rails-6.0/config/initializers/inflections.rb +16 -0
  75. data/sentry-ruby/examples/rails-6.0/config/initializers/mime_types.rb +4 -0
  76. data/sentry-ruby/examples/rails-6.0/config/initializers/wrap_parameters.rb +14 -0
  77. data/sentry-ruby/examples/rails-6.0/config/locales/en.yml +33 -0
  78. data/sentry-ruby/examples/rails-6.0/config/puma.rb +38 -0
  79. data/sentry-ruby/examples/rails-6.0/config/routes.rb +10 -0
  80. data/sentry-ruby/examples/rails-6.0/config/spring.rb +6 -0
  81. data/sentry-ruby/examples/rails-6.0/config/storage.yml +34 -0
  82. data/sentry-ruby/examples/rails-6.0/config/webpack/development.js +5 -0
  83. data/sentry-ruby/examples/rails-6.0/config/webpack/environment.js +3 -0
  84. data/sentry-ruby/examples/rails-6.0/config/webpack/production.js +5 -0
  85. data/sentry-ruby/examples/rails-6.0/config/webpack/test.js +5 -0
  86. data/sentry-ruby/examples/rails-6.0/config/webpacker.yml +96 -0
  87. data/sentry-ruby/examples/rails-6.0/db/seeds.rb +7 -0
  88. data/sentry-ruby/examples/rails-6.0/lib/assets/.keep +0 -0
  89. data/sentry-ruby/examples/rails-6.0/lib/tasks/.keep +0 -0
  90. data/sentry-ruby/examples/rails-6.0/package.json +15 -0
  91. data/sentry-ruby/examples/rails-6.0/postcss.config.js +12 -0
  92. data/sentry-ruby/examples/rails-6.0/public/404.html +67 -0
  93. data/sentry-ruby/examples/rails-6.0/public/422.html +67 -0
  94. data/sentry-ruby/examples/rails-6.0/public/500.html +66 -0
  95. data/sentry-ruby/examples/rails-6.0/public/apple-touch-icon-precomposed.png +0 -0
  96. data/sentry-ruby/examples/rails-6.0/public/apple-touch-icon.png +0 -0
  97. data/sentry-ruby/examples/rails-6.0/public/favicon.ico +0 -0
  98. data/sentry-ruby/examples/rails-6.0/public/robots.txt +1 -0
  99. data/sentry-ruby/examples/rails-6.0/storage/.keep +0 -0
  100. data/sentry-ruby/examples/rails-6.0/test/application_system_test_case.rb +5 -0
  101. data/sentry-ruby/examples/rails-6.0/test/channels/application_cable/connection_test.rb +11 -0
  102. data/sentry-ruby/examples/rails-6.0/test/controllers/.keep +0 -0
  103. data/sentry-ruby/examples/rails-6.0/test/fixtures/.keep +0 -0
  104. data/sentry-ruby/examples/rails-6.0/test/fixtures/files/.keep +0 -0
  105. data/sentry-ruby/examples/rails-6.0/test/helpers/.keep +0 -0
  106. data/sentry-ruby/examples/rails-6.0/test/integration/.keep +0 -0
  107. data/sentry-ruby/examples/rails-6.0/test/mailers/.keep +0 -0
  108. data/sentry-ruby/examples/rails-6.0/test/models/.keep +0 -0
  109. data/sentry-ruby/examples/rails-6.0/test/system/.keep +0 -0
  110. data/sentry-ruby/examples/rails-6.0/test/test_helper.rb +13 -0
  111. data/sentry-ruby/examples/rails-6.0/vendor/.keep +0 -0
  112. data/sentry-ruby/examples/rails-6.0/yarn.lock +7508 -0
  113. data/sentry-ruby/lib/sentry.rb +16 -0
  114. data/sentry-ruby/lib/sentry/backtrace.rb +128 -0
  115. data/sentry-ruby/lib/sentry/client.rb +162 -0
  116. data/sentry-ruby/lib/sentry/client/state.rb +40 -0
  117. data/sentry-ruby/lib/sentry/configuration.rb +533 -0
  118. data/sentry-ruby/lib/sentry/event.rb +209 -0
  119. data/sentry-ruby/lib/sentry/interface.rb +31 -0
  120. data/sentry-ruby/lib/sentry/interfaces/exception.rb +15 -0
  121. data/sentry-ruby/lib/sentry/interfaces/http.rb +16 -0
  122. data/sentry-ruby/lib/sentry/interfaces/message.rb +18 -0
  123. data/sentry-ruby/lib/sentry/interfaces/single_exception.rb +14 -0
  124. data/sentry-ruby/lib/sentry/interfaces/stack_trace.rb +69 -0
  125. data/sentry-ruby/lib/sentry/linecache.rb +44 -0
  126. data/sentry-ruby/lib/sentry/logger.rb +20 -0
  127. data/sentry-ruby/lib/sentry/transports.rb +19 -0
  128. data/sentry-ruby/lib/sentry/transports/dummy.rb +16 -0
  129. data/sentry-ruby/lib/sentry/transports/http.rb +66 -0
  130. data/sentry-ruby/lib/sentry/transports/stdout.rb +20 -0
  131. data/sentry-ruby/lib/sentry/utils/deep_merge.rb +22 -0
  132. data/sentry-ruby/lib/sentry/utils/exception_cause_chain.rb +20 -0
  133. data/sentry-ruby/lib/sentry/version.rb +3 -0
  134. data/sentry-ruby/sentry-ruby.gemspec +26 -0
  135. data/sentry-ruby/spec/sentry/backtrace_spec.rb +38 -0
  136. data/sentry-ruby/spec/sentry/client_spec.rb +443 -0
  137. data/sentry-ruby/spec/sentry/configuration_spec.rb +400 -0
  138. data/sentry-ruby/spec/sentry/event_spec.rb +238 -0
  139. data/sentry-ruby/spec/sentry/interface_spec.rb +38 -0
  140. data/sentry-ruby/spec/sentry/interfaces/stack_trace_spec.rb +11 -0
  141. data/sentry-ruby/spec/sentry/linecache_spec.rb +40 -0
  142. data/sentry-ruby/spec/sentry/transports/http_spec.rb +57 -0
  143. data/sentry-ruby/spec/sentry/transports/stdout_spec.rb +11 -0
  144. data/sentry-ruby/spec/sentry_spec.rb +9 -0
  145. data/sentry-ruby/spec/spec_helper.rb +49 -0
  146. data/sentry-ruby/spec/support/linecache.txt +6 -0
  147. metadata +138 -3
@@ -0,0 +1,16 @@
1
+ require "sentry/configuration"
2
+ require "sentry/logger"
3
+ require "sentry/event"
4
+ require "sentry/client"
5
+
6
+ module Sentry
7
+ class Error < StandardError
8
+ end
9
+
10
+ def self.sys_command(command)
11
+ result = `#{command} 2>&1` rescue nil
12
+ return if result.nil? || result.empty? || ($CHILD_STATUS && $CHILD_STATUS.exitstatus != 0)
13
+
14
+ result.strip
15
+ end
16
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ ## Inspired by Rails' and Airbrake's backtrace parsers.
4
+
5
+ module Sentry
6
+ # Front end to parsing the backtrace for each notice
7
+ class Backtrace
8
+ # Handles backtrace parsing line by line
9
+ class Line
10
+ RB_EXTENSION = ".rb"
11
+ # regexp (optional leading X: on windows, or JRuby9000 class-prefix)
12
+ RUBY_INPUT_FORMAT = /
13
+ ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
14
+ (\d+)
15
+ (?: :in \s `([^']+)')?$
16
+ /x.freeze
17
+
18
+ # org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
19
+ JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
20
+
21
+ # The file portion of the line (such as app/models/user.rb)
22
+ attr_reader :file
23
+
24
+ # The line number portion of the line
25
+ attr_reader :number
26
+
27
+ # The method of the line (such as index)
28
+ attr_reader :method
29
+
30
+ # The module name (JRuby)
31
+ attr_reader :module_name
32
+
33
+ attr_reader :in_app_pattern
34
+
35
+ # Parses a single line of a given backtrace
36
+ # @param [String] unparsed_line The raw line from +caller+ or some backtrace
37
+ # @return [Line] The parsed backtrace line
38
+ def self.parse(unparsed_line, in_app_pattern)
39
+ ruby_match = unparsed_line.match(RUBY_INPUT_FORMAT)
40
+ if ruby_match
41
+ _, file, number, method = ruby_match.to_a
42
+ file.sub!(/\.class$/, RB_EXTENSION)
43
+ module_name = nil
44
+ else
45
+ java_match = unparsed_line.match(JAVA_INPUT_FORMAT)
46
+ _, module_name, method, file, number = java_match.to_a
47
+ end
48
+ new(file, number, method, module_name, in_app_pattern)
49
+ end
50
+
51
+ def initialize(file, number, method, module_name, in_app_pattern)
52
+ @file = file
53
+ @module_name = module_name
54
+ @number = number.to_i
55
+ @method = method
56
+ @in_app_pattern = in_app_pattern
57
+ end
58
+
59
+ def in_app
60
+ if file =~ in_app_pattern
61
+ true
62
+ else
63
+ false
64
+ end
65
+ end
66
+
67
+ # Reconstructs the line in a readable fashion
68
+ def to_s
69
+ "#{file}:#{number}:in `#{method}'"
70
+ end
71
+
72
+ def ==(other)
73
+ to_s == other.to_s
74
+ end
75
+
76
+ def inspect
77
+ "<Line:#{self}>"
78
+ end
79
+ end
80
+
81
+ APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test)/.freeze
82
+
83
+ # holder for an Array of Backtrace::Line instances
84
+ attr_reader :lines
85
+ attr_reader :configuration
86
+
87
+ def self.parse(backtrace, configuration:)
88
+ ruby_lines = backtrace.is_a?(Array) ? backtrace : backtrace.split(/\n\s*/)
89
+
90
+ ruby_lines = configuration.backtrace_cleanup_callback.call(ruby_lines) if configuration&.backtrace_cleanup_callback
91
+
92
+ in_app_pattern ||= begin
93
+ project_root = configuration.project_root&.to_s
94
+ Regexp.new("^(#{project_root}/)?#{configuration.app_dirs_pattern || APP_DIRS_PATTERN}")
95
+ end
96
+
97
+ lines = ruby_lines.to_a.map do |unparsed_line|
98
+ Line.parse(unparsed_line, in_app_pattern)
99
+ end
100
+
101
+ new(lines)
102
+ end
103
+
104
+ def initialize(lines)
105
+ @lines = lines
106
+ end
107
+
108
+ def inspect
109
+ "<Backtrace: " + lines.map(&:inspect).join(", ") + ">"
110
+ end
111
+
112
+ def to_s
113
+ content = []
114
+ lines.each do |line|
115
+ content << line
116
+ end
117
+ content.join("\n")
118
+ end
119
+
120
+ def ==(other)
121
+ if other.respond_to?(:lines)
122
+ lines == other.lines
123
+ else
124
+ false
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,162 @@
1
+ require "json"
2
+ require "base64"
3
+ require "sentry/transports"
4
+ require "sentry/client/state"
5
+ require 'sentry/utils/deep_merge'
6
+
7
+ module Sentry
8
+ class Client
9
+ PROTOCOL_VERSION = '5'
10
+ USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
11
+ CONTENT_TYPE = 'application/json'
12
+
13
+ attr_reader :transport, :configuration
14
+
15
+ def initialize(configuration)
16
+ @configuration = configuration
17
+ @state = State.new
18
+ @transport = case configuration.scheme
19
+ when 'http', 'https'
20
+ Transports::HTTP.new(configuration)
21
+ when 'stdout'
22
+ Transports::Stdout.new(configuration)
23
+ when 'dummy'
24
+ Transports::Dummy.new(configuration)
25
+ else
26
+ fail "Unknown transport scheme '#{configuration.scheme}'"
27
+ end
28
+ end
29
+
30
+ def event_from_exception(exception, message: nil, extra: {}, backtrace: [], checksum: nil, release: nil, fingerprint: [])
31
+ options = {
32
+ message: message,
33
+ extra: extra,
34
+ backtrace: backtrace,
35
+ checksum: checksum,
36
+ fingerprint: fingerprint,
37
+ configuration: configuration,
38
+ release: release
39
+ }
40
+
41
+ exception_context =
42
+ if exception.instance_variable_defined?(:@__sentry_context)
43
+ exception.instance_variable_get(:@__sentry_context)
44
+ elsif exception.respond_to?(:sentry_context)
45
+ exception.sentry_context
46
+ else
47
+ {}
48
+ end
49
+
50
+ options = Utils::DeepMergeHash.deep_merge(exception_context, options)
51
+
52
+ return unless @configuration.exception_class_allowed?(exception)
53
+
54
+ Event.new(**options) do |evt|
55
+ evt.add_exception_interface(exception)
56
+ end
57
+ end
58
+
59
+ def event_from_message(message, extra: {}, backtrace: [], level: :error)
60
+ options = {
61
+ message: message,
62
+ extra: extra,
63
+ backtrace: backtrace,
64
+ configuration: configuration,
65
+ level: level
66
+ }
67
+ Event.new(**options)
68
+ end
69
+
70
+ def generate_auth_header
71
+ now = Time.now.to_i.to_s
72
+ fields = {
73
+ 'sentry_version' => PROTOCOL_VERSION,
74
+ 'sentry_client' => USER_AGENT,
75
+ 'sentry_timestamp' => now,
76
+ 'sentry_key' => configuration.public_key
77
+ }
78
+ fields['sentry_secret'] = configuration.secret_key unless configuration.secret_key.nil?
79
+ 'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
80
+ end
81
+
82
+ def send_event(event, hint = nil)
83
+ return false unless configuration.sending_allowed?(event)
84
+
85
+ event = configuration.before_send.call(event, hint) if configuration.before_send
86
+ if event.nil?
87
+ configuration.logger.info "Discarded event because before_send returned nil"
88
+ return
89
+ end
90
+
91
+ # Convert to hash
92
+ event = event.to_hash
93
+
94
+ unless @state.should_try?
95
+ failed_send(nil, event)
96
+ return
97
+ end
98
+
99
+ event_id = event[:event_id] || event['event_id']
100
+ configuration.logger.info "Sending event #{event_id} to Sentry"
101
+
102
+ content_type, encoded_data = encode(event)
103
+
104
+ begin
105
+ transport.send_event(generate_auth_header, encoded_data,
106
+ :content_type => content_type)
107
+ successful_send
108
+ rescue => e
109
+ failed_send(e, event)
110
+ return
111
+ end
112
+
113
+ event
114
+ end
115
+
116
+ private
117
+
118
+ def encode(event)
119
+ encoded = JSON.fast_generate(event.to_hash)
120
+
121
+ case configuration.encoding
122
+ when 'gzip'
123
+ ['application/octet-stream', Base64.strict_encode64(Zlib::Deflate.deflate(encoded))]
124
+ else
125
+ ['application/json', encoded]
126
+ end
127
+ end
128
+
129
+ def successful_send
130
+ @state.success
131
+ end
132
+
133
+ def failed_send(e, event)
134
+ if e # exception was raised
135
+ @state.failure
136
+ configuration.logger.warn "Unable to record event with remote Sentry server (#{e.class} - #{e.message}):\n#{e.backtrace[0..10].join("\n")}"
137
+ else
138
+ configuration.logger.warn "Not sending event due to previous failure(s)."
139
+ end
140
+ configuration.logger.warn("Failed to submit event: #{get_log_message(event)}")
141
+
142
+ # configuration.transport_failure_callback can be false & nil
143
+ configuration.transport_failure_callback.call(event, e) if configuration.transport_failure_callback # rubocop:disable Style/SafeNavigation
144
+ end
145
+
146
+ def get_log_message(event)
147
+ (event && event[:message]) || (event && event['message']) || get_message_from_exception(event) || '<no message value>'
148
+ end
149
+
150
+ def get_message_from_exception(event)
151
+ (
152
+ event &&
153
+ event[:exception] &&
154
+ event[:exception][:values] &&
155
+ event[:exception][:values][0] &&
156
+ event[:exception][:values][0][:type] &&
157
+ event[:exception][:values][0][:value] &&
158
+ "#{event[:exception][:values][0][:type]}: #{event[:exception][:values][0][:value]}"
159
+ )
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,40 @@
1
+ module Sentry
2
+ class Client
3
+ class State
4
+ def initialize
5
+ reset
6
+ end
7
+
8
+ def should_try?
9
+ return true if @status == :online
10
+
11
+ interval = @retry_after || [@retry_number, 6].min**2
12
+ return true if Time.now - @last_check >= interval
13
+
14
+ false
15
+ end
16
+
17
+ def failure(retry_after = nil)
18
+ @status = :error
19
+ @retry_number += 1
20
+ @last_check = Time.now
21
+ @retry_after = retry_after
22
+ end
23
+
24
+ def success
25
+ reset
26
+ end
27
+
28
+ def reset
29
+ @status = :online
30
+ @retry_number = 0
31
+ @last_check = nil
32
+ @retry_after = nil
33
+ end
34
+
35
+ def failed?
36
+ @status == :error
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,533 @@
1
+ require "uri"
2
+ require "sentry/utils/exception_cause_chain"
3
+ require "sentry/linecache"
4
+
5
+ module Sentry
6
+ class Configuration
7
+ # Directories to be recognized as part of your app. e.g. if you
8
+ # have an `engines` dir at the root of your project, you may want
9
+ # to set this to something like /(app|config|engines|lib)/
10
+ attr_accessor :app_dirs_pattern
11
+
12
+ # Provide an object that responds to `call` to send events asynchronously.
13
+ # E.g.: lambda { |event| Thread.new { Sentry.send_event(event) } }
14
+ attr_reader :async
15
+ alias async? async
16
+
17
+ # An array of breadcrumbs loggers to be used. Available options are:
18
+ # - :sentry_logger
19
+ # - :active_support_logger
20
+ attr_reader :breadcrumbs_logger
21
+
22
+ # Number of lines of code context to capture, or nil for none
23
+ attr_accessor :context_lines
24
+
25
+ # RACK_ENV by default.
26
+ attr_reader :current_environment
27
+
28
+ # Encoding type for event bodies. Must be :json or :gzip.
29
+ attr_reader :encoding
30
+
31
+ # Whitelist of environments that will send notifications to Sentry. Array of Strings.
32
+ attr_accessor :environments
33
+
34
+ # Logger 'progname's to exclude from breadcrumbs
35
+ attr_accessor :exclude_loggers
36
+
37
+ # Array of exception classes that should never be sent. See IGNORE_DEFAULT.
38
+ # You should probably append to this rather than overwrite it.
39
+ attr_accessor :excluded_exceptions
40
+
41
+ # Boolean to check nested exceptions when deciding if to exclude. Defaults to false
42
+ attr_accessor :inspect_exception_causes_for_exclusion
43
+ alias inspect_exception_causes_for_exclusion? inspect_exception_causes_for_exclusion
44
+
45
+ # DSN component - set automatically if DSN provided
46
+ attr_accessor :host
47
+
48
+ # The Faraday adapter to be used. Will default to Net::HTTP when not set.
49
+ attr_accessor :http_adapter
50
+
51
+ # A Proc yeilding the faraday builder allowing for further configuration
52
+ # of the faraday adapter
53
+ attr_accessor :faraday_builder
54
+
55
+ # You may provide your own LineCache for matching paths with source files.
56
+ # This may be useful if you need to get source code from places other than
57
+ # the disk. See Sentry::LineCache for the required interface you must implement.
58
+ attr_accessor :linecache
59
+
60
+ # Logger used by Sentry. In Rails, this is the Rails logger, otherwise
61
+ # Sentry provides its own Sentry::Logger.
62
+ attr_accessor :logger
63
+
64
+ # Timeout waiting for the Sentry server connection to open in seconds
65
+ attr_accessor :open_timeout
66
+
67
+ # DSN component - set automatically if DSN provided
68
+ attr_accessor :path
69
+
70
+ # DSN component - set automatically if DSN provided
71
+ attr_accessor :port
72
+
73
+ # Project ID number to send to the Sentry server
74
+ # If you provide a DSN, this will be set automatically.
75
+ attr_accessor :project_id
76
+
77
+ # Project directory root for in_app detection. Could be Rails root, etc.
78
+ # Set automatically for Rails.
79
+ attr_reader :project_root
80
+
81
+ # Proxy information to pass to the HTTP adapter (via Faraday)
82
+ attr_accessor :proxy
83
+
84
+ # Public key for authentication with the Sentry server
85
+ # If you provide a DSN, this will be set automatically.
86
+ attr_accessor :public_key
87
+
88
+ # Turns on ActiveSupport breadcrumbs integration
89
+ attr_reader :rails_activesupport_breadcrumbs
90
+
91
+ # Rails catches exceptions in the ActionDispatch::ShowExceptions or
92
+ # ActionDispatch::DebugExceptions middlewares, depending on the environment.
93
+ # When `rails_report_rescued_exceptions` is true (it is by default), Sentry
94
+ # will report exceptions even when they are rescued by these middlewares.
95
+ attr_accessor :rails_report_rescued_exceptions
96
+
97
+ # Release tag to be passed with every event sent to Sentry.
98
+ # We automatically try to set this to a git SHA or Capistrano release.
99
+ attr_accessor :release
100
+
101
+ # The sampling factor to apply to events. A value of 0.0 will not send
102
+ # any events, and a value of 1.0 will send 100% of events.
103
+ attr_accessor :sample_rate
104
+
105
+ # Boolean - sanitize values that look like credit card numbers
106
+ attr_accessor :sanitize_credit_cards
107
+
108
+ # By default, Sentry censors Hash values when their keys match things like
109
+ # "secret", "password", etc. Provide an array of Strings that, when matched in
110
+ # a hash key, will be censored and not sent to Sentry.
111
+ attr_accessor :sanitize_fields
112
+
113
+ # If you're sure you want to override the default sanitization values, you can
114
+ # add to them to an array of Strings here, e.g. %w(authorization password)
115
+ attr_accessor :sanitize_fields_excluded
116
+
117
+ # Sanitize additional HTTP headers - only Authorization is removed by default.
118
+ attr_accessor :sanitize_http_headers
119
+
120
+ # DSN component - set automatically if DSN provided.
121
+ # Otherwise, can be one of "http", "https", or "dummy"
122
+ attr_accessor :scheme
123
+
124
+ # a proc/lambda that takes an array of stack traces
125
+ # it'll be used to silence (reduce) backtrace of the exception
126
+ #
127
+ # for example:
128
+ #
129
+ # ```ruby
130
+ # Sentry.configuration.backtrace_cleanup_callback = lambda do |backtrace|
131
+ # Rails.backtrace_cleaner.clean(backtrace)
132
+ # end
133
+ # ```
134
+ #
135
+ attr_accessor :backtrace_cleanup_callback
136
+
137
+ # Secret key for authentication with the Sentry server
138
+ # If you provide a DSN, this will be set automatically.
139
+ #
140
+ # This is deprecated and not necessary for newer Sentry installations any more.
141
+ attr_accessor :secret_key
142
+
143
+ # Include module versions in reports - boolean.
144
+ attr_accessor :send_modules
145
+
146
+ # Simple server string - set this to the DSN found on your Sentry settings.
147
+ attr_reader :server
148
+
149
+ attr_accessor :server_name
150
+
151
+ # Provide a configurable callback to determine event capture.
152
+ # Note that the object passed into the block will be a String (messages) or
153
+ # an exception.
154
+ # e.g. lambda { |exc_or_msg| exc_or_msg.some_attr == false }
155
+ attr_reader :should_capture
156
+
157
+ # Silences ready message when true.
158
+ attr_accessor :silence_ready
159
+
160
+ # SSL settings passed directly to Faraday's ssl option
161
+ attr_accessor :ssl
162
+
163
+ # The path to the SSL certificate file
164
+ attr_accessor :ssl_ca_file
165
+
166
+ # Should the SSL certificate of the server be verified?
167
+ attr_accessor :ssl_verification
168
+
169
+ # Default tags for events. Hash.
170
+ attr_accessor :tags
171
+
172
+ # Timeout when waiting for the server to return data in seconds.
173
+ attr_accessor :timeout
174
+
175
+ # Optional Proc, called when the Sentry server cannot be contacted for any reason
176
+ # E.g. lambda { |event| Thread.new { MyJobProcessor.send_email(event) } }
177
+ attr_reader :transport_failure_callback
178
+
179
+ # Optional Proc, called before sending an event to the server/
180
+ # E.g.: lambda { |event, hint| event }
181
+ # E.g.: lambda { |event, hint| nil }
182
+ # E.g.: lambda { |event, hint|
183
+ # event[:message] = 'a'
184
+ # event
185
+ # }
186
+ attr_reader :before_send
187
+
188
+ # Errors object - an Array that contains error messages. See #
189
+ attr_reader :errors
190
+
191
+ # the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
192
+ attr_reader :dsn
193
+
194
+ # Most of these errors generate 4XX responses. In general, Sentry clients
195
+ # only automatically report 5xx responses.
196
+ IGNORE_DEFAULT = [
197
+ 'AbstractController::ActionNotFound',
198
+ 'ActionController::BadRequest',
199
+ 'ActionController::InvalidAuthenticityToken',
200
+ 'ActionController::InvalidCrossOriginRequest',
201
+ 'ActionController::MethodNotAllowed',
202
+ 'ActionController::NotImplemented',
203
+ 'ActionController::ParameterMissing',
204
+ 'ActionController::RoutingError',
205
+ 'ActionController::UnknownAction',
206
+ 'ActionController::UnknownFormat',
207
+ 'ActionController::UnknownHttpMethod',
208
+ 'ActionDispatch::Http::Parameters::ParseError',
209
+ 'ActionView::MissingTemplate',
210
+ 'ActiveJob::DeserializationError', # Can cause infinite loops
211
+ 'ActiveRecord::RecordNotFound',
212
+ 'CGI::Session::CookieStore::TamperedWithCookie',
213
+ 'Mongoid::Errors::DocumentNotFound',
214
+ 'Rack::QueryParser::InvalidParameterError',
215
+ 'Rack::QueryParser::ParameterTypeError',
216
+ 'Sinatra::NotFound'
217
+ ].freeze
218
+
219
+ HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
220
+ "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
221
+
222
+ LOG_PREFIX = "** [Sentry] ".freeze
223
+ MODULE_SEPARATOR = "::".freeze
224
+
225
+ AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
226
+
227
+ def initialize
228
+ self.async = false
229
+ self.breadcrumbs_logger = []
230
+ self.context_lines = 3
231
+ self.current_environment = current_environment_from_env
232
+ self.encoding = 'gzip'
233
+ self.environments = []
234
+ self.exclude_loggers = []
235
+ self.excluded_exceptions = IGNORE_DEFAULT.dup
236
+ self.inspect_exception_causes_for_exclusion = false
237
+ self.linecache = ::Sentry::LineCache.new
238
+ self.logger = ::Sentry::Logger.new(STDOUT)
239
+ self.open_timeout = 1
240
+ self.project_root = detect_project_root
241
+ @rails_activesupport_breadcrumbs = false
242
+
243
+ self.rails_report_rescued_exceptions = true
244
+ self.release = detect_release
245
+ self.sample_rate = 1.0
246
+ self.sanitize_credit_cards = true
247
+ self.sanitize_fields = []
248
+ self.sanitize_fields_excluded = []
249
+ self.sanitize_http_headers = []
250
+ self.send_modules = true
251
+ self.server = ENV['SENTRY_DSN']
252
+ self.server_name = server_name_from_env
253
+ self.should_capture = false
254
+ self.ssl_verification = true
255
+ self.tags = {}
256
+ self.timeout = 2
257
+ self.transport_failure_callback = false
258
+ self.before_send = false
259
+ end
260
+
261
+ def server=(value)
262
+ return if value.nil?
263
+
264
+ @dsn = value
265
+
266
+ uri = URI.parse(value)
267
+ uri_path = uri.path.split('/')
268
+
269
+ if uri.user
270
+ # DSN-style string
271
+ self.project_id = uri_path.pop
272
+ self.public_key = uri.user
273
+ self.secret_key = !(uri.password.nil? || uri.password.empty?) ? uri.password : nil
274
+ end
275
+
276
+ self.scheme = uri.scheme
277
+ self.host = uri.host
278
+ self.port = uri.port if uri.port
279
+ self.path = uri_path.join('/')
280
+
281
+ # For anyone who wants to read the base server string
282
+ @server = "#{scheme}://#{host}"
283
+ @server += ":#{port}" unless port == { 'http' => 80, 'https' => 443 }[scheme]
284
+ @server += path
285
+ end
286
+ alias dsn= server=
287
+
288
+ def encoding=(encoding)
289
+ raise(Error, 'Unsupported encoding') unless %w(gzip json).include? encoding
290
+
291
+ @encoding = encoding
292
+ end
293
+
294
+ def async=(value)
295
+ unless value == false || value.respond_to?(:call)
296
+ raise(ArgumentError, "async must be callable (or false to disable)")
297
+ end
298
+
299
+ @async = value
300
+ end
301
+
302
+ def breadcrumbs_logger=(logger)
303
+ loggers =
304
+ if logger.is_a?(Array)
305
+ logger
306
+ else
307
+ unless AVAILABLE_BREADCRUMBS_LOGGERS.include?(logger)
308
+ raise Sentry::Error, "Unsupported breadcrumbs logger. Supported loggers: #{AVAILABLE_BREADCRUMBS_LOGGERS}"
309
+ end
310
+
311
+ Array(logger)
312
+ end
313
+
314
+ require "raven/breadcrumbs/sentry_logger" if loggers.include?(:sentry_logger)
315
+
316
+ @breadcrumbs_logger = logger
317
+ end
318
+
319
+ def transport_failure_callback=(value)
320
+ unless value == false || value.respond_to?(:call)
321
+ raise(ArgumentError, "transport_failure_callback must be callable (or false to disable)")
322
+ end
323
+
324
+ @transport_failure_callback = value
325
+ end
326
+
327
+ def should_capture=(value)
328
+ unless value == false || value.respond_to?(:call)
329
+ raise ArgumentError, "should_capture must be callable (or false to disable)"
330
+ end
331
+
332
+ @should_capture = value
333
+ end
334
+
335
+ def before_send=(value)
336
+ unless value == false || value.respond_to?(:call)
337
+ raise ArgumentError, "before_send must be callable (or false to disable)"
338
+ end
339
+
340
+ @before_send = value
341
+ end
342
+
343
+ # Allows config options to be read like a hash
344
+ #
345
+ # @param [Symbol] option Key for a given attribute
346
+ def [](option)
347
+ public_send(option)
348
+ end
349
+
350
+ def current_environment=(environment)
351
+ @current_environment = environment.to_s
352
+ end
353
+
354
+ def capture_allowed?(message_or_exc = nil)
355
+ @errors = []
356
+
357
+ valid? &&
358
+ capture_in_current_environment? &&
359
+ capture_allowed_by_callback?(message_or_exc) &&
360
+ sample_allowed?
361
+ end
362
+ # If we cannot capture, we cannot send.
363
+ alias sending_allowed? capture_allowed?
364
+
365
+ def error_messages
366
+ @errors = [errors[0]] + errors[1..-1].map(&:downcase) # fix case of all but first
367
+ errors.join(", ")
368
+ end
369
+
370
+ def project_root=(root_dir)
371
+ @project_root = root_dir
372
+ end
373
+
374
+ def rails_activesupport_breadcrumbs=(val)
375
+ DeprecationHelper.deprecate_old_breadcrumbs_configuration(:active_support_logger)
376
+ @rails_activesupport_breadcrumbs = val
377
+ end
378
+
379
+ def exception_class_allowed?(exc)
380
+ if exc.is_a?(Sentry::Error)
381
+ # Try to prevent error reporting loops
382
+ logger.debug "Refusing to capture Sentry error: #{exc.inspect}"
383
+ false
384
+ elsif excluded_exception?(exc)
385
+ logger.debug "User excluded error: #{exc.inspect}"
386
+ false
387
+ else
388
+ true
389
+ end
390
+ end
391
+
392
+ def enabled_in_current_env?
393
+ environments.empty? || environments.include?(current_environment)
394
+ end
395
+
396
+ private
397
+
398
+ def detect_project_root
399
+ if defined? Rails.root # we are in a Rails application
400
+ Rails.root.to_s
401
+ else
402
+ Dir.pwd
403
+ end
404
+ end
405
+
406
+ def detect_release
407
+ detect_release_from_env ||
408
+ detect_release_from_git ||
409
+ detect_release_from_capistrano ||
410
+ detect_release_from_heroku
411
+ rescue => e
412
+ logger.error "Error detecting release: #{e.message}"
413
+ end
414
+
415
+ def excluded_exception?(incoming_exception)
416
+ excluded_exceptions.any? do |excluded_exception|
417
+ matches_exception?(get_exception_class(excluded_exception), incoming_exception)
418
+ end
419
+ end
420
+
421
+ def get_exception_class(x)
422
+ x.is_a?(Module) ? x : qualified_const_get(x)
423
+ end
424
+
425
+ def matches_exception?(excluded_exception_class, incoming_exception)
426
+ if inspect_exception_causes_for_exclusion?
427
+ Sentry::Utils::ExceptionCauseChain.exception_to_array(incoming_exception).any? { |cause| excluded_exception_class === cause }
428
+ else
429
+ excluded_exception_class === incoming_exception
430
+ end
431
+ end
432
+
433
+ # In Ruby <2.0 const_get can't lookup "SomeModule::SomeClass" in one go
434
+ def qualified_const_get(x)
435
+ x = x.to_s
436
+ if !x.match(/::/)
437
+ Object.const_get(x)
438
+ else
439
+ x.split(MODULE_SEPARATOR).reject(&:empty?).inject(Object) { |a, e| a.const_get(e) }
440
+ end
441
+ rescue NameError # There's no way to safely ask if a constant exist for an unknown string
442
+ nil
443
+ end
444
+
445
+ def detect_release_from_heroku
446
+ return unless running_on_heroku?
447
+ return if ENV['CI']
448
+ logger.warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
449
+
450
+ ENV['HEROKU_SLUG_COMMIT']
451
+ end
452
+
453
+ def running_on_heroku?
454
+ File.directory?("/etc/heroku")
455
+ end
456
+
457
+ def detect_release_from_capistrano
458
+ revision_file = File.join(project_root, 'REVISION')
459
+ revision_log = File.join(project_root, '..', 'revisions.log')
460
+
461
+ if File.exist?(revision_file)
462
+ File.read(revision_file).strip
463
+ elsif File.exist?(revision_log)
464
+ File.open(revision_log).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
465
+ end
466
+ end
467
+
468
+ def detect_release_from_git
469
+ Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
470
+ end
471
+
472
+ def detect_release_from_env
473
+ ENV['SENTRY_RELEASE']
474
+ end
475
+
476
+ def capture_in_current_environment?
477
+ return true if enabled_in_current_env?
478
+
479
+ @errors << "Not configured to send/capture in environment '#{current_environment}'"
480
+ false
481
+ end
482
+
483
+ def capture_allowed_by_callback?(message_or_exc)
484
+ return true if !should_capture || message_or_exc.nil? || should_capture.call(message_or_exc)
485
+
486
+ @errors << "should_capture returned false"
487
+ false
488
+ end
489
+
490
+ def valid?
491
+ return true if %w(server host path public_key project_id).all? { |k| public_send(k) }
492
+
493
+ if server
494
+ %w(server host path public_key project_id).map do |key|
495
+ @errors << "No #{key} specified" unless public_send(key)
496
+ end
497
+ else
498
+ @errors << "DSN not set"
499
+ end
500
+ false
501
+ end
502
+
503
+ def sample_allowed?
504
+ return true if sample_rate == 1.0
505
+
506
+ if Random::DEFAULT.rand >= sample_rate
507
+ @errors << "Excluded by random sample"
508
+ false
509
+ else
510
+ true
511
+ end
512
+ end
513
+
514
+ # Try to resolve the hostname to an FQDN, but fall back to whatever
515
+ # the load name is.
516
+ def resolve_hostname
517
+ Socket.gethostname ||
518
+ Socket.gethostbyname(hostname).first rescue server_name
519
+ end
520
+
521
+ def current_environment_from_env
522
+ ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default'
523
+ end
524
+
525
+ def server_name_from_env
526
+ if running_on_heroku?
527
+ ENV['DYNO']
528
+ else
529
+ resolve_hostname
530
+ end
531
+ end
532
+ end
533
+ end