jones-gem 2.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +74 -0
  4. data/.travis.yml +47 -0
  5. data/Gemfile +38 -0
  6. data/LICENSE +201 -0
  7. data/README.md +132 -0
  8. data/Rakefile +29 -0
  9. data/changelog.md +442 -0
  10. data/docs/Makefile +130 -0
  11. data/docs/breadcrumbs.rst +51 -0
  12. data/docs/conf.py +228 -0
  13. data/docs/config.rst +260 -0
  14. data/docs/context.rst +141 -0
  15. data/docs/index.rst +113 -0
  16. data/docs/install.rst +40 -0
  17. data/docs/integrations/heroku.rst +11 -0
  18. data/docs/integrations/index.rst +59 -0
  19. data/docs/integrations/puma.rst +30 -0
  20. data/docs/integrations/rack.rst +27 -0
  21. data/docs/integrations/rails.rst +84 -0
  22. data/docs/make.bat +155 -0
  23. data/docs/processors.rst +124 -0
  24. data/docs/sentry-doc-config.json +31 -0
  25. data/docs/usage.rst +176 -0
  26. data/exe/raven +32 -0
  27. data/jones-gem.gemspec +22 -0
  28. data/lib/raven.rb +3 -0
  29. data/lib/raven/backtrace.rb +137 -0
  30. data/lib/raven/base.rb +106 -0
  31. data/lib/raven/breadcrumbs.rb +76 -0
  32. data/lib/raven/breadcrumbs/activesupport.rb +19 -0
  33. data/lib/raven/breadcrumbs/logger.rb +93 -0
  34. data/lib/raven/cli.rb +59 -0
  35. data/lib/raven/client.rb +142 -0
  36. data/lib/raven/configuration.rb +434 -0
  37. data/lib/raven/context.rb +43 -0
  38. data/lib/raven/event.rb +259 -0
  39. data/lib/raven/instance.rb +221 -0
  40. data/lib/raven/integrations/delayed_job.rb +58 -0
  41. data/lib/raven/integrations/rack-timeout.rb +19 -0
  42. data/lib/raven/integrations/rack.rb +139 -0
  43. data/lib/raven/integrations/rails.rb +79 -0
  44. data/lib/raven/integrations/rails/active_job.rb +55 -0
  45. data/lib/raven/integrations/rails/controller_methods.rb +13 -0
  46. data/lib/raven/integrations/rails/controller_transaction.rb +13 -0
  47. data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +31 -0
  48. data/lib/raven/integrations/rails/overrides/streaming_reporter.rb +23 -0
  49. data/lib/raven/integrations/railties.rb +1 -0
  50. data/lib/raven/integrations/rake.rb +18 -0
  51. data/lib/raven/integrations/sidekiq.rb +87 -0
  52. data/lib/raven/integrations/tasks.rb +11 -0
  53. data/lib/raven/interface.rb +25 -0
  54. data/lib/raven/interfaces/exception.rb +15 -0
  55. data/lib/raven/interfaces/http.rb +16 -0
  56. data/lib/raven/interfaces/message.rb +20 -0
  57. data/lib/raven/interfaces/single_exception.rb +14 -0
  58. data/lib/raven/interfaces/stack_trace.rb +69 -0
  59. data/lib/raven/linecache.rb +41 -0
  60. data/lib/raven/logger.rb +19 -0
  61. data/lib/raven/processor.rb +15 -0
  62. data/lib/raven/processor/cookies.rb +26 -0
  63. data/lib/raven/processor/http_headers.rb +55 -0
  64. data/lib/raven/processor/post_data.rb +22 -0
  65. data/lib/raven/processor/removecircularreferences.rb +17 -0
  66. data/lib/raven/processor/removestacktrace.rb +24 -0
  67. data/lib/raven/processor/sanitizedata.rb +88 -0
  68. data/lib/raven/processor/utf8conversion.rb +52 -0
  69. data/lib/raven/transports.rb +15 -0
  70. data/lib/raven/transports/dummy.rb +16 -0
  71. data/lib/raven/transports/http.rb +66 -0
  72. data/lib/raven/utils/deep_merge.rb +22 -0
  73. data/lib/raven/utils/real_ip.rb +62 -0
  74. data/lib/raven/version.rb +5 -0
  75. data/lib/sentry-raven-without-integrations.rb +1 -0
  76. data/lib/sentry-raven.rb +1 -0
  77. metadata +141 -0
@@ -0,0 +1,43 @@
1
+ require 'rbconfig'
2
+
3
+ module Raven
4
+ class Context
5
+ def self.current
6
+ Thread.current[:sentry_context] ||= new
7
+ end
8
+
9
+ def self.clear!
10
+ Thread.current[:sentry_context] = nil
11
+ end
12
+
13
+ attr_accessor :transaction, :extra, :server_os, :rack_env, :runtime, :tags, :user
14
+
15
+ def initialize
16
+ self.server_os = self.class.os_context
17
+ self.runtime = self.class.runtime_context
18
+ self.extra = { :server => { :os => server_os, :runtime => runtime } }
19
+ self.rack_env = nil
20
+ self.tags = {}
21
+ self.user = {}
22
+ self.transaction = []
23
+ end
24
+
25
+ class << self
26
+ def os_context
27
+ @os_context ||= {
28
+ :name => Raven.sys_command("uname -s") || RbConfig::CONFIG["host_os"],
29
+ :version => Raven.sys_command("uname -v"),
30
+ :build => Raven.sys_command("uname -r"),
31
+ :kernel_version => Raven.sys_command("uname -a") || Raven.sys_command("ver") # windows
32
+ }
33
+ end
34
+
35
+ def runtime_context
36
+ @runtime_context ||= {
37
+ :name => RbConfig::CONFIG["ruby_install_name"],
38
+ :version => Raven.sys_command("ruby -v")
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+ require 'socket'
3
+ require 'securerandom'
4
+
5
+ module Raven
6
+ class Event
7
+ # See Sentry server default limits at
8
+ # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
9
+ MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
10
+
11
+ SDK = { "name" => "raven-ruby", "version" => Raven::VERSION }.freeze
12
+
13
+ attr_accessor :id, :logger, :transaction, :server_name, :release, :modules,
14
+ :extra, :tags, :context, :configuration, :checksum,
15
+ :fingerprint, :environment, :server_os, :runtime,
16
+ :breadcrumbs, :user, :backtrace, :platform, :sdk
17
+ alias event_id id
18
+
19
+ attr_reader :level, :timestamp, :time_spent
20
+
21
+ def initialize(init = {})
22
+ # Set some simple default values
23
+ self.id = SecureRandom.uuid.delete("-")
24
+ self.timestamp = Time.now.utc
25
+ self.level = :error
26
+ self.logger = :ruby
27
+ self.platform = :ruby
28
+ self.sdk = SDK
29
+
30
+ # Set some attributes with empty hashes to allow merging
31
+ @interfaces = {}
32
+ self.user = {} # TODO: contexts
33
+ self.extra = {} # TODO: contexts
34
+ self.server_os = {} # TODO: contexts
35
+ self.runtime = {} # TODO: contexts
36
+ self.tags = {} # TODO: contexts
37
+
38
+ copy_initial_state
39
+
40
+ # Allow attributes to be set on the event at initialization
41
+ yield self if block_given?
42
+ init.each_pair { |key, val| public_send("#{key}=", val) }
43
+
44
+ set_core_attributes_from_configuration
45
+ set_core_attributes_from_context
46
+ end
47
+
48
+ def self.from_exception(exc, options = {}, &block)
49
+ exception_context = if exc.instance_variable_defined?(:@__raven_context)
50
+ exc.instance_variable_get(:@__raven_context)
51
+ elsif exc.respond_to?(:raven_context)
52
+ exc.raven_context
53
+ else
54
+ {}
55
+ end
56
+ options = Raven::Utils::DeepMergeHash.deep_merge(exception_context, options)
57
+
58
+ configuration = options[:configuration] || Raven.configuration
59
+ return unless configuration.exception_class_allowed?(exc)
60
+
61
+ new(options) do |evt|
62
+ evt.message = "#{exc.class}: #{exc.message}"
63
+
64
+ evt.add_exception_interface(exc)
65
+
66
+ yield evt if block
67
+ end
68
+ end
69
+
70
+ def self.from_message(message, options = {})
71
+ new(options) do |evt|
72
+ evt.message = message, options[:message_params] || []
73
+ if options[:backtrace]
74
+ evt.interface(:stacktrace) do |int|
75
+ int.frames = evt.stacktrace_interface_from(options[:backtrace])
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def message
82
+ @interfaces[:logentry] && @interfaces[:logentry].unformatted_message
83
+ end
84
+
85
+ def message=(args)
86
+ message, params = *args
87
+ interface(:message) do |int|
88
+ int.message = message.byteslice(0...MAX_MESSAGE_SIZE_IN_BYTES) # Messages limited to 10kb
89
+ int.params = params
90
+ end
91
+ end
92
+
93
+ def timestamp=(time)
94
+ @timestamp = time.is_a?(Time) ? time.strftime('%Y-%m-%dT%H:%M:%S') : time
95
+ end
96
+
97
+ def time_spent=(time)
98
+ @time_spent = time.is_a?(Float) ? (time * 1000).to_i : time
99
+ end
100
+
101
+ def level=(new_level) # needed to meet the Sentry spec
102
+ @level = new_level == "warn" || new_level == :warn ? :warning : new_level
103
+ end
104
+
105
+ def interface(name, value = nil, &block)
106
+ int = Interface.registered[name]
107
+ raise(Error, "Unknown interface: #{name}") unless int
108
+ @interfaces[int.sentry_alias] = int.new(value, &block) if value || block
109
+ @interfaces[int.sentry_alias]
110
+ end
111
+
112
+ def [](key)
113
+ interface(key)
114
+ end
115
+
116
+ def []=(key, value)
117
+ interface(key, value)
118
+ end
119
+
120
+ def to_hash
121
+ data = [:checksum, :environment, :event_id, :extra, :fingerprint, :level,
122
+ :logger, :message, :modules, :platform, :release, :sdk, :server_name,
123
+ :tags, :time_spent, :timestamp, :transaction, :user].each_with_object({}) do |att, memo|
124
+ memo[att] = public_send(att) if public_send(att)
125
+ end
126
+
127
+ data[:breadcrumbs] = @breadcrumbs.to_hash unless @breadcrumbs.empty?
128
+
129
+ @interfaces.each_pair do |name, int_data|
130
+ data[name.to_sym] = int_data.to_hash
131
+ end
132
+ data
133
+ end
134
+
135
+ def to_json_compatible
136
+ cleaned_hash = async_json_processors.reduce(to_hash) { |a, e| e.process(a) }
137
+ JSON.parse(JSON.generate(cleaned_hash))
138
+ end
139
+
140
+ def add_exception_interface(exc)
141
+ interface(:exception) do |exc_int|
142
+ exceptions = exception_chain_to_array(exc)
143
+ backtraces = Set.new
144
+ exc_int.values = exceptions.map do |e|
145
+ SingleExceptionInterface.new do |int|
146
+ int.type = e.class.to_s
147
+ int.value = e.to_s
148
+ int.module = e.class.to_s.split('::')[0...-1].join('::')
149
+
150
+ int.stacktrace =
151
+ if e.backtrace && !backtraces.include?(e.backtrace.object_id)
152
+ backtraces << e.backtrace.object_id
153
+ StacktraceInterface.new do |stacktrace|
154
+ stacktrace.frames = stacktrace_interface_from(e.backtrace)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def stacktrace_interface_from(backtrace)
163
+ Backtrace.parse(backtrace).lines.reverse.each_with_object([]) do |line, memo|
164
+ frame = StacktraceInterface::Frame.new
165
+ frame.abs_path = line.file if line.file
166
+ frame.function = line.method if line.method
167
+ frame.lineno = line.number
168
+ frame.in_app = line.in_app
169
+ frame.module = line.module_name if line.module_name
170
+
171
+ if configuration[:context_lines] && frame.abs_path
172
+ frame.pre_context, frame.context_line, frame.post_context = \
173
+ configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration[:context_lines])
174
+ end
175
+
176
+ memo << frame if frame.filename
177
+ end
178
+ end
179
+
180
+ # For cross-language compat
181
+ class << self
182
+ alias captureException from_exception
183
+ alias captureMessage from_message
184
+ alias capture_exception from_exception
185
+ alias capture_message from_message
186
+ end
187
+
188
+ private
189
+
190
+ def copy_initial_state
191
+ self.configuration = Raven.configuration
192
+ self.breadcrumbs = Raven.breadcrumbs
193
+ self.context = Raven.context
194
+ end
195
+
196
+ def set_core_attributes_from_configuration
197
+ self.server_name ||= configuration.server_name
198
+ self.release ||= configuration.release
199
+ self.modules = list_gem_specs if configuration.send_modules
200
+ self.environment ||= configuration.current_environment
201
+ end
202
+
203
+ def set_core_attributes_from_context
204
+ self.transaction ||= context.transaction.last
205
+
206
+ # If this is a Rack event, merge Rack context
207
+ add_rack_context if !self[:http] && context.rack_env
208
+
209
+ # Merge contexts
210
+ self.user = context.user.merge(user) # TODO: contexts
211
+ self.extra = context.extra.merge(extra) # TODO: contexts
212
+ self.tags = configuration.tags.merge(context.tags).merge!(tags) # TODO: contexts
213
+ end
214
+
215
+ def add_rack_context
216
+ interface :http do |int|
217
+ int.from_rack(context.rack_env)
218
+ end
219
+ context.user[:ip_address] = calculate_real_ip_from_rack
220
+ end
221
+
222
+ # When behind a proxy (or if the user is using a proxy), we can't use
223
+ # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
224
+ def calculate_real_ip_from_rack
225
+ Utils::RealIp.new(
226
+ :remote_addr => context.rack_env["REMOTE_ADDR"],
227
+ :client_ip => context.rack_env["HTTP_CLIENT_IP"],
228
+ :real_ip => context.rack_env["HTTP_X_REAL_IP"],
229
+ :forwarded_for => context.rack_env["HTTP_X_FORWARDED_FOR"]
230
+ ).calculate_ip
231
+ end
232
+
233
+ def async_json_processors
234
+ [
235
+ Raven::Processor::RemoveCircularReferences,
236
+ Raven::Processor::UTF8Conversion
237
+ ].map { |v| v.new(self) }
238
+ end
239
+
240
+ def exception_chain_to_array(exc)
241
+ if exc.respond_to?(:cause) && exc.cause
242
+ exceptions = [exc]
243
+ while exc.cause
244
+ exc = exc.cause
245
+ break if exceptions.any? { |e| e.object_id == exc.object_id }
246
+ exceptions << exc
247
+ end
248
+ exceptions.reverse!
249
+ else
250
+ [exc]
251
+ end
252
+ end
253
+
254
+ def list_gem_specs
255
+ # Older versions of Rubygems don't support iterating over all specs
256
+ Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,221 @@
1
+ module Raven
2
+ # A copy of Raven's base module class methods, minus some of the integration
3
+ # and global hooks since it's meant to be used explicitly. Useful for
4
+ # sending errors to multiple sentry projects in a large application.
5
+ #
6
+ # @example
7
+ # class Foo
8
+ # def initialize
9
+ # @other_raven = Raven::Instance.new
10
+ # @other_raven.configure do |config|
11
+ # config.server = 'http://...'
12
+ # end
13
+ # end
14
+ #
15
+ # def foo
16
+ # # ...
17
+ # rescue => e
18
+ # @other_raven.capture_exception(e)
19
+ # end
20
+ # end
21
+ class Instance
22
+ # See Raven::Client.
23
+ attr_writer :client
24
+
25
+ # See Raven::Configuration.
26
+ attr_accessor :configuration
27
+
28
+ def initialize(context = nil, config = nil)
29
+ @context = @explicit_context = context
30
+ self.configuration = config || Configuration.new
31
+ end
32
+
33
+ def context
34
+ if @explicit_context
35
+ @context ||= Context.new
36
+ else
37
+ Context.current
38
+ end
39
+ end
40
+
41
+ def logger
42
+ configuration.logger
43
+ end
44
+
45
+ # The client object is responsible for delivering formatted data to the
46
+ # Sentry server.
47
+ def client
48
+ @client ||= Client.new(configuration)
49
+ end
50
+
51
+ # Tell the log that the client is good to go
52
+ def report_status
53
+ return if configuration.silence_ready
54
+ if configuration.capture_allowed?
55
+ logger.info "Raven #{VERSION} ready to catch errors"
56
+ else
57
+ logger.info "Raven #{VERSION} configured not to capture errors: #{configuration.error_messages}"
58
+ end
59
+ end
60
+
61
+ # Call this method to modify defaults in your initializers.
62
+ #
63
+ # @example
64
+ # Raven.configure do |config|
65
+ # config.server = 'http://...'
66
+ # end
67
+ def configure
68
+ yield(configuration) if block_given?
69
+
70
+ self.client = Client.new(configuration)
71
+ report_status
72
+ client
73
+ end
74
+
75
+ # Send an event to the configured Sentry server
76
+ #
77
+ # @example
78
+ # evt = Raven::Event.new(:message => "An error")
79
+ # Raven.send_event(evt)
80
+ def send_event(event)
81
+ client.send_event(event)
82
+ end
83
+
84
+ # Capture and process any exceptions from the given block.
85
+ #
86
+ # @example
87
+ # Raven.capture do
88
+ # MyApp.run
89
+ # end
90
+ def capture(options = {})
91
+ if block_given?
92
+ begin
93
+ yield
94
+ rescue Error
95
+ raise # Don't capture Raven errors
96
+ rescue Exception => e
97
+ capture_type(e, options)
98
+ raise
99
+ end
100
+ else
101
+ install_at_exit_hook(options)
102
+ end
103
+ end
104
+
105
+ def capture_type(obj, options = {})
106
+ unless configuration.capture_allowed?(obj)
107
+ logger.debug("#{obj} excluded from capture: #{configuration.error_messages}")
108
+ return false
109
+ end
110
+
111
+ message_or_exc = obj.is_a?(String) ? "message" : "exception"
112
+ options[:configuration] = configuration
113
+ options[:context] = context
114
+ if (evt = Event.send("from_" + message_or_exc, obj, options))
115
+ yield evt if block_given?
116
+ if configuration.async?
117
+ begin
118
+ # We have to convert to a JSON-like hash, because background job
119
+ # processors (esp ActiveJob) may not like weird types in the event hash
120
+ configuration.async.call(evt.to_json_compatible)
121
+ rescue => ex
122
+ logger.error("async event sending failed: #{ex.message}")
123
+ send_event(evt)
124
+ end
125
+ else
126
+ send_event(evt)
127
+ end
128
+ Thread.current["sentry_#{object_id}_last_event_id".to_sym] = evt.id
129
+ evt
130
+ end
131
+ end
132
+
133
+ alias capture_message capture_type
134
+ alias capture_exception capture_type
135
+
136
+ def last_event_id
137
+ Thread.current["sentry_#{object_id}_last_event_id".to_sym]
138
+ end
139
+
140
+ # Provides extra context to the exception prior to it being handled by
141
+ # Raven. An exception can have multiple annotations, which are merged
142
+ # together.
143
+ #
144
+ # The options (annotation) is treated the same as the ``options``
145
+ # parameter to ``capture_exception`` or ``Event.from_exception``, and
146
+ # can contain the same ``:user``, ``:tags``, etc. options as these
147
+ # methods.
148
+ #
149
+ # These will be merged with the ``options`` parameter to
150
+ # ``Event.from_exception`` at the top of execution.
151
+ #
152
+ # @example
153
+ # begin
154
+ # raise "Hello"
155
+ # rescue => exc
156
+ # Raven.annotate_exception(exc, :user => { 'id' => 1,
157
+ # 'email' => 'foo@example.com' })
158
+ # end
159
+ def annotate_exception(exc, options = {})
160
+ notes = (exc.instance_variable_defined?(:@__raven_context) && exc.instance_variable_get(:@__raven_context)) || {}
161
+ Raven::Utils::DeepMergeHash.deep_merge!(notes, options)
162
+ exc.instance_variable_set(:@__raven_context, notes)
163
+ exc
164
+ end
165
+
166
+ # Bind user context. Merges with existing context (if any).
167
+ #
168
+ # It is recommending that you send at least the ``id`` and ``email``
169
+ # values. All other values are arbitrary.
170
+ #
171
+ # @example
172
+ # Raven.user_context('id' => 1, 'email' => 'foo@example.com')
173
+ def user_context(options = nil)
174
+ context.user = options || {}
175
+ end
176
+
177
+ # Bind tags context. Merges with existing context (if any).
178
+ #
179
+ # Tags are key / value pairs which generally represent things like
180
+ # application version, environment, role, and server names.
181
+ #
182
+ # @example
183
+ # Raven.tags_context('my_custom_tag' => 'tag_value')
184
+ def tags_context(options = nil)
185
+ context.tags.merge!(options || {})
186
+ end
187
+
188
+ # Bind extra context. Merges with existing context (if any).
189
+ #
190
+ # Extra context shows up as Additional Data within Sentry, and is
191
+ # completely arbitrary.
192
+ #
193
+ # @example
194
+ # Raven.extra_context('my_custom_data' => 'value')
195
+ def extra_context(options = nil)
196
+ context.extra.merge!(options || {})
197
+ end
198
+
199
+ def rack_context(env)
200
+ env = nil if env.empty?
201
+
202
+ context.rack_env = env
203
+ end
204
+
205
+ def breadcrumbs
206
+ BreadcrumbBuffer.current
207
+ end
208
+
209
+ private
210
+
211
+ def install_at_exit_hook(options)
212
+ at_exit do
213
+ exception = $ERROR_INFO
214
+ if exception
215
+ logger.debug "Caught a post-mortem exception: #{exception.inspect}"
216
+ capture_type(exception, options)
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end