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 CHANGED
@@ -14,3 +14,4 @@ spec/reports
14
14
  test/tmp
15
15
  test/version_tmp
16
16
  tmp
17
+ *~
data/Gemfile.lock CHANGED
@@ -1,14 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- batbugger (0.0.1)
5
- honeybadger (>= 1.6.0)
4
+ batbugger (1.1.0)
5
+ json
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- honeybadger (1.6.0)
11
- json
12
10
  json (1.7.7)
13
11
  rake (10.0.3)
14
12
 
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 :zap: [BatBugger Exception Notifier for Ruby and Rails](http://batbugger.io).
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
@@ -1,3 +1,3 @@
1
1
  module Batbugger
2
- VERSION = "0.0.1"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/batbugger.rb CHANGED
@@ -1,14 +1,129 @@
1
- require "batbugger/version"
2
- require "honeybadger"
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
- def self.configure(options)
6
- Honeybadger.configure do |config|
7
- config.api_key = options[:api_key]
8
- config.secure = false
9
- config.environment_name = options[:environment_name]
10
- config.host = "batbugger.herokuapp.com"
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: 0.0.1
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-03-13 00:00:00 Z
13
+ date: 2013-09-11 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: honeybadger
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.6.0
24
- type: :runtime
23
+ version: "1.3"
24
+ type: :development
25
25
  version_requirements: *id001
26
26
  - !ruby/object:Gem::Dependency
27
- name: bundler
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: "1.3"
35
- type: :development
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: