mustwin-sentry-raven 0.11.2
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 +15 -0
- data/LICENSE +201 -0
- data/README.md +54 -0
- data/bin/raven +42 -0
- data/lib/raven.rb +3 -0
- data/lib/raven/backtrace.rb +128 -0
- data/lib/raven/base.rb +225 -0
- data/lib/raven/better_attr_accessor.rb +44 -0
- data/lib/raven/cli.rb +63 -0
- data/lib/raven/client.rb +104 -0
- data/lib/raven/configuration.rb +173 -0
- data/lib/raven/context.rb +21 -0
- data/lib/raven/error.rb +4 -0
- data/lib/raven/event.rb +223 -0
- data/lib/raven/integrations/delayed_job.rb +49 -0
- data/lib/raven/interfaces.rb +34 -0
- data/lib/raven/interfaces/exception.rb +22 -0
- data/lib/raven/interfaces/http.rb +53 -0
- data/lib/raven/interfaces/message.rb +17 -0
- data/lib/raven/interfaces/stack_trace.rb +55 -0
- data/lib/raven/linecache.rb +23 -0
- data/lib/raven/logger.rb +22 -0
- data/lib/raven/okjson.rb +609 -0
- data/lib/raven/processor.rb +28 -0
- data/lib/raven/processor/removecircularreferences.rb +17 -0
- data/lib/raven/processor/sanitizedata.rb +42 -0
- data/lib/raven/processor/utf8conversion.rb +26 -0
- data/lib/raven/rack.rb +75 -0
- data/lib/raven/rails/controller_methods.rb +13 -0
- data/lib/raven/rails/middleware/debug_exceptions_catcher.rb +16 -0
- data/lib/raven/railtie.rb +38 -0
- data/lib/raven/rake.rb +13 -0
- data/lib/raven/sidekiq.rb +27 -0
- data/lib/raven/tasks.rb +12 -0
- data/lib/raven/transports.rb +27 -0
- data/lib/raven/transports/http.rb +56 -0
- data/lib/raven/transports/udp.rb +30 -0
- data/lib/raven/version.rb +3 -0
- data/lib/sentry-raven.rb +1 -0
- metadata +168 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Raven
|
2
|
+
class Context
|
3
|
+
def self.current
|
4
|
+
Thread.current[:sentry_context] ||= new
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.clear!
|
8
|
+
Thread.current[:sentry_context] = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :extra, :tags
|
12
|
+
attr_accessor :rack_env, :user
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@extra = {}
|
16
|
+
@tags = {}
|
17
|
+
@user = {}
|
18
|
+
@rack_env = nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/raven/error.rb
ADDED
data/lib/raven/event.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'socket'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
require 'raven/error'
|
6
|
+
require 'raven/linecache'
|
7
|
+
|
8
|
+
module Raven
|
9
|
+
|
10
|
+
class Event
|
11
|
+
|
12
|
+
LOG_LEVELS = {
|
13
|
+
"debug" => 10,
|
14
|
+
"info" => 20,
|
15
|
+
"warn" => 30,
|
16
|
+
"warning" => 30,
|
17
|
+
"error" => 40,
|
18
|
+
"fatal" => 50,
|
19
|
+
}
|
20
|
+
|
21
|
+
BACKTRACE_RE = /^(.+?):(\d+)(?::in `(.+?)')?$/
|
22
|
+
|
23
|
+
PLATFORM = "ruby"
|
24
|
+
|
25
|
+
attr_reader :id
|
26
|
+
attr_accessor :project, :message, :timestamp, :time_spent, :level
|
27
|
+
attr_accessor :logger, :culprit, :server_name, :modules, :extra, :tags
|
28
|
+
|
29
|
+
def initialize(options = {}, &block)
|
30
|
+
@configuration = options[:configuration] || Raven.configuration
|
31
|
+
@interfaces = {}
|
32
|
+
|
33
|
+
context = options[:context] || Raven.context
|
34
|
+
|
35
|
+
@id = options[:id] || generate_event_id
|
36
|
+
@message = options[:message]
|
37
|
+
@timestamp = options[:timestamp] || Time.now.utc
|
38
|
+
@time_spent = options[:time_spent]
|
39
|
+
|
40
|
+
@level = options[:level] || :error
|
41
|
+
@logger = options[:logger] || 'root'
|
42
|
+
@culprit = options[:culprit]
|
43
|
+
@server_name = options[:server_name] || @configuration.server_name || get_hostname
|
44
|
+
|
45
|
+
options[:modules] ||= get_modules if @configuration.send_modules
|
46
|
+
|
47
|
+
@modules = options[:modules]
|
48
|
+
|
49
|
+
@user = options[:user] || {}
|
50
|
+
@user.merge!(context.user) if context.respond_to?(:user)
|
51
|
+
|
52
|
+
@extra = options[:extra] || {}
|
53
|
+
@extra.merge!(context.extra) if context.respond_to?(:extra)
|
54
|
+
|
55
|
+
@tags = @configuration.tags
|
56
|
+
@tags.merge!(options[:tags] || {})
|
57
|
+
@tags.merge!(context.tags) if context.respond_to?(:tags)
|
58
|
+
|
59
|
+
block.call(self) if block
|
60
|
+
|
61
|
+
if @configuration.send_in_current_environment?
|
62
|
+
if !self[:http] && context.rack_env
|
63
|
+
self.interface :http do |int|
|
64
|
+
int.from_rack(context.rack_env)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Some type coercion
|
70
|
+
@timestamp = @timestamp.strftime('%Y-%m-%dT%H:%M:%S') if @timestamp.is_a?(Time)
|
71
|
+
@time_spent = (@time_spent*1000).to_i if @time_spent.is_a?(Float)
|
72
|
+
@level = LOG_LEVELS[@level.to_s.downcase] if @level.is_a?(String) || @level.is_a?(Symbol)
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_hostname
|
76
|
+
# Try to resolve the hostname to an FQDN, but fall back to whatever the load name is
|
77
|
+
hostname = Socket.gethostname
|
78
|
+
hostname = Socket.gethostbyname(hostname).first rescue hostname
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_modules
|
82
|
+
# Older versions of Rubygems don't support iterating over all specs
|
83
|
+
Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
84
|
+
end
|
85
|
+
|
86
|
+
def interface(name, value = nil, &block)
|
87
|
+
int = Raven.find_interface(name)
|
88
|
+
raise Error.new("Unknown interface: #{name}") unless int
|
89
|
+
@interfaces[int.name] = int.new(value, &block) if value || block
|
90
|
+
@interfaces[int.name]
|
91
|
+
end
|
92
|
+
|
93
|
+
def [](key)
|
94
|
+
interface(key)
|
95
|
+
end
|
96
|
+
|
97
|
+
def []=(key, value)
|
98
|
+
interface(key, value)
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_hash
|
102
|
+
data = {
|
103
|
+
'event_id' => @id,
|
104
|
+
'message' => @message,
|
105
|
+
'timestamp' => @timestamp,
|
106
|
+
'time_spent' => @time_spent,
|
107
|
+
'level' => @level,
|
108
|
+
'project' => @project,
|
109
|
+
'logger' => @logger,
|
110
|
+
'platform' => PLATFORM,
|
111
|
+
}
|
112
|
+
data['culprit'] = @culprit if @culprit
|
113
|
+
data['server_name'] = @server_name if @server_name
|
114
|
+
data['modules'] = @modules if @modules
|
115
|
+
data['extra'] = @extra if @extra
|
116
|
+
data['tags'] = @tags if @tags
|
117
|
+
data['user'] = @user if @user
|
118
|
+
@interfaces.each_pair do |name, int_data|
|
119
|
+
data[name] = int_data.to_hash
|
120
|
+
end
|
121
|
+
data
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.from_exception(exc, options = {}, &block)
|
125
|
+
notes = exc.instance_variable_get(:@__raven_context) || {}
|
126
|
+
options = notes.merge(options)
|
127
|
+
|
128
|
+
configuration = options[:configuration] || Raven.configuration
|
129
|
+
if exc.is_a?(Raven::Error)
|
130
|
+
# Try to prevent error reporting loops
|
131
|
+
Raven.logger.info "Refusing to capture Raven error: #{exc.inspect}"
|
132
|
+
return nil
|
133
|
+
end
|
134
|
+
if configuration[:excluded_exceptions].any? { |x| (x === exc rescue false) || x == exc.class.name }
|
135
|
+
Raven.logger.info "User excluded error: #{exc.inspect}"
|
136
|
+
return nil
|
137
|
+
end
|
138
|
+
|
139
|
+
context_lines = configuration[:context_lines]
|
140
|
+
|
141
|
+
new(options) do |evt|
|
142
|
+
evt.message = "#{exc.class.to_s}: #{exc.message}"
|
143
|
+
evt.level = options[:level] || :error
|
144
|
+
|
145
|
+
evt.interface(:exception) do |int|
|
146
|
+
int.type = exc.class.to_s
|
147
|
+
int.value = exc.to_s
|
148
|
+
int.module = exc.class.to_s.split('::')[0...-1].join('::')
|
149
|
+
|
150
|
+
# TODO(dcramer): this needs cleaned up, but I couldn't figure out how to
|
151
|
+
# work Hashie as a non-Rubyist
|
152
|
+
if exc.backtrace
|
153
|
+
int.stacktrace = StacktraceInterface.new do |stacktrace|
|
154
|
+
backtrace = Backtrace.parse(exc.backtrace)
|
155
|
+
stacktrace.frames = backtrace.lines.reverse.map do |line|
|
156
|
+
stacktrace.frame do |frame|
|
157
|
+
frame.abs_path = line.file
|
158
|
+
frame.function = line.method
|
159
|
+
frame.lineno = line.number
|
160
|
+
frame.in_app = line.in_app
|
161
|
+
if context_lines && frame.abs_path
|
162
|
+
frame.pre_context, frame.context_line, frame.post_context = \
|
163
|
+
evt.get_file_context(frame.abs_path, frame.lineno, context_lines)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end.select { |f| f.filename }
|
167
|
+
|
168
|
+
evt.culprit = evt.get_culprit(stacktrace.frames)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
block.call(evt) if block
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.from_message(message, options = {})
|
178
|
+
new(options) do |evt|
|
179
|
+
evt.message = message
|
180
|
+
evt.level = options[:level] || :error
|
181
|
+
evt.interface :message do |int|
|
182
|
+
int.message = message
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Because linecache can go to hell
|
188
|
+
def self._source_lines(path, from, to)
|
189
|
+
end
|
190
|
+
|
191
|
+
def get_file_context(filename, lineno, context)
|
192
|
+
lines = (2 * context + 1).times.map do |i|
|
193
|
+
Raven::LineCache.getline(filename, lineno - context + i)
|
194
|
+
end
|
195
|
+
[lines[0..(context - 1)], lines[context], lines[(context + 1)..-1]]
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_culprit(frames)
|
199
|
+
lastframe = frames.reverse.find { |f| f.in_app } || frames.last
|
200
|
+
"#{lastframe.filename} in #{lastframe.function} at line #{lastframe.lineno}" if lastframe
|
201
|
+
end
|
202
|
+
|
203
|
+
# For cross-language compat
|
204
|
+
class << self
|
205
|
+
alias :captureException :from_exception
|
206
|
+
alias :captureMessage :from_message
|
207
|
+
alias :capture_exception :from_exception
|
208
|
+
alias :capture_message :from_message
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
def generate_event_id
|
214
|
+
# generate a uuid. copy-pasted from SecureRandom, this method is not
|
215
|
+
# available in <1.9.
|
216
|
+
ary = SecureRandom.random_bytes(16).unpack("NnnnnN")
|
217
|
+
ary[2] = (ary[2] & 0x0fff) | 0x4000
|
218
|
+
ary[3] = (ary[3] & 0x3fff) | 0x8000
|
219
|
+
uuid = "%08x-%04x-%04x-%04x-%04x%08x" % ary
|
220
|
+
Digest::MD5.hexdigest(uuid)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'delayed_job'
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
module Plugins
|
5
|
+
|
6
|
+
class Raven < ::Delayed::Plugin
|
7
|
+
callbacks do |lifecycle|
|
8
|
+
lifecycle.around(:invoke_job) do |job, *args, &block|
|
9
|
+
begin
|
10
|
+
# Forward the call to the next callback in the callback chain
|
11
|
+
block.call(job, *args)
|
12
|
+
|
13
|
+
rescue Exception => exception
|
14
|
+
# Log error to Sentry
|
15
|
+
::Raven.capture_exception(exception,
|
16
|
+
:logger => 'delayed_job',
|
17
|
+
:tags => {
|
18
|
+
:delayed_job_queue => job.queue
|
19
|
+
},
|
20
|
+
:extra => {
|
21
|
+
:delayed_job => {
|
22
|
+
:id => job.id,
|
23
|
+
:priority => job.priority,
|
24
|
+
:attempts => job.attempts,
|
25
|
+
:handler => job.handler,
|
26
|
+
:last_error => job.last_error,
|
27
|
+
:run_at => job.run_at,
|
28
|
+
:locked_at => job.locked_at,
|
29
|
+
#failed_at => job.failed_at,
|
30
|
+
:locked_by => job.locked_by,
|
31
|
+
:queue => job.queue,
|
32
|
+
:created_at => job.created_at
|
33
|
+
}
|
34
|
+
})
|
35
|
+
|
36
|
+
# Make sure we propagate the failure!
|
37
|
+
raise exception
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Register DelayedJob Raven plugin
|
48
|
+
#
|
49
|
+
Delayed::Worker.plugins << Delayed::Plugins::Raven
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'raven/better_attr_accessor'
|
2
|
+
|
3
|
+
module Raven
|
4
|
+
|
5
|
+
INTERFACES = {}
|
6
|
+
|
7
|
+
class Interface
|
8
|
+
include BetterAttrAccessor
|
9
|
+
alias_method :to_hash, :attributes
|
10
|
+
|
11
|
+
def initialize(attributes = nil)
|
12
|
+
attributes.each do |attr, value|
|
13
|
+
send "#{attr}=", value
|
14
|
+
end if attributes
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.name(value = nil)
|
20
|
+
@interface_name ||= value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.register_interface(mapping)
|
25
|
+
mapping.each_pair do |key, klass|
|
26
|
+
INTERFACES[key.to_s] = klass
|
27
|
+
INTERFACES[klass.name] = klass
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.find_interface(name)
|
32
|
+
INTERFACES[name.to_s]
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'raven/interfaces'
|
2
|
+
|
3
|
+
module Raven
|
4
|
+
class ExceptionInterface < Interface
|
5
|
+
|
6
|
+
name 'exception'
|
7
|
+
attr_accessor :type
|
8
|
+
attr_accessor :value
|
9
|
+
attr_accessor :module
|
10
|
+
attr_accessor :stacktrace
|
11
|
+
|
12
|
+
def to_hash(*args)
|
13
|
+
data = super(*args)
|
14
|
+
if data['stacktrace']
|
15
|
+
data['stacktrace'] = data['stacktrace'].to_hash
|
16
|
+
end
|
17
|
+
data
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
register_interface :exception => ExceptionInterface
|
22
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'raven/interfaces'
|
2
|
+
|
3
|
+
module Raven
|
4
|
+
class HttpInterface < Interface
|
5
|
+
|
6
|
+
name 'request'
|
7
|
+
attr_accessor :url
|
8
|
+
attr_accessor :method
|
9
|
+
attr_accessor :data
|
10
|
+
attr_accessor :query_string
|
11
|
+
attr_accessor :cookies
|
12
|
+
attr_accessor :headers
|
13
|
+
attr_accessor :env
|
14
|
+
|
15
|
+
def initialize(*arguments)
|
16
|
+
self.headers = {}
|
17
|
+
self.env = {}
|
18
|
+
super(*arguments)
|
19
|
+
end
|
20
|
+
|
21
|
+
def from_rack(env)
|
22
|
+
require 'rack'
|
23
|
+
req = ::Rack::Request.new(env)
|
24
|
+
self.url = req.url.split('?').first
|
25
|
+
self.method = req.request_method
|
26
|
+
self.query_string = req.query_string
|
27
|
+
env.each_pair do |key, value|
|
28
|
+
next unless key.upcase == key # Non-upper case stuff isn't either
|
29
|
+
if key.start_with?('HTTP_')
|
30
|
+
# Header
|
31
|
+
http_key = key[5..key.length - 1].split('_').map { |s| s.capitalize }.join('-')
|
32
|
+
self.headers[http_key] = value.to_s
|
33
|
+
elsif ['CONTENT_TYPE', 'CONTENT_LENGTH'].include? key
|
34
|
+
self.headers[key.capitalize] = value.to_s
|
35
|
+
elsif ['REMOTE_ADDR', 'SERVER_NAME', 'SERVER_PORT'].include? key
|
36
|
+
# Environment
|
37
|
+
self.env[key] = value.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
self.data =
|
42
|
+
if req.form_data?
|
43
|
+
req.POST
|
44
|
+
elsif req.body
|
45
|
+
data = req.body.read
|
46
|
+
req.body.rewind
|
47
|
+
data
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
register_interface :http => HttpInterface
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'raven/interfaces'
|
2
|
+
|
3
|
+
module Raven
|
4
|
+
class MessageInterface < Interface
|
5
|
+
|
6
|
+
name 'sentry.interfaces.Message'
|
7
|
+
attr_accessor :message
|
8
|
+
attr_accessor :params
|
9
|
+
|
10
|
+
def initialize(*arguments)
|
11
|
+
self.params = []
|
12
|
+
super(*arguments)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
register_interface :message => MessageInterface
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'raven/interfaces'
|
2
|
+
|
3
|
+
module Raven
|
4
|
+
class StacktraceInterface < Interface
|
5
|
+
|
6
|
+
name 'stacktrace'
|
7
|
+
attr_accessor :frames, :default => []
|
8
|
+
|
9
|
+
def initialize(*arguments)
|
10
|
+
self.frames = []
|
11
|
+
super(*arguments)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash(*args)
|
15
|
+
data = super(*args)
|
16
|
+
data['frames'] = data['frames'].map { |frame| frame.to_hash }
|
17
|
+
data
|
18
|
+
end
|
19
|
+
|
20
|
+
def frame(attributes = nil, &block)
|
21
|
+
Frame.new(attributes, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Not actually an interface, but I want to use the same style
|
25
|
+
class Frame < Interface
|
26
|
+
attr_accessor :abs_path
|
27
|
+
attr_accessor :function
|
28
|
+
attr_accessor :vars, :default => []
|
29
|
+
attr_accessor :pre_context, :default => []
|
30
|
+
attr_accessor :post_context, :default => []
|
31
|
+
attr_accessor :context_line
|
32
|
+
attr_accessor :lineno
|
33
|
+
attr_accessor :in_app
|
34
|
+
|
35
|
+
def filename
|
36
|
+
return nil if self.abs_path.nil?
|
37
|
+
|
38
|
+
prefix = $LOAD_PATH.select { |s| self.abs_path.start_with?(s.to_s) }.sort_by { |s| s.to_s.length }.last
|
39
|
+
prefix ? self.abs_path[prefix.to_s.chomp(File::SEPARATOR).length+1..-1] : self.abs_path
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_hash(*args)
|
43
|
+
data = super(*args)
|
44
|
+
data['filename'] = self.filename
|
45
|
+
data.delete('vars') unless self.vars && !self.vars.empty?
|
46
|
+
data.delete('pre_context') unless self.pre_context && !self.pre_context.empty?
|
47
|
+
data.delete('post_context') unless self.post_context && !self.post_context.empty?
|
48
|
+
data.delete('context_line') unless self.context_line && !self.context_line.empty?
|
49
|
+
data
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
register_interface :stack_trace => StacktraceInterface
|
55
|
+
end
|