batbugger 0.0.1 → 1.2.0
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.
- data/.gitignore +1 -0
- data/Gemfile.lock +2 -4
- data/README.md +1 -1
- data/batbugger.gemspec +1 -1
- data/lib/batbugger/backtrace.rb +136 -0
- data/lib/batbugger/configuration.rb +204 -0
- data/lib/batbugger/notice.rb +306 -0
- data/lib/batbugger/rack.rb +57 -0
- data/lib/batbugger/rails/controller_methods.rb +70 -0
- data/lib/batbugger/rails/middleware/exceptions_catcher.rb +29 -0
- data/lib/batbugger/railtie.rb +33 -0
- data/lib/batbugger/sender.rb +114 -0
- data/lib/batbugger/version.rb +1 -1
- data/lib/batbugger.rb +124 -9
- metadata +18 -10
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
BatBugger
|
2
2
|
===============
|
3
3
|
|
4
|
-
This is the notifier gem for integrating apps with the :
|
4
|
+
This is the notifier gem for integrating apps with the : [BatBugger Exception Notifier for Ruby and Rails](http://batbugger.io).
|
5
5
|
Developed and Maintained by grepruby team: [Ruby and Rails Expert Team](http://grepruby.com).
|
6
6
|
|
7
7
|
## Installation
|
data/batbugger.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "honeybadger", ">=1.6.0"
|
22
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_dependency('json')
|
23
23
|
spec.add_development_dependency "rake"
|
24
24
|
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Batbugger
|
2
|
+
class Backtrace
|
3
|
+
|
4
|
+
class Line
|
5
|
+
INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
|
6
|
+
|
7
|
+
attr_reader :file
|
8
|
+
|
9
|
+
attr_reader :number
|
10
|
+
|
11
|
+
attr_reader :method
|
12
|
+
|
13
|
+
attr_reader :filtered_file, :filtered_number, :filtered_method
|
14
|
+
|
15
|
+
def self.parse(unparsed_line, opts = {})
|
16
|
+
filters = opts[:filters] || []
|
17
|
+
filtered_line = filters.inject(unparsed_line) do |line, proc|
|
18
|
+
proc.call(line)
|
19
|
+
end
|
20
|
+
|
21
|
+
if filtered_line
|
22
|
+
_, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
|
23
|
+
_, *filtered_args = filtered_line.match(INPUT_FORMAT).to_a
|
24
|
+
new(file, number, method, *filtered_args)
|
25
|
+
else
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(file, number, method, filtered_file = file,
|
31
|
+
filtered_number = number, filtered_method = method)
|
32
|
+
self.filtered_file = filtered_file
|
33
|
+
self.filtered_number = filtered_number
|
34
|
+
self.filtered_method = filtered_method
|
35
|
+
self.file = file
|
36
|
+
self.number = number
|
37
|
+
self.method = method
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"#{filtered_file}:#{filtered_number}:in `#{filtered_method}'"
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
to_s == other.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
"<Line:#{to_s}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
def application?
|
53
|
+
(filtered_file =~ /^\[PROJECT_ROOT\]/i) && !(filtered_file =~ /^\[PROJECT_ROOT\]\/vendor/i)
|
54
|
+
end
|
55
|
+
|
56
|
+
def source(radius = 2)
|
57
|
+
@source ||= get_source(file, number, radius)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_writer :file, :number, :method, :filtered_file, :filtered_number, :filtered_method
|
63
|
+
|
64
|
+
def get_source(file, number, radius = 2)
|
65
|
+
if file && File.exists?(file)
|
66
|
+
before = after = radius
|
67
|
+
start = (number.to_i - 1) - before
|
68
|
+
start = 0 and before = 1 if start <= 0
|
69
|
+
duration = before + 1 + after
|
70
|
+
|
71
|
+
l = 0
|
72
|
+
File.open(file) do |f|
|
73
|
+
start.times { f.gets ; l += 1 }
|
74
|
+
return Hash[duration.times.map { (line = f.gets) ? [(l += 1), line] : nil }.compact]
|
75
|
+
end
|
76
|
+
else
|
77
|
+
{}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
attr_reader :lines, :application_lines
|
83
|
+
|
84
|
+
def self.parse(ruby_backtrace, opts = {})
|
85
|
+
ruby_lines = split_multiline_backtrace(ruby_backtrace)
|
86
|
+
|
87
|
+
lines = ruby_lines.collect do |unparsed_line|
|
88
|
+
Line.parse(unparsed_line, opts)
|
89
|
+
end.compact
|
90
|
+
|
91
|
+
instance = new(lines)
|
92
|
+
end
|
93
|
+
|
94
|
+
def initialize(lines)
|
95
|
+
self.lines = lines
|
96
|
+
self.application_lines = lines.select(&:application?)
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_ary
|
100
|
+
lines.map { |l| { :number => l.filtered_number, :file => l.filtered_file, :method => l.filtered_method } }
|
101
|
+
end
|
102
|
+
alias :to_a :to_ary
|
103
|
+
|
104
|
+
def as_json(options = {})
|
105
|
+
to_ary
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_json(*a)
|
109
|
+
as_json.to_json(*a)
|
110
|
+
end
|
111
|
+
|
112
|
+
def inspect
|
113
|
+
"<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
|
114
|
+
end
|
115
|
+
|
116
|
+
def ==(other)
|
117
|
+
if other.respond_to?(:to_json)
|
118
|
+
to_json == other.to_json
|
119
|
+
else
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
attr_writer :lines, :application_lines
|
127
|
+
|
128
|
+
def self.split_multiline_backtrace(backtrace)
|
129
|
+
if backtrace.to_a.size == 1
|
130
|
+
backtrace.to_a.first.split(/\n\s*/)
|
131
|
+
else
|
132
|
+
backtrace
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Batbugger
|
2
|
+
class Configuration
|
3
|
+
OPTIONS = [:api_key, :backtrace_filters, :development_environments, :environment_name,
|
4
|
+
:host, :http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters,
|
5
|
+
:ignore_user_agent, :notifier_name, :notifier_url, :notifier_version,
|
6
|
+
:params_filters, :project_root, :port, :protocol, :proxy_host, :proxy_pass,
|
7
|
+
:proxy_port, :proxy_user, :secure, :use_system_ssl_cert_chain, :framework,
|
8
|
+
:user_information, :rescue_rake_exceptions, :source_extract_radius,
|
9
|
+
:send_request_session, :debug].freeze
|
10
|
+
|
11
|
+
attr_accessor :api_key
|
12
|
+
|
13
|
+
attr_accessor :host
|
14
|
+
|
15
|
+
attr_accessor :port
|
16
|
+
|
17
|
+
attr_accessor :secure
|
18
|
+
|
19
|
+
attr_accessor :use_system_ssl_cert_chain
|
20
|
+
|
21
|
+
attr_accessor :http_open_timeout
|
22
|
+
|
23
|
+
attr_accessor :http_read_timeout
|
24
|
+
|
25
|
+
attr_accessor :proxy_host
|
26
|
+
|
27
|
+
attr_accessor :proxy_port
|
28
|
+
|
29
|
+
attr_accessor :proxy_user
|
30
|
+
|
31
|
+
attr_accessor :proxy_pass
|
32
|
+
|
33
|
+
attr_reader :params_filters
|
34
|
+
|
35
|
+
attr_reader :backtrace_filters
|
36
|
+
|
37
|
+
attr_reader :ignore_by_filters
|
38
|
+
|
39
|
+
attr_reader :ignore
|
40
|
+
|
41
|
+
attr_reader :ignore_user_agent
|
42
|
+
|
43
|
+
attr_accessor :development_environments
|
44
|
+
|
45
|
+
attr_accessor :environment_name
|
46
|
+
|
47
|
+
attr_accessor :project_root
|
48
|
+
|
49
|
+
attr_accessor :notifier_name
|
50
|
+
|
51
|
+
attr_accessor :notifier_version
|
52
|
+
|
53
|
+
attr_accessor :notifier_url
|
54
|
+
|
55
|
+
attr_accessor :logger
|
56
|
+
|
57
|
+
attr_accessor :user_information
|
58
|
+
|
59
|
+
attr_accessor :framework
|
60
|
+
|
61
|
+
attr_accessor :rescue_rake_exceptions
|
62
|
+
|
63
|
+
attr_accessor :source_extract_radius
|
64
|
+
|
65
|
+
attr_accessor :send_request_session
|
66
|
+
|
67
|
+
attr_accessor :debug
|
68
|
+
|
69
|
+
attr_writer :async
|
70
|
+
|
71
|
+
DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
|
72
|
+
|
73
|
+
DEFAULT_BACKTRACE_FILTERS = [
|
74
|
+
lambda { |line|
|
75
|
+
if defined?(Batbugger.configuration.project_root) && Batbugger.configuration.project_root.to_s != ''
|
76
|
+
line.sub(/#{Batbugger.configuration.project_root}/, "[PROJECT_ROOT]")
|
77
|
+
else
|
78
|
+
line
|
79
|
+
end
|
80
|
+
},
|
81
|
+
lambda { |line| line.gsub(/^\.\//, "") },
|
82
|
+
lambda { |line|
|
83
|
+
if defined?(Gem)
|
84
|
+
Gem.path.inject(line) do |line, path|
|
85
|
+
line.gsub(/#{path}/, "[GEM_ROOT]")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
},
|
89
|
+
lambda { |line| line if line !~ %r{lib/batbugger} }
|
90
|
+
].freeze
|
91
|
+
|
92
|
+
IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
|
93
|
+
'ActionController::RoutingError',
|
94
|
+
'ActionController::InvalidAuthenticityToken',
|
95
|
+
'CGI::Session::CookieStore::TamperedWithCookie',
|
96
|
+
'ActionController::UnknownAction',
|
97
|
+
'AbstractController::ActionNotFound',
|
98
|
+
'Mongoid::Errors::DocumentNotFound']
|
99
|
+
|
100
|
+
alias_method :secure?, :secure
|
101
|
+
alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
|
102
|
+
|
103
|
+
def initialize
|
104
|
+
@secure = true
|
105
|
+
@use_system_ssl_cert_chain = false
|
106
|
+
@host = 'batbugger.io'
|
107
|
+
@http_open_timeout = 2
|
108
|
+
@http_read_timeout = 5
|
109
|
+
@params_filters = DEFAULT_PARAMS_FILTERS.dup
|
110
|
+
@backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup
|
111
|
+
@ignore_by_filters = []
|
112
|
+
@ignore = IGNORE_DEFAULT.dup
|
113
|
+
@ignore_user_agent = []
|
114
|
+
@development_environments = %w(development test cucumber)
|
115
|
+
@notifier_name = 'Batbugger Notifier'
|
116
|
+
@notifier_version = VERSION
|
117
|
+
@notifier_url = 'https://github.com/grepruby/batbugger'
|
118
|
+
@framework = 'Standalone'
|
119
|
+
@user_information = 'Batbugger Error {{error_id}}'
|
120
|
+
@rescue_rake_exceptions = nil
|
121
|
+
@source_extract_radius = 2
|
122
|
+
@send_request_session = true
|
123
|
+
@debug = false
|
124
|
+
end
|
125
|
+
|
126
|
+
def filter_backtrace(&block)
|
127
|
+
self.backtrace_filters << block
|
128
|
+
end
|
129
|
+
|
130
|
+
def ignore_by_filter(&block)
|
131
|
+
self.ignore_by_filters << block
|
132
|
+
end
|
133
|
+
|
134
|
+
def ignore_only=(names)
|
135
|
+
@ignore = [names].flatten
|
136
|
+
end
|
137
|
+
|
138
|
+
def ignore_user_agent_only=(names)
|
139
|
+
@ignore_user_agent = [names].flatten
|
140
|
+
end
|
141
|
+
|
142
|
+
def [](option)
|
143
|
+
send(option)
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_hash
|
147
|
+
OPTIONS.inject({}) do |hash, option|
|
148
|
+
hash[option.to_sym] = self.send(option)
|
149
|
+
hash
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def merge(hash)
|
154
|
+
to_hash.merge(hash)
|
155
|
+
end
|
156
|
+
|
157
|
+
def public?
|
158
|
+
!development_environments.include?(environment_name)
|
159
|
+
end
|
160
|
+
|
161
|
+
def async
|
162
|
+
@async = Proc.new if block_given?
|
163
|
+
@async
|
164
|
+
end
|
165
|
+
alias :async? :async
|
166
|
+
|
167
|
+
def port
|
168
|
+
@port || default_port
|
169
|
+
end
|
170
|
+
|
171
|
+
def protocol
|
172
|
+
if secure?
|
173
|
+
'https'
|
174
|
+
else
|
175
|
+
'http'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def ca_bundle_path
|
180
|
+
if use_system_ssl_cert_chain? && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
|
181
|
+
OpenSSL::X509::DEFAULT_CERT_FILE
|
182
|
+
else
|
183
|
+
local_cert_path
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def local_cert_path
|
188
|
+
File.expand_path(File.join("..", "..", "..", "resources", "ca-bundle.crt"), __FILE__)
|
189
|
+
end
|
190
|
+
|
191
|
+
def current_user_method=(null) ; end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def default_port
|
196
|
+
if secure?
|
197
|
+
443
|
198
|
+
else
|
199
|
+
80
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Batbugger
|
4
|
+
class Notice
|
5
|
+
attr_reader :exception
|
6
|
+
|
7
|
+
attr_reader :backtrace
|
8
|
+
|
9
|
+
attr_reader :error_class
|
10
|
+
|
11
|
+
attr_reader :source_extract
|
12
|
+
|
13
|
+
attr_reader :source_extract_radius
|
14
|
+
|
15
|
+
attr_reader :environment_name
|
16
|
+
|
17
|
+
attr_reader :cgi_data
|
18
|
+
|
19
|
+
attr_reader :error_message
|
20
|
+
|
21
|
+
attr_reader :send_request_session
|
22
|
+
|
23
|
+
attr_reader :backtrace_filters
|
24
|
+
|
25
|
+
attr_reader :params_filters
|
26
|
+
|
27
|
+
attr_reader :parameters
|
28
|
+
alias_method :params, :parameters
|
29
|
+
|
30
|
+
attr_reader :component
|
31
|
+
alias_method :controller, :component
|
32
|
+
|
33
|
+
attr_reader :action
|
34
|
+
|
35
|
+
attr_reader :session_data
|
36
|
+
|
37
|
+
attr_reader :context
|
38
|
+
|
39
|
+
attr_reader :project_root
|
40
|
+
|
41
|
+
attr_reader :url
|
42
|
+
|
43
|
+
attr_reader :ignore
|
44
|
+
|
45
|
+
attr_reader :ignore_by_filters
|
46
|
+
|
47
|
+
attr_reader :notifier_name
|
48
|
+
|
49
|
+
attr_reader :notifier_version
|
50
|
+
|
51
|
+
attr_reader :notifier_url
|
52
|
+
|
53
|
+
attr_reader :hostname
|
54
|
+
|
55
|
+
def initialize(args)
|
56
|
+
self.args = args
|
57
|
+
self.exception = args[:exception]
|
58
|
+
self.project_root = args[:project_root]
|
59
|
+
self.url = args[:url] || rack_env(:url)
|
60
|
+
|
61
|
+
self.notifier_name = args[:notifier_name]
|
62
|
+
self.notifier_version = args[:notifier_version]
|
63
|
+
self.notifier_url = args[:notifier_url]
|
64
|
+
|
65
|
+
self.ignore = args[:ignore] || []
|
66
|
+
self.ignore_by_filters = args[:ignore_by_filters] || []
|
67
|
+
self.backtrace_filters = args[:backtrace_filters] || []
|
68
|
+
self.params_filters = args[:params_filters] || []
|
69
|
+
self.parameters = args[:parameters] ||
|
70
|
+
action_dispatch_params ||
|
71
|
+
rack_env(:params) ||
|
72
|
+
{}
|
73
|
+
self.component = args[:component] || args[:controller] || parameters['controller']
|
74
|
+
self.action = args[:action] || parameters['action']
|
75
|
+
|
76
|
+
self.environment_name = args[:environment_name]
|
77
|
+
self.cgi_data = args[:cgi_data] || args[:rack_env]
|
78
|
+
self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller), :filters => self.backtrace_filters)
|
79
|
+
self.error_class = exception_attribute(:error_class) {|exception| exception.class.name }
|
80
|
+
self.error_message = exception_attribute(:error_message, 'Notification') do |exception|
|
81
|
+
"#{exception.class.name}: #{exception.message}"
|
82
|
+
end
|
83
|
+
|
84
|
+
self.hostname = local_hostname
|
85
|
+
|
86
|
+
self.source_extract_radius = args[:source_extract_radius] || 2
|
87
|
+
self.source_extract = extract_source_from_backtrace
|
88
|
+
|
89
|
+
self.send_request_session = args[:send_request_session].nil? ? true : args[:send_request_session]
|
90
|
+
|
91
|
+
also_use_rack_params_filters
|
92
|
+
find_session_data
|
93
|
+
clean_params
|
94
|
+
clean_rack_request_data
|
95
|
+
set_context
|
96
|
+
end
|
97
|
+
|
98
|
+
def deliver
|
99
|
+
Batbugger.sender.send_to_batbugger(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
def as_json(options = {})
|
103
|
+
{
|
104
|
+
:notifier => {
|
105
|
+
:name => notifier_name,
|
106
|
+
:url => notifier_url,
|
107
|
+
:version => notifier_version,
|
108
|
+
:language => 'ruby'
|
109
|
+
},
|
110
|
+
:error => {
|
111
|
+
:class => error_class,
|
112
|
+
:message => error_message,
|
113
|
+
:backtrace => backtrace,
|
114
|
+
:source => source_extract
|
115
|
+
},
|
116
|
+
:request => {
|
117
|
+
:url => url,
|
118
|
+
:component => component,
|
119
|
+
:action => action,
|
120
|
+
:params => parameters,
|
121
|
+
:session => session_data,
|
122
|
+
:cgi_data => cgi_data,
|
123
|
+
:context => context
|
124
|
+
},
|
125
|
+
:server => {
|
126
|
+
:project_root => project_root,
|
127
|
+
:environment_name => environment_name,
|
128
|
+
:hostname => hostname
|
129
|
+
}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_json(*a)
|
134
|
+
as_json.to_json(*a)
|
135
|
+
end
|
136
|
+
|
137
|
+
def ignore_by_class?(ignored_class = nil)
|
138
|
+
@ignore_by_class ||= Proc.new do |ignored_class|
|
139
|
+
case error_class
|
140
|
+
when (ignored_class.respond_to?(:name) ? ignored_class.name : ignored_class)
|
141
|
+
true
|
142
|
+
else
|
143
|
+
exception && ignored_class.is_a?(Class) && exception.class < ignored_class
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
ignored_class ? @ignore_by_class.call(ignored_class) : @ignore_by_class
|
148
|
+
end
|
149
|
+
|
150
|
+
def ignore?
|
151
|
+
ignore.any?(&ignore_by_class?) ||
|
152
|
+
ignore_by_filters.any? {|filter| filter.call(self) }
|
153
|
+
end
|
154
|
+
|
155
|
+
def [](method)
|
156
|
+
case method
|
157
|
+
when :request
|
158
|
+
self
|
159
|
+
else
|
160
|
+
send(method)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
attr_writer :exception, :backtrace, :error_class, :error_message,
|
167
|
+
:backtrace_filters, :parameters, :params_filters, :environment_filters,
|
168
|
+
:session_data, :project_root, :url, :ignore, :ignore_by_filters,
|
169
|
+
:notifier_name, :notifier_url, :notifier_version, :component, :action,
|
170
|
+
:cgi_data, :environment_name, :hostname, :context, :source_extract,
|
171
|
+
:source_extract_radius, :send_request_session
|
172
|
+
|
173
|
+
attr_accessor :args
|
174
|
+
|
175
|
+
def exception_attribute(attribute, default = nil, &block)
|
176
|
+
(exception && from_exception(attribute, &block)) || args[attribute] || default
|
177
|
+
end
|
178
|
+
|
179
|
+
def from_exception(attribute)
|
180
|
+
if block_given?
|
181
|
+
yield(exception)
|
182
|
+
else
|
183
|
+
exception.send(attribute)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def clean_unserializable_data_from(attribute)
|
188
|
+
self.send(:"#{attribute}=", clean_unserializable_data(send(attribute)))
|
189
|
+
end
|
190
|
+
|
191
|
+
def clean_unserializable_data(data, stack = [])
|
192
|
+
return "[possible infinite recursion halted]" if stack.any?{|item| item == data.object_id }
|
193
|
+
|
194
|
+
if data.respond_to?(:to_hash)
|
195
|
+
data.to_hash.inject({}) do |result, (key, value)|
|
196
|
+
result.merge(key => clean_unserializable_data(value, stack + [data.object_id]))
|
197
|
+
end
|
198
|
+
elsif data.respond_to?(:to_ary)
|
199
|
+
data.to_ary.collect do |value|
|
200
|
+
clean_unserializable_data(value, stack + [data.object_id])
|
201
|
+
end
|
202
|
+
else
|
203
|
+
data.to_s
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def clean_params
|
208
|
+
clean_unserializable_data_from(:parameters)
|
209
|
+
filter(parameters)
|
210
|
+
if cgi_data
|
211
|
+
clean_unserializable_data_from(:cgi_data)
|
212
|
+
filter(cgi_data)
|
213
|
+
end
|
214
|
+
if session_data
|
215
|
+
clean_unserializable_data_from(:session_data)
|
216
|
+
filter(session_data)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def clean_rack_request_data
|
221
|
+
if cgi_data
|
222
|
+
cgi_data.delete("rack.request.form_vars")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def extract_source_from_backtrace
|
227
|
+
if backtrace.lines.empty?
|
228
|
+
nil
|
229
|
+
else
|
230
|
+
if exception.respond_to?(:source_extract)
|
231
|
+
Hash[exception_attribute(:source_extract).split("\n").map do |line|
|
232
|
+
parts = line.split(': ')
|
233
|
+
[parts[0].strip, parts[1] || '']
|
234
|
+
end]
|
235
|
+
elsif backtrace.application_lines.any?
|
236
|
+
backtrace.application_lines.first.source(source_extract_radius)
|
237
|
+
else
|
238
|
+
backtrace.lines.first.source(source_extract_radius)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def filter(hash)
|
244
|
+
if params_filters
|
245
|
+
hash.each do |key, value|
|
246
|
+
if filter_key?(key)
|
247
|
+
hash[key] = "[FILTERED]"
|
248
|
+
elsif value.respond_to?(:to_hash)
|
249
|
+
filter(hash[key])
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def filter_key?(key)
|
256
|
+
params_filters.any? do |filter|
|
257
|
+
key.to_s.eql?(filter.to_s)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def find_session_data
|
262
|
+
if send_request_session
|
263
|
+
self.session_data = args[:session_data] || args[:session] || rack_session || {}
|
264
|
+
self.session_data = session_data[:data] if session_data[:data]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def set_context
|
269
|
+
self.context = Thread.current[:batbugger_context] || {}
|
270
|
+
self.context.merge!(args[:context]) if args[:context]
|
271
|
+
self.context = nil if context.empty?
|
272
|
+
end
|
273
|
+
|
274
|
+
def rack_env(method)
|
275
|
+
rack_request.send(method) if rack_request
|
276
|
+
end
|
277
|
+
|
278
|
+
def rack_request
|
279
|
+
@rack_request ||= if args[:rack_env]
|
280
|
+
::Rack::Request.new(args[:rack_env])
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def action_dispatch_params
|
285
|
+
args[:rack_env]['action_dispatch.request.parameters'] if args[:rack_env]
|
286
|
+
end
|
287
|
+
|
288
|
+
def rack_session
|
289
|
+
args[:rack_env]['rack.session'] if args[:rack_env]
|
290
|
+
end
|
291
|
+
|
292
|
+
# Private: (Rails 3+) Adds params filters to filter list
|
293
|
+
#
|
294
|
+
# Returns nothing
|
295
|
+
def also_use_rack_params_filters
|
296
|
+
if cgi_data
|
297
|
+
@params_filters ||= []
|
298
|
+
@params_filters += cgi_data['action_dispatch.parameter_filter'] || []
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def local_hostname
|
303
|
+
Socket.gethostname
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Batbugger
|
2
|
+
# Middleware for Rack applications. Any errors raised by the upstream
|
3
|
+
# application will be delivered to Batbugger and re-raised.
|
4
|
+
#
|
5
|
+
# Synopsis:
|
6
|
+
#
|
7
|
+
# require 'rack'
|
8
|
+
# require 'batbugger'
|
9
|
+
#
|
10
|
+
# Batbugger.configure do |config|
|
11
|
+
# config.api_key = 'my_api_key'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# app = Rack::Builder.app do
|
15
|
+
# run lambda { |env| raise "Rack down" }
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# use Batbugger::Rack
|
19
|
+
# run app
|
20
|
+
#
|
21
|
+
# Use a standard Batbugger.configure call to configure your api key.
|
22
|
+
class Rack
|
23
|
+
def initialize(app)
|
24
|
+
@app = app
|
25
|
+
end
|
26
|
+
|
27
|
+
def ignored_user_agent?(env)
|
28
|
+
true if Batbugger.
|
29
|
+
configuration.
|
30
|
+
ignore_user_agent.
|
31
|
+
flatten.
|
32
|
+
any? { |ua| ua === env['HTTP_USER_AGENT'] }
|
33
|
+
end
|
34
|
+
|
35
|
+
def notify_batbugger(exception,env)
|
36
|
+
Batbugger.notify_or_ignore(exception, :rack_env => env) unless ignored_user_agent?(env)
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(env)
|
40
|
+
begin
|
41
|
+
response = @app.call(env)
|
42
|
+
rescue Exception => raised
|
43
|
+
env['batbugger.error_id'] = notify_batbugger(raised, env)
|
44
|
+
raise
|
45
|
+
ensure
|
46
|
+
Batbugger.context.clear!
|
47
|
+
end
|
48
|
+
|
49
|
+
framework_exception = env['rack.exception'] || env['sinatra.error']
|
50
|
+
if framework_exception
|
51
|
+
env['batbugger.error_id'] = notify_batbugger(framework_exception, env)
|
52
|
+
end
|
53
|
+
|
54
|
+
response
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Batbugger
|
2
|
+
module Rails
|
3
|
+
module ControllerMethods
|
4
|
+
def batbugger_request_data
|
5
|
+
{ :parameters => batbugger_filter_if_filtering(params.to_hash),
|
6
|
+
:session_data => batbugger_filter_if_filtering(batbugger_session_data),
|
7
|
+
:controller => params[:controller],
|
8
|
+
:action => params[:action],
|
9
|
+
:url => batbugger_request_url,
|
10
|
+
:cgi_data => batbugger_filter_if_filtering(request.env) }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# This method should be used for sending manual notifications while you are still
|
16
|
+
# inside the controller. Otherwise it works like Batbugger.notify.
|
17
|
+
def notify_batbugger(hash_or_exception)
|
18
|
+
unless batbugger_local_request?
|
19
|
+
Batbugger.notify(hash_or_exception, batbugger_request_data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def batbugger_local_request?
|
24
|
+
if defined?(::Rails.application.config)
|
25
|
+
::Rails.application.config.consider_all_requests_local || (request.local? && (!request.env["HTTP_X_FORWARDED_FOR"]))
|
26
|
+
else
|
27
|
+
consider_all_requests_local || (local_request? && (!request.env["HTTP_X_FORWARDED_FOR"]))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def batbugger_ignore_user_agent? #:nodoc:
|
32
|
+
# Rails 1.2.6 doesn't have request.user_agent, so check for it here
|
33
|
+
user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"]
|
34
|
+
Batbugger.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
|
35
|
+
end
|
36
|
+
|
37
|
+
def batbugger_filter_if_filtering(hash)
|
38
|
+
return hash if ! hash.is_a?(Hash)
|
39
|
+
|
40
|
+
# Rails 2 filters parameters in the controller
|
41
|
+
# In Rails 3+ we use request.env['action_dispatch.parameter_filter']
|
42
|
+
# to filter parameters in Batbugger::Notice (avoids filtering twice)
|
43
|
+
if respond_to?(:filter_parameters)
|
44
|
+
filter_parameters(hash)
|
45
|
+
else
|
46
|
+
hash
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def batbugger_session_data
|
51
|
+
if session.respond_to?(:to_hash)
|
52
|
+
session.to_hash
|
53
|
+
else
|
54
|
+
session.data
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def batbugger_request_url
|
59
|
+
url = "#{request.protocol}#{request.host}"
|
60
|
+
|
61
|
+
unless [80, 443].include?(request.port)
|
62
|
+
url << ":#{request.port}"
|
63
|
+
end
|
64
|
+
|
65
|
+
url << request.fullpath
|
66
|
+
url
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Batbugger
|
2
|
+
module Rails
|
3
|
+
module Middleware
|
4
|
+
module ExceptionsCatcher
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:alias_method_chain,:render_exception,:batbugger)
|
7
|
+
end
|
8
|
+
|
9
|
+
def skip_user_agent?(env)
|
10
|
+
user_agent = env["HTTP_USER_AGENT"]
|
11
|
+
::Batbugger.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
|
12
|
+
rescue
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def render_exception_with_batbugger(env,exception)
|
17
|
+
controller = env['action_controller.instance']
|
18
|
+
env['batbugger.error_id'] = Batbugger.
|
19
|
+
notify_or_ignore(exception,
|
20
|
+
(controller.respond_to?(:batbugger_request_data) ? controller.batbugger_request_data : {:rack_env => env})) unless skip_user_agent?(env)
|
21
|
+
if defined?(controller.rescue_action_in_public_without_batbugger)
|
22
|
+
controller.rescue_action_in_public_without_batbugger(exception)
|
23
|
+
end
|
24
|
+
render_exception_without_batbugger(env,exception)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'batbugger'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Batbugger
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer "batbugger.use_rack_middleware" do |app|
|
7
|
+
app.config.middleware.insert 0, "Batbugger::Rack"
|
8
|
+
end
|
9
|
+
|
10
|
+
config.after_initialize do
|
11
|
+
Batbugger.configure(true) do |config|
|
12
|
+
config.logger ||= ::Rails.logger
|
13
|
+
config.environment_name ||= ::Rails.env
|
14
|
+
config.project_root ||= ::Rails.root
|
15
|
+
config.framework = "Rails: #{::Rails::VERSION::STRING}"
|
16
|
+
end
|
17
|
+
|
18
|
+
ActiveSupport.on_load(:action_controller) do
|
19
|
+
require 'batbugger/rails/controller_methods'
|
20
|
+
|
21
|
+
include Batbugger::Rails::ControllerMethods
|
22
|
+
end
|
23
|
+
|
24
|
+
if defined?(::ActionDispatch::DebugExceptions)
|
25
|
+
require 'batbugger/rails/middleware/exceptions_catcher'
|
26
|
+
::ActionDispatch::DebugExceptions.send(:include,Batbugger::Rails::Middleware::ExceptionsCatcher)
|
27
|
+
elsif defined?(::ActionDispatch::ShowExceptions)
|
28
|
+
require 'batbugger/rails/middleware/exceptions_catcher'
|
29
|
+
::ActionDispatch::ShowExceptions.send(:include,Batbugger::Rails::Middleware::ExceptionsCatcher)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Batbugger
|
2
|
+
class Sender
|
3
|
+
NOTICES_URI = '/v1/notices/'.freeze
|
4
|
+
HTTP_ERRORS = [Timeout::Error,
|
5
|
+
Errno::EINVAL,
|
6
|
+
Errno::ECONNRESET,
|
7
|
+
EOFError,
|
8
|
+
Net::HTTPBadResponse,
|
9
|
+
Net::HTTPHeaderSyntaxError,
|
10
|
+
Net::ProtocolError,
|
11
|
+
Errno::ECONNREFUSED].freeze
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
[ :api_key,
|
15
|
+
:proxy_host,
|
16
|
+
:proxy_port,
|
17
|
+
:proxy_user,
|
18
|
+
:proxy_pass,
|
19
|
+
:protocol,
|
20
|
+
:host,
|
21
|
+
:port,
|
22
|
+
:secure,
|
23
|
+
:use_system_ssl_cert_chain,
|
24
|
+
:http_open_timeout,
|
25
|
+
:http_read_timeout
|
26
|
+
].each do |option|
|
27
|
+
instance_variable_set("@#{option}", options[option])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_to_batbugger(notice)
|
32
|
+
data = notice.is_a?(String) ? notice : notice.to_json
|
33
|
+
|
34
|
+
http = setup_http_connection
|
35
|
+
headers = HEADERS
|
36
|
+
|
37
|
+
headers.merge!({ 'X-API-Key' => api_key}) unless api_key.nil?
|
38
|
+
|
39
|
+
response = begin
|
40
|
+
http.post(url.path, data, headers)
|
41
|
+
rescue *HTTP_ERRORS => e
|
42
|
+
log(:error, "Unable to contact the Batbugger server. HTTP Error=#{e}")
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
case response
|
47
|
+
when Net::HTTPSuccess then
|
48
|
+
log(Batbugger.configuration.debug ? :info : :debug, "Success: #{response.class}", response, data)
|
49
|
+
JSON.parse(response.body)['id']
|
50
|
+
else
|
51
|
+
log(:error, "Failure: #{response.class}", response, data)
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
rescue => e
|
55
|
+
log(:error, "[Batbugger::Sender#send_to_batbugger] Error: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}")
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :api_key,
|
60
|
+
:proxy_host,
|
61
|
+
:proxy_port,
|
62
|
+
:proxy_user,
|
63
|
+
:proxy_pass,
|
64
|
+
:protocol,
|
65
|
+
:host,
|
66
|
+
:port,
|
67
|
+
:secure,
|
68
|
+
:use_system_ssl_cert_chain,
|
69
|
+
:http_open_timeout,
|
70
|
+
:http_read_timeout
|
71
|
+
|
72
|
+
alias_method :secure?, :secure
|
73
|
+
alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def url
|
78
|
+
URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
|
79
|
+
end
|
80
|
+
|
81
|
+
def log(level, message, response = nil, data = nil)
|
82
|
+
# Log result:
|
83
|
+
Batbugger.write_verbose_log(message, level)
|
84
|
+
|
85
|
+
# Log debug information:
|
86
|
+
Batbugger.report_environment_info
|
87
|
+
Batbugger.report_response_body(response.body) if response && response.respond_to?(:body)
|
88
|
+
Batbugger.write_verbose_log("Notice: #{data}", :debug) if data && Batbugger.configuration.debug
|
89
|
+
end
|
90
|
+
|
91
|
+
def setup_http_connection
|
92
|
+
http =
|
93
|
+
Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).
|
94
|
+
new(url.host, url.port)
|
95
|
+
|
96
|
+
http.read_timeout = http_read_timeout
|
97
|
+
http.open_timeout = http_open_timeout
|
98
|
+
|
99
|
+
if secure?
|
100
|
+
http.use_ssl = true
|
101
|
+
|
102
|
+
http.ca_file = Batbugger.configuration.ca_bundle_path
|
103
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
104
|
+
else
|
105
|
+
http.use_ssl = false
|
106
|
+
end
|
107
|
+
|
108
|
+
http
|
109
|
+
rescue => e
|
110
|
+
log(:error, "[Batbugger::Sender#setup_http_connection] Failure initializing the HTTP connection.\nError: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}")
|
111
|
+
raise e
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/batbugger/version.rb
CHANGED
data/lib/batbugger.rb
CHANGED
@@ -1,14 +1,129 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'json'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
require 'batbugger/configuration'
|
7
|
+
require 'batbugger/backtrace'
|
8
|
+
require 'batbugger/notice'
|
9
|
+
require 'batbugger/rack'
|
10
|
+
require 'batbugger/sender'
|
11
|
+
|
12
|
+
require 'batbugger/railtie' if defined?(Rails::Railtie)
|
3
13
|
|
4
14
|
module Batbugger
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
15
|
+
VERSION = '1.6.0'
|
16
|
+
LOG_PREFIX = "** [Batbugger] "
|
17
|
+
|
18
|
+
HEADERS = {
|
19
|
+
'Content-type' => 'application/json',
|
20
|
+
'Accept' => 'text/json, application/json'
|
21
|
+
}
|
22
|
+
|
23
|
+
class << self
|
24
|
+
attr_accessor :sender
|
25
|
+
attr_writer :configuration
|
26
|
+
|
27
|
+
def report_ready
|
28
|
+
write_verbose_log("Notifier #{VERSION} ready to catch errors", :info)
|
29
|
+
end
|
30
|
+
|
31
|
+
def report_environment_info
|
32
|
+
write_verbose_log("Environment Info: #{environment_info}")
|
33
|
+
end
|
34
|
+
|
35
|
+
def report_response_body(response)
|
36
|
+
write_verbose_log("Response from Batbugger: \n#{response}")
|
37
|
+
end
|
38
|
+
|
39
|
+
def environment_info
|
40
|
+
info = "[Ruby: #{RUBY_VERSION}]"
|
41
|
+
info << " [#{configuration.framework}]" if configuration.framework
|
42
|
+
info << " [Env: #{configuration.environment_name}]" if configuration.environment_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_verbose_log(message, level = Batbugger.configuration.debug ? :info : :debug)
|
46
|
+
logger.send(level, LOG_PREFIX + message) if logger
|
47
|
+
end
|
48
|
+
|
49
|
+
def logger
|
50
|
+
self.configuration.logger
|
51
|
+
end
|
52
|
+
|
53
|
+
def configure(silent = false)
|
54
|
+
yield(configuration)
|
55
|
+
self.sender = Sender.new(configuration)
|
56
|
+
report_ready unless silent
|
57
|
+
self.sender
|
58
|
+
end
|
59
|
+
|
60
|
+
def configuration
|
61
|
+
@configuration ||= Configuration.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def notify(exception, options = {})
|
65
|
+
send_notice(build_notice_for(exception, options))
|
66
|
+
end
|
67
|
+
|
68
|
+
def notify_or_ignore(exception, opts = {})
|
69
|
+
notice = build_notice_for(exception, opts)
|
70
|
+
send_notice(notice) unless notice.ignore?
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_lookup_hash_for(exception, options = {})
|
74
|
+
notice = build_notice_for(exception, options)
|
75
|
+
|
76
|
+
result = {}
|
77
|
+
result[:action] = notice.action rescue nil
|
78
|
+
result[:component] = notice.component rescue nil
|
79
|
+
result[:error_class] = notice.error_class if notice.error_class
|
80
|
+
result[:environment_name] = 'production'
|
81
|
+
|
82
|
+
unless notice.backtrace.lines.empty?
|
83
|
+
result[:file] = notice.backtrace.lines[0].file
|
84
|
+
result[:line_number] = notice.backtrace.lines[0].number
|
85
|
+
end
|
86
|
+
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
def context(hash = {})
|
91
|
+
Thread.current[:batbugger_context] ||= {}
|
92
|
+
Thread.current[:batbugger_context].merge!(hash)
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def clear!
|
97
|
+
Thread.current[:batbugger_context] = nil
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def send_notice(notice)
|
103
|
+
if configuration.public?
|
104
|
+
if configuration.async?
|
105
|
+
configuration.async.call(notice)
|
106
|
+
else
|
107
|
+
notice.deliver
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_notice_for(exception, opts = {})
|
113
|
+
exception = unwrap_exception(exception)
|
114
|
+
opts = opts.merge(:exception => exception) if exception.is_a?(Exception)
|
115
|
+
opts = opts.merge(exception.to_hash) if exception.respond_to?(:to_hash)
|
116
|
+
Notice.new(configuration.merge(opts))
|
117
|
+
end
|
118
|
+
|
119
|
+
def unwrap_exception(exception)
|
120
|
+
if exception.respond_to?(:original_exception)
|
121
|
+
exception.original_exception
|
122
|
+
elsif exception.respond_to?(:continued_exception)
|
123
|
+
exception.continued_exception
|
124
|
+
else
|
125
|
+
exception
|
126
|
+
end
|
11
127
|
end
|
12
128
|
end
|
13
129
|
end
|
14
|
-
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: batbugger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version:
|
5
|
+
version: 1.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Kumar
|
@@ -10,29 +10,29 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-09-11 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: bundler
|
17
17
|
prerelease: false
|
18
18
|
requirement: &id001 !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
|
-
- -
|
21
|
+
- - ~>
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 1.
|
24
|
-
type: :
|
23
|
+
version: "1.3"
|
24
|
+
type: :development
|
25
25
|
version_requirements: *id001
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
27
|
+
name: json
|
28
28
|
prerelease: false
|
29
29
|
requirement: &id002 !ruby/object:Gem::Requirement
|
30
30
|
none: false
|
31
31
|
requirements:
|
32
|
-
- -
|
32
|
+
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: "
|
35
|
-
type: :
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
36
|
version_requirements: *id002
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: rake
|
@@ -63,6 +63,14 @@ files:
|
|
63
63
|
- Rakefile
|
64
64
|
- batbugger.gemspec
|
65
65
|
- lib/batbugger.rb
|
66
|
+
- lib/batbugger/backtrace.rb
|
67
|
+
- lib/batbugger/configuration.rb
|
68
|
+
- lib/batbugger/notice.rb
|
69
|
+
- lib/batbugger/rack.rb
|
70
|
+
- lib/batbugger/rails/controller_methods.rb
|
71
|
+
- lib/batbugger/rails/middleware/exceptions_catcher.rb
|
72
|
+
- lib/batbugger/railtie.rb
|
73
|
+
- lib/batbugger/sender.rb
|
66
74
|
- lib/batbugger/version.rb
|
67
75
|
homepage: ""
|
68
76
|
licenses:
|