exception_engine 0.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/GPLv3.txt +674 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +114 -0
- data/LICENSE.txt +17 -0
- data/README.rdoc +41 -0
- data/Rakefile +29 -0
- data/lib/exception_engine.rb +49 -0
- data/lib/exception_engine/backtrace.rb +100 -0
- data/lib/exception_engine/engine.rb +5 -0
- data/lib/exception_engine/exception_middleware.rb +21 -0
- data/lib/exception_engine/notice.rb +336 -0
- data/lib/exception_engine/version.rb +3 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/posts_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +45 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +26 -0
- data/test/dummy/config/environments/production.rb +49 -0
- data/test/dummy/config/environments/test.rb +35 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +60 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +462 -0
- data/test/dummy/log/production.log +0 -0
- data/test/dummy/log/server.log +0 -0
- data/test/dummy/log/test.log +547 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +2 -0
- data/test/dummy/public/javascripts/controls.js +965 -0
- data/test/dummy/public/javascripts/dragdrop.js +974 -0
- data/test/dummy/public/javascripts/effects.js +1123 -0
- data/test/dummy/public/javascripts/prototype.js +6001 -0
- data/test/dummy/public/javascripts/rails.js +175 -0
- data/test/dummy/script/rails +6 -0
- data/test/exception_engine_test.rb +52 -0
- data/test/integration/navigation_test.rb +15 -0
- data/test/support/integration_case.rb +5 -0
- data/test/test_helper.rb +35 -0
- metadata +180 -0
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "rails", "3.0.3"
|
4
|
+
gem "capybara", ">= 0.4.0"
|
5
|
+
gem "sqlite3-ruby", :require => "sqlite3"
|
6
|
+
gem "mongoid", "2.0.0.rc.5"
|
7
|
+
gem 'mongo', '1.1.5'
|
8
|
+
gem 'bson_ext', '1.2.0'
|
9
|
+
|
10
|
+
# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
|
11
|
+
# gem 'ruby-debug'
|
12
|
+
# gem 'ruby-debug19'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
abstract (1.0.0)
|
5
|
+
actionmailer (3.0.3)
|
6
|
+
actionpack (= 3.0.3)
|
7
|
+
mail (~> 2.2.9)
|
8
|
+
actionpack (3.0.3)
|
9
|
+
activemodel (= 3.0.3)
|
10
|
+
activesupport (= 3.0.3)
|
11
|
+
builder (~> 2.1.2)
|
12
|
+
erubis (~> 2.6.6)
|
13
|
+
i18n (~> 0.4)
|
14
|
+
rack (~> 1.2.1)
|
15
|
+
rack-mount (~> 0.6.13)
|
16
|
+
rack-test (~> 0.5.6)
|
17
|
+
tzinfo (~> 0.3.23)
|
18
|
+
activemodel (3.0.3)
|
19
|
+
activesupport (= 3.0.3)
|
20
|
+
builder (~> 2.1.2)
|
21
|
+
i18n (~> 0.4)
|
22
|
+
activerecord (3.0.3)
|
23
|
+
activemodel (= 3.0.3)
|
24
|
+
activesupport (= 3.0.3)
|
25
|
+
arel (~> 2.0.2)
|
26
|
+
tzinfo (~> 0.3.23)
|
27
|
+
activeresource (3.0.3)
|
28
|
+
activemodel (= 3.0.3)
|
29
|
+
activesupport (= 3.0.3)
|
30
|
+
activesupport (3.0.3)
|
31
|
+
arel (2.0.7)
|
32
|
+
bson (1.2.0)
|
33
|
+
bson_ext (1.2.0)
|
34
|
+
builder (2.1.2)
|
35
|
+
capybara (0.4.1.1)
|
36
|
+
celerity (>= 0.7.9)
|
37
|
+
culerity (>= 0.2.4)
|
38
|
+
mime-types (>= 1.16)
|
39
|
+
nokogiri (>= 1.3.3)
|
40
|
+
rack (>= 1.0.0)
|
41
|
+
rack-test (>= 0.5.4)
|
42
|
+
selenium-webdriver (>= 0.0.27)
|
43
|
+
xpath (~> 0.1.3)
|
44
|
+
celerity (0.8.7)
|
45
|
+
childprocess (0.1.6)
|
46
|
+
ffi (~> 0.6.3)
|
47
|
+
culerity (0.2.15)
|
48
|
+
erubis (2.6.6)
|
49
|
+
abstract (>= 1.0.0)
|
50
|
+
ffi (0.6.3)
|
51
|
+
rake (>= 0.8.7)
|
52
|
+
i18n (0.5.0)
|
53
|
+
json_pure (1.5.0)
|
54
|
+
mail (2.2.14)
|
55
|
+
activesupport (>= 2.3.6)
|
56
|
+
i18n (>= 0.4.0)
|
57
|
+
mime-types (~> 1.16)
|
58
|
+
treetop (~> 1.4.8)
|
59
|
+
mime-types (1.16)
|
60
|
+
mongo (1.1.5)
|
61
|
+
bson (>= 1.1.5)
|
62
|
+
mongoid (2.0.0.rc.5)
|
63
|
+
activemodel (~> 3.0)
|
64
|
+
mongo (~> 1.1.5)
|
65
|
+
tzinfo (~> 0.3.22)
|
66
|
+
will_paginate (~> 3.0.pre)
|
67
|
+
nokogiri (1.4.4)
|
68
|
+
polyglot (0.3.1)
|
69
|
+
rack (1.2.1)
|
70
|
+
rack-mount (0.6.13)
|
71
|
+
rack (>= 1.0.0)
|
72
|
+
rack-test (0.5.7)
|
73
|
+
rack (>= 1.0)
|
74
|
+
rails (3.0.3)
|
75
|
+
actionmailer (= 3.0.3)
|
76
|
+
actionpack (= 3.0.3)
|
77
|
+
activerecord (= 3.0.3)
|
78
|
+
activeresource (= 3.0.3)
|
79
|
+
activesupport (= 3.0.3)
|
80
|
+
bundler (~> 1.0)
|
81
|
+
railties (= 3.0.3)
|
82
|
+
railties (3.0.3)
|
83
|
+
actionpack (= 3.0.3)
|
84
|
+
activesupport (= 3.0.3)
|
85
|
+
rake (>= 0.8.7)
|
86
|
+
thor (~> 0.14.4)
|
87
|
+
rake (0.8.7)
|
88
|
+
rubyzip (0.9.4)
|
89
|
+
selenium-webdriver (0.1.2)
|
90
|
+
childprocess (~> 0.1.5)
|
91
|
+
ffi (~> 0.6.3)
|
92
|
+
json_pure
|
93
|
+
rubyzip
|
94
|
+
sqlite3 (1.3.3)
|
95
|
+
sqlite3-ruby (1.3.3)
|
96
|
+
sqlite3 (>= 1.3.3)
|
97
|
+
thor (0.14.6)
|
98
|
+
treetop (1.4.9)
|
99
|
+
polyglot (>= 0.3.1)
|
100
|
+
tzinfo (0.3.24)
|
101
|
+
will_paginate (3.0.pre2)
|
102
|
+
xpath (0.1.3)
|
103
|
+
nokogiri (~> 1.3)
|
104
|
+
|
105
|
+
PLATFORMS
|
106
|
+
ruby
|
107
|
+
|
108
|
+
DEPENDENCIES
|
109
|
+
bson_ext (= 1.2.0)
|
110
|
+
capybara (>= 0.4.0)
|
111
|
+
mongo (= 1.1.5)
|
112
|
+
mongoid (= 2.0.0.rc.5)
|
113
|
+
rails (= 3.0.3)
|
114
|
+
sqlite3-ruby
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Engineworks
|
2
|
+
Copyright (C) 2011 Consoci8 Sdn Bhd
|
3
|
+
|
4
|
+
This file is part of Engineworks.
|
5
|
+
|
6
|
+
Engineworks is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
Engineworks is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU General Public License
|
17
|
+
along with Engineworks. If not, see <http://www.gnu.org/licenses/>.
|
data/README.rdoc
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
= ExceptionEngine
|
2
|
+
Copyright (C) 2011 Consoci8 Sdn Bhd
|
3
|
+
|
4
|
+
The Exception Engine logs your Rails exceptions in your mongodb database and provides a web interface to manage them
|
5
|
+
|
6
|
+
It assumes that the main rails application it's mounted to uses devise for authentication and you have mongodb
|
7
|
+
installed and running in your machine
|
8
|
+
|
9
|
+
|
10
|
+
To use this in your rails engine, add the following line to your gemfile:
|
11
|
+
|
12
|
+
gem "exception_engine", :git => 'git://github.com/Consoci8/exception_engine'
|
13
|
+
|
14
|
+
And then run
|
15
|
+
|
16
|
+
bundle install
|
17
|
+
|
18
|
+
|
19
|
+
Next, run your rails server and you can view all your application exceptions at http://localhost:3000/exceptions
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
For mongo installation:
|
25
|
+
|
26
|
+
http://www.mongodb.org/display/DOCS/Quickstart
|
27
|
+
|
28
|
+
After you installed mongodb, make sure you run the mongod server
|
29
|
+
|
30
|
+
$ mongod
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
This project uses GPLv3 License.
|
36
|
+
Check Gemfile for other dependencies.
|
37
|
+
|
38
|
+
Credits:
|
39
|
+
|
40
|
+
1. José Valim for enginex
|
41
|
+
2. Thoughtbot's HoptoadNotifier gem
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rake'
|
10
|
+
require 'rake/rdoctask'
|
11
|
+
|
12
|
+
require 'rake/testtask'
|
13
|
+
|
14
|
+
Rake::TestTask.new(:test) do |t|
|
15
|
+
t.libs << 'lib'
|
16
|
+
t.libs << 'test'
|
17
|
+
t.pattern = 'test/**/*_test.rb'
|
18
|
+
t.verbose = false
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :test
|
22
|
+
|
23
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
25
|
+
rdoc.title = 'ExceptionEngine'
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
27
|
+
rdoc.rdoc_files.include('README.rdoc')
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "exception_engine/engine"
|
2
|
+
require "exception_engine/exception_middleware"
|
3
|
+
require "exception_engine/backtrace"
|
4
|
+
require "exception_engine/notice"
|
5
|
+
require "mongoid"
|
6
|
+
|
7
|
+
# We are required to choose a database name
|
8
|
+
Mongoid.configure do |config|
|
9
|
+
name = "exception_engine-#{Rails.env}"
|
10
|
+
host = "localhost"
|
11
|
+
config.master = Mongo::Connection.new.db(name)
|
12
|
+
config.persist_in_safe_mode = false
|
13
|
+
end
|
14
|
+
|
15
|
+
module ExceptionEngine
|
16
|
+
class << self
|
17
|
+
|
18
|
+
# Stores the notice exception
|
19
|
+
# @see ExceptionEngine.exceptionize
|
20
|
+
# @params exception
|
21
|
+
def exceptionize(exception, opts = {})
|
22
|
+
notice = build_notice_for(exception, opts)
|
23
|
+
ExceptionEngine::Data.store!(notice)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_notice_for(exception, opts = {})
|
29
|
+
exception = unwrap_exception(exception)
|
30
|
+
if exception.respond_to?(:to_hash)
|
31
|
+
opts = opts.merge(exception.to_hash)
|
32
|
+
else
|
33
|
+
opts = opts.merge(:exception => exception)
|
34
|
+
end
|
35
|
+
Notice.new(opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
def unwrap_exception(exception)
|
39
|
+
if exception.respond_to?(:original_exception)
|
40
|
+
exception.original_exception
|
41
|
+
elsif exception.respond_to?(:continued_exception)
|
42
|
+
exception.continued_exception
|
43
|
+
else
|
44
|
+
exception
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# Credits to Thoughtbot's Hoptoad::Backtrace
|
2
|
+
module ExceptionEngine
|
3
|
+
# Front end to parsing the backtrace for each notice
|
4
|
+
class Backtrace
|
5
|
+
|
6
|
+
# Handles backtrace parsing line by line
|
7
|
+
class Line
|
8
|
+
|
9
|
+
INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze
|
10
|
+
|
11
|
+
# The file portion of the line (such as app/models/user.rb)
|
12
|
+
attr_reader :file
|
13
|
+
|
14
|
+
# The line number portion of the line
|
15
|
+
attr_reader :number
|
16
|
+
|
17
|
+
# The method of the line (such as index)
|
18
|
+
attr_reader :method
|
19
|
+
|
20
|
+
# Parses a single line of a given backtrace
|
21
|
+
# @param [String] unparsed_line The raw line from +caller+ or some backtrace
|
22
|
+
# @return [Line] The parsed backtrace line
|
23
|
+
def self.parse(unparsed_line)
|
24
|
+
_, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
|
25
|
+
new(file, number, method)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(file, number, method)
|
29
|
+
self.file = file
|
30
|
+
self.number = number
|
31
|
+
self.method = method
|
32
|
+
end
|
33
|
+
|
34
|
+
# Reconstructs the line in a readable fashion
|
35
|
+
def to_s
|
36
|
+
"#{file}:#{number}:in `#{method}'"
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
to_s == other.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
"<Line:#{to_s}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_writer :file, :number, :method
|
50
|
+
end
|
51
|
+
|
52
|
+
# holder for an Array of Backtrace::Line instances
|
53
|
+
attr_reader :lines
|
54
|
+
|
55
|
+
def self.parse(ruby_backtrace, opts = {})
|
56
|
+
ruby_lines = split_multiline_backtrace(ruby_backtrace)
|
57
|
+
|
58
|
+
filters = opts[:filters] || []
|
59
|
+
filtered_lines = ruby_lines.to_a.map do |line|
|
60
|
+
filters.inject(line) do |line, proc|
|
61
|
+
proc.call(line)
|
62
|
+
end
|
63
|
+
end.compact
|
64
|
+
|
65
|
+
lines = filtered_lines.collect do |unparsed_line|
|
66
|
+
Line.parse(unparsed_line)
|
67
|
+
end
|
68
|
+
|
69
|
+
instance = new(lines)
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(lines)
|
73
|
+
self.lines = lines
|
74
|
+
end
|
75
|
+
|
76
|
+
def inspect
|
77
|
+
"<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
|
78
|
+
end
|
79
|
+
|
80
|
+
def ==(other)
|
81
|
+
if other.respond_to?(:lines)
|
82
|
+
lines == other.lines
|
83
|
+
else
|
84
|
+
false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
attr_writer :lines
|
91
|
+
|
92
|
+
def self.split_multiline_backtrace(backtrace)
|
93
|
+
if backtrace.to_a.size == 1
|
94
|
+
backtrace.to_a.first.split(/\n\s*/)
|
95
|
+
else
|
96
|
+
backtrace
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ExceptionEngine
|
2
|
+
class ExceptionMiddleware
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
begin
|
9
|
+
response = @app.call(env)
|
10
|
+
rescue Exception => raised
|
11
|
+
ExceptionEngine.exceptionize(raised)
|
12
|
+
raise
|
13
|
+
end
|
14
|
+
|
15
|
+
if env['rack.exception']
|
16
|
+
ExceptionEngine.exceptionize(raised)
|
17
|
+
end
|
18
|
+
response
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,336 @@
|
|
1
|
+
require 'builder'
|
2
|
+
|
3
|
+
# Credits to Thoughtbot's Hoptoad::Notice
|
4
|
+
module ExceptionEngine
|
5
|
+
class Notice
|
6
|
+
|
7
|
+
# The exception that caused this notice, if any
|
8
|
+
attr_reader :exception
|
9
|
+
|
10
|
+
# The API key for the project to which this notice should be sent
|
11
|
+
attr_reader :api_key
|
12
|
+
|
13
|
+
# The backtrace from the given exception or hash.
|
14
|
+
attr_reader :backtrace
|
15
|
+
|
16
|
+
# The name of the class of error (such as RuntimeError)
|
17
|
+
attr_reader :error_class
|
18
|
+
|
19
|
+
# The name of the server environment (such as "production")
|
20
|
+
attr_reader :environment_name
|
21
|
+
|
22
|
+
# CGI variables such as HTTP_METHOD
|
23
|
+
attr_reader :cgi_data
|
24
|
+
|
25
|
+
# The message from the exception, or a general description of the error
|
26
|
+
attr_reader :error_message
|
27
|
+
|
28
|
+
# See Configuration#backtrace_filters
|
29
|
+
attr_reader :backtrace_filters
|
30
|
+
|
31
|
+
# See Configuration#params_filters
|
32
|
+
attr_reader :params_filters
|
33
|
+
|
34
|
+
# A hash of parameters from the query string or post body.
|
35
|
+
attr_reader :parameters
|
36
|
+
alias_method :params, :parameters
|
37
|
+
|
38
|
+
# The component (if any) which was used in this request (usually the controller)
|
39
|
+
attr_reader :component
|
40
|
+
alias_method :controller, :component
|
41
|
+
|
42
|
+
# The action (if any) that was called in this request
|
43
|
+
attr_reader :action
|
44
|
+
|
45
|
+
# A hash of session data from the request
|
46
|
+
attr_reader :session_data
|
47
|
+
|
48
|
+
# The path to the project that caused the error (usually RAILS_ROOT)
|
49
|
+
attr_reader :project_root
|
50
|
+
|
51
|
+
# The URL at which the error occurred (if any)
|
52
|
+
attr_reader :url
|
53
|
+
|
54
|
+
# See Configuration#ignore
|
55
|
+
attr_reader :ignore
|
56
|
+
|
57
|
+
# See Configuration#ignore_by_filters
|
58
|
+
attr_reader :ignore_by_filters
|
59
|
+
|
60
|
+
# The name of the notifier library sending this notice, such as "Hoptoad Notifier"
|
61
|
+
attr_reader :notifier_name
|
62
|
+
|
63
|
+
# The version number of the notifier library sending this notice, such as "2.1.3"
|
64
|
+
attr_reader :notifier_version
|
65
|
+
|
66
|
+
# A URL for more information about the notifier library sending this notice
|
67
|
+
attr_reader :notifier_url
|
68
|
+
|
69
|
+
def initialize(args)
|
70
|
+
self.args = args
|
71
|
+
self.exception = args[:exception]
|
72
|
+
self.api_key = args[:api_key]
|
73
|
+
self.project_root = args[:project_root]
|
74
|
+
self.url = args[:url] || rack_env(:url)
|
75
|
+
|
76
|
+
self.notifier_name = args[:notifier_name]
|
77
|
+
self.notifier_version = args[:notifier_version]
|
78
|
+
self.notifier_url = args[:notifier_url]
|
79
|
+
|
80
|
+
self.ignore = args[:ignore] || []
|
81
|
+
self.ignore_by_filters = args[:ignore_by_filters] || []
|
82
|
+
self.backtrace_filters = args[:backtrace_filters] || []
|
83
|
+
self.params_filters = args[:params_filters] || []
|
84
|
+
self.parameters = args[:parameters] ||
|
85
|
+
action_dispatch_params ||
|
86
|
+
rack_env(:params) ||
|
87
|
+
{}
|
88
|
+
self.component = args[:component] || args[:controller] || parameters['controller']
|
89
|
+
self.action = args[:action] || parameters['action']
|
90
|
+
|
91
|
+
self.environment_name = args[:environment_name]
|
92
|
+
self.cgi_data = args[:cgi_data] || args[:rack_env]
|
93
|
+
self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller), :filters => self.backtrace_filters)
|
94
|
+
self.error_class = exception_attribute(:error_class) {|exception| exception.class.name }
|
95
|
+
self.error_message = exception_attribute(:error_message, 'Notification') do |exception|
|
96
|
+
"#{exception.class.name}: #{exception.message}"
|
97
|
+
end
|
98
|
+
|
99
|
+
also_use_rack_params_filters
|
100
|
+
find_session_data
|
101
|
+
clean_params
|
102
|
+
clean_rack_request_data
|
103
|
+
end
|
104
|
+
|
105
|
+
# Converts the given notice to XML
|
106
|
+
def to_xml
|
107
|
+
builder = Builder::XmlMarkup.new
|
108
|
+
builder.instruct!
|
109
|
+
xml = builder.notice(:version => "1.0") do |notice|
|
110
|
+
notice.tag!("api-key", api_key)
|
111
|
+
notice.notifier do |notifier|
|
112
|
+
notifier.name(notifier_name)
|
113
|
+
notifier.version(notifier_version)
|
114
|
+
notifier.url(notifier_url)
|
115
|
+
end
|
116
|
+
notice.error do |error|
|
117
|
+
error.tag!('class', error_class)
|
118
|
+
error.message(error_message)
|
119
|
+
error.backtrace do |backtrace|
|
120
|
+
self.backtrace.lines.each do |line|
|
121
|
+
backtrace.line(:number => line.number,
|
122
|
+
:file => line.file,
|
123
|
+
:method => line.method)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
if url ||
|
128
|
+
controller ||
|
129
|
+
action ||
|
130
|
+
!parameters.blank? ||
|
131
|
+
!cgi_data.blank? ||
|
132
|
+
!session_data.blank?
|
133
|
+
notice.request do |request|
|
134
|
+
request.url(url)
|
135
|
+
request.component(controller)
|
136
|
+
request.action(action)
|
137
|
+
unless parameters.nil? || parameters.empty?
|
138
|
+
request.params do |params|
|
139
|
+
xml_vars_for(params, parameters)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
unless session_data.nil? || session_data.empty?
|
143
|
+
request.session do |session|
|
144
|
+
xml_vars_for(session, session_data)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
unless cgi_data.nil? || cgi_data.empty?
|
148
|
+
request.tag!("cgi-data") do |cgi_datum|
|
149
|
+
xml_vars_for(cgi_datum, cgi_data)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
notice.tag!("server-environment") do |env|
|
155
|
+
env.tag!("project-root", project_root)
|
156
|
+
env.tag!("environment-name", environment_name)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
xml.to_s
|
160
|
+
end
|
161
|
+
|
162
|
+
# Determines if this notice should be ignored
|
163
|
+
def ignore?
|
164
|
+
ignored_class_names.include?(error_class) ||
|
165
|
+
ignore_by_filters.any? {|filter| filter.call(self) }
|
166
|
+
end
|
167
|
+
|
168
|
+
# Allows properties to be accessed using a hash-like syntax
|
169
|
+
#
|
170
|
+
# @example
|
171
|
+
# notice[:error_message]
|
172
|
+
# @param [String] method The given key for an attribute
|
173
|
+
# @return The attribute value, or self if given +:request+
|
174
|
+
def [](method)
|
175
|
+
case method
|
176
|
+
when :request
|
177
|
+
self
|
178
|
+
else
|
179
|
+
send(method)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
attr_writer :exception, :api_key, :backtrace, :error_class, :error_message,
|
186
|
+
:backtrace_filters, :parameters, :params_filters,
|
187
|
+
:environment_filters, :session_data, :project_root, :url, :ignore,
|
188
|
+
:ignore_by_filters, :notifier_name, :notifier_url, :notifier_version,
|
189
|
+
:component, :action, :cgi_data, :environment_name
|
190
|
+
|
191
|
+
# Arguments given in the initializer
|
192
|
+
attr_accessor :args
|
193
|
+
|
194
|
+
# Gets a property named +attribute+ of an exception, either from an actual
|
195
|
+
# exception or a hash.
|
196
|
+
#
|
197
|
+
# If an exception is available, #from_exception will be used. Otherwise,
|
198
|
+
# a key named +attribute+ will be used from the #args.
|
199
|
+
#
|
200
|
+
# If no exception or hash key is available, +default+ will be used.
|
201
|
+
def exception_attribute(attribute, default = nil, &block)
|
202
|
+
(exception && from_exception(attribute, &block)) || args[attribute] || default
|
203
|
+
end
|
204
|
+
|
205
|
+
# Gets a property named +attribute+ from an exception.
|
206
|
+
#
|
207
|
+
# If a block is given, it will be used when getting the property from an
|
208
|
+
# exception. The block should accept and exception and return the value for
|
209
|
+
# the property.
|
210
|
+
#
|
211
|
+
# If no block is given, a method with the same name as +attribute+ will be
|
212
|
+
# invoked for the value.
|
213
|
+
def from_exception(attribute)
|
214
|
+
if block_given?
|
215
|
+
yield(exception)
|
216
|
+
else
|
217
|
+
exception.send(attribute)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Removes non-serializable data from the given attribute.
|
222
|
+
# See #clean_unserializable_data
|
223
|
+
def clean_unserializable_data_from(attribute)
|
224
|
+
self.send(:"#{attribute}=", clean_unserializable_data(send(attribute)))
|
225
|
+
end
|
226
|
+
|
227
|
+
# Removes non-serializable data. Allowed data types are strings, arrays,
|
228
|
+
# and hashes. All other types are converted to strings.
|
229
|
+
# TODO: move this onto Hash
|
230
|
+
def clean_unserializable_data(data)
|
231
|
+
if data.respond_to?(:to_hash)
|
232
|
+
data.to_hash.inject({}) do |result, (key, value)|
|
233
|
+
result.merge(key => clean_unserializable_data(value))
|
234
|
+
end
|
235
|
+
elsif data.respond_to?(:to_ary)
|
236
|
+
data.collect do |value|
|
237
|
+
clean_unserializable_data(value)
|
238
|
+
end
|
239
|
+
else
|
240
|
+
data.to_s
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Replaces the contents of params that match params_filters.
|
245
|
+
# TODO: extract this to a different class
|
246
|
+
def clean_params
|
247
|
+
clean_unserializable_data_from(:parameters)
|
248
|
+
filter(parameters)
|
249
|
+
if cgi_data
|
250
|
+
clean_unserializable_data_from(:cgi_data)
|
251
|
+
filter(cgi_data)
|
252
|
+
end
|
253
|
+
if session_data
|
254
|
+
clean_unserializable_data_from(:session_data)
|
255
|
+
filter(session_data)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def clean_rack_request_data
|
260
|
+
if cgi_data
|
261
|
+
cgi_data.delete("rack.request.form_vars")
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def filter(hash)
|
266
|
+
if params_filters
|
267
|
+
hash.each do |key, value|
|
268
|
+
if filter_key?(key)
|
269
|
+
hash[key] = "[FILTERED]"
|
270
|
+
elsif value.respond_to?(:to_hash)
|
271
|
+
filter(hash[key])
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def filter_key?(key)
|
278
|
+
params_filters.any? do |filter|
|
279
|
+
key.to_s.include?(filter.to_s)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def find_session_data
|
284
|
+
self.session_data = args[:session_data] || args[:session] || rack_session || {}
|
285
|
+
self.session_data = session_data[:data] if session_data[:data]
|
286
|
+
end
|
287
|
+
|
288
|
+
# Converts the mixed class instances and class names into just names
|
289
|
+
# TODO: move this into Configuration or another class
|
290
|
+
def ignored_class_names
|
291
|
+
ignore.collect do |string_or_class|
|
292
|
+
if string_or_class.respond_to?(:name)
|
293
|
+
string_or_class.name
|
294
|
+
else
|
295
|
+
string_or_class
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def xml_vars_for(builder, hash)
|
301
|
+
hash.each do |key, value|
|
302
|
+
if value.respond_to?(:to_hash)
|
303
|
+
builder.var(:key => key){|b| xml_vars_for(b, value.to_hash) }
|
304
|
+
else
|
305
|
+
builder.var(value.to_s, :key => key)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def rack_env(method)
|
311
|
+
rack_request.send(method) if rack_request
|
312
|
+
end
|
313
|
+
|
314
|
+
def rack_request
|
315
|
+
@rack_request ||= if args[:rack_env]
|
316
|
+
::Rack::Request.new(args[:rack_env])
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def action_dispatch_params
|
321
|
+
args[:rack_env]['action_dispatch.request.parameters'] if args[:rack_env]
|
322
|
+
end
|
323
|
+
|
324
|
+
def rack_session
|
325
|
+
args[:rack_env]['rack.session'] if args[:rack_env]
|
326
|
+
end
|
327
|
+
|
328
|
+
def also_use_rack_params_filters
|
329
|
+
if args[:rack_env]
|
330
|
+
@params_filters ||= []
|
331
|
+
@params_filters += rack_request.env["action_dispatch.parameter_filter"] || []
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
end
|
336
|
+
end
|