global_error_handler 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +41 -0
- data/Rakefile +2 -0
- data/config/redis_example.yml +38 -0
- data/global_error_handler.gemspec +26 -0
- data/lib/global_error_handler.rb +22 -0
- data/lib/global_error_handler/app_exception.rb +68 -0
- data/lib/global_error_handler/global_error_handler.rb +185 -0
- data/lib/global_error_handler/handler.rb +25 -0
- data/lib/global_error_handler/middleware.rb +15 -0
- data/lib/global_error_handler/parser.rb +41 -0
- data/lib/global_error_handler/rails.rb +13 -0
- data/lib/global_error_handler/redis.rb +99 -0
- data/lib/global_error_handler/server/public/css/global_error_handler.css +8 -0
- data/lib/global_error_handler/server/public/js/global_error_handler.js +46 -0
- data/lib/global_error_handler/server/views/index.html.haml +45 -0
- data/lib/global_error_handler/server/views/show.html.haml +28 -0
- data/lib/global_error_handler/version.rb +3 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 07f69e5667ebe1d85b0a8c19d1235a4e2f616376
|
4
|
+
data.tar.gz: 6b567dc07286f74344be7f8095f5ff2143653428
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b6e21e73feb9f79d781bda7e1568f189d9e925e6b4e5add59358dc27f3dd756026b75c945b8daabe872a24756d28db307c67083f51be810077089c9c3d61a79
|
7
|
+
data.tar.gz: ac37c37754e81d3619c43c3dc8b75be62a041597a81f157643559266a3f53a6506511e836f4d636cef9d06da9187609b9400393f01479f61580581ae8340328d
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# GlobalErrorHandler
|
2
|
+
|
3
|
+
GlobalErrorHandler catches application exceptions on the middleware level and store them into the redis database.
|
4
|
+
It adds Exceptions tab to Redis Web server in case to view, filter, delete or truncate them.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'global_error_handler'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install global_error_handler
|
19
|
+
|
20
|
+
## Configuration
|
21
|
+
|
22
|
+
Add redis database configuration into `global_exceptions_handler` section of _redis.yml_. See [redis_example.yml](https://github.com/kolobock/global_error_handler/blob/master/config/redis_example.yml) for more details.
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Target your browser to `/resque/exceptions/` path of your Rails Application server to view all Exceptions.
|
27
|
+
*Truncate all* deletes all Exceptions by filter if filter is selected or _ALL_ Exceptions otherwise.
|
28
|
+
|
29
|
+
If `rescue_from` is used in your application, add following line at top of the method specified to `with:` parameter of resque_from helper.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
GlobalErrorHandler::Handler.new(request.env, exception).process_exception!
|
33
|
+
```
|
34
|
+
|
35
|
+
## Contributing
|
36
|
+
|
37
|
+
1. Fork it ( https://github.com/[my-github-username]/global_error_handler/fork )
|
38
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
39
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
40
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
41
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
development:
|
2
|
+
main: &dev_main
|
3
|
+
db: "1"
|
4
|
+
host: localhost
|
5
|
+
port: 6379
|
6
|
+
password:
|
7
|
+
|
8
|
+
global_exception_handler: &dev_global_exception_handler
|
9
|
+
db: "3"
|
10
|
+
host: localhost
|
11
|
+
port: 6379
|
12
|
+
password:
|
13
|
+
|
14
|
+
test:
|
15
|
+
main:
|
16
|
+
<<: *dev_main
|
17
|
+
|
18
|
+
global_exception_handler:
|
19
|
+
<<: *dev_global_exception_handler
|
20
|
+
|
21
|
+
production:
|
22
|
+
main: &main
|
23
|
+
db: '0'
|
24
|
+
host: localhost
|
25
|
+
port: 6379
|
26
|
+
password:
|
27
|
+
|
28
|
+
global_exception_handler: &global_exception_handler
|
29
|
+
db: '2'
|
30
|
+
host: localhost
|
31
|
+
port: 6379
|
32
|
+
password:
|
33
|
+
|
34
|
+
staging:
|
35
|
+
main:
|
36
|
+
<<: *main
|
37
|
+
global_exception_handler:
|
38
|
+
<<: *global_exception_handler
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'global_error_handler/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "global_error_handler"
|
8
|
+
spec.version = GlobalErrorHandler::VERSION
|
9
|
+
spec.authors = ["Andrii Rudenko"]
|
10
|
+
spec.email = ["kolobock@gmail.com"]
|
11
|
+
spec.summary = %q{Records application' exceptions into the separated redis database.}
|
12
|
+
spec.description = %q{On the middleware level catch an exception from Rails app and store in the separated Redis database.}
|
13
|
+
spec.homepage = "https://github.com/kolobock/global_error_handler/"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", '~> 1.6'
|
22
|
+
spec.add_development_dependency "rake", '~> 10.3', '>= 10.3.2'
|
23
|
+
|
24
|
+
spec.add_dependency 'resque', '~> 1.25', '>= 1.25.1'
|
25
|
+
spec.add_dependency 'haml', '~> 4.0', '>= 4.0.5'
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
|
2
|
+
|
3
|
+
module GlobalErrorHandler
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'resque'
|
7
|
+
require 'resque/server'
|
8
|
+
require 'haml'
|
9
|
+
|
10
|
+
|
11
|
+
require 'global_error_handler/redis'
|
12
|
+
require 'global_error_handler/parser'
|
13
|
+
require 'global_error_handler/handler'
|
14
|
+
require 'global_error_handler/app_exception'
|
15
|
+
require 'global_error_handler/global_error_handler'
|
16
|
+
|
17
|
+
require 'global_error_handler/middleware'
|
18
|
+
require 'global_error_handler/rails' if defined? Rails::Railtie
|
19
|
+
|
20
|
+
require "global_error_handler/version"
|
21
|
+
|
22
|
+
Resque::Server.register GlobalErrorHandler::Server
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class GlobalErrorHandler::AppException
|
2
|
+
class << self
|
3
|
+
def all(page, field = nil, filter = nil)
|
4
|
+
page ||= 0
|
5
|
+
if field && filter
|
6
|
+
keys = GlobalErrorHandler::Redis.filter_exception_keys page, "error_#{field}", filter
|
7
|
+
else
|
8
|
+
keys = GlobalErrorHandler::Redis.exception_keys page
|
9
|
+
end
|
10
|
+
GlobalErrorHandler::Redis.find_all keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def count(field = nil, filter = nil)
|
14
|
+
if field && filter
|
15
|
+
GlobalErrorHandler::Redis.filtered_exceptions_count("error_#{field}", filter)
|
16
|
+
else
|
17
|
+
GlobalErrorHandler::Redis.exceptions_count
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(id)
|
22
|
+
return if id.blank?
|
23
|
+
GlobalErrorHandler::Redis.find exception_key(id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(id)
|
27
|
+
return if id.blank?
|
28
|
+
GlobalErrorHandler::Redis.delete exception_key(id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_all(ids)
|
32
|
+
return if ids.blank?
|
33
|
+
keys = ids.map{ |id| exception_key id }
|
34
|
+
GlobalErrorHandler::Redis.delete_all keys
|
35
|
+
end
|
36
|
+
|
37
|
+
def truncate(filter = nil, opts = {})
|
38
|
+
if filter
|
39
|
+
field = opts.delete(:field)
|
40
|
+
ids = filtered_ids_by field, filter
|
41
|
+
delete_all ids
|
42
|
+
else
|
43
|
+
GlobalErrorHandler::Redis.truncate!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def filters_for(field)
|
48
|
+
keys = GlobalErrorHandler::Redis.filter_keys_for "error_#{field}"
|
49
|
+
return [] if keys.blank?
|
50
|
+
keys.map do |key|
|
51
|
+
key =~ /^#{GlobalErrorHandler::Redis::FILTER_KEY_PREFIX}:error_#{field}:(.*)/
|
52
|
+
$1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def filtered_ids_by(field, str)
|
57
|
+
GlobalErrorHandler::Redis.filter_exception_keys 0, "error_#{field}", str, len
|
58
|
+
return [] if keys.blank?
|
59
|
+
keys.map{ |key| key.split(':').last rescue nil }.compact
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def exception_key(id)
|
65
|
+
GlobalErrorHandler::Redis.exception_key(id)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module GlobalErrorHandler
|
2
|
+
module Server
|
3
|
+
GEH_VIEW_PATH = File.join(File.dirname(__FILE__), 'server', 'views')
|
4
|
+
GEH_PUBLIC_PATH = File.join(File.dirname(__FILE__), 'server', 'public')
|
5
|
+
|
6
|
+
def self.registered(app)
|
7
|
+
app.get '/exceptions' do
|
8
|
+
key = params.keys.first
|
9
|
+
if %w(js css).include? key
|
10
|
+
geh_public_view params[key], key
|
11
|
+
else
|
12
|
+
prepare_and_show_index_action
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
app.get '/exceptions/filter/:filter_by/:filter' do
|
17
|
+
prepare_and_show_index_action
|
18
|
+
end
|
19
|
+
|
20
|
+
app.get '/exceptions/:id' do
|
21
|
+
@app_exception = GlobalErrorHandler::AppException.find(params[:id])
|
22
|
+
show_view :show
|
23
|
+
end
|
24
|
+
|
25
|
+
app.delete '/exceptions/filter/:filter_by/:filter/truncate' do
|
26
|
+
truncate_and_redirect_to_exceptions
|
27
|
+
end
|
28
|
+
|
29
|
+
app.delete '/exceptions/truncate' do
|
30
|
+
truncate_and_redirect_to_exceptions
|
31
|
+
end
|
32
|
+
|
33
|
+
app.delete '/exceptions/delete' do
|
34
|
+
GlobalErrorHandler::AppException.delete_all(params[:app_exception_delete_ids])
|
35
|
+
redirect_to_exceptions
|
36
|
+
end
|
37
|
+
|
38
|
+
app.delete '/exceptions/:id' do
|
39
|
+
GlobalErrorHandler::AppException.delete(params[:id])
|
40
|
+
redirect_to_exceptions
|
41
|
+
end
|
42
|
+
|
43
|
+
app.tabs << 'Exceptions'
|
44
|
+
|
45
|
+
app.helpers do
|
46
|
+
include ActionView::Helpers::TextHelper #link_to
|
47
|
+
include ActionView::Helpers::UrlHelper #simple_format
|
48
|
+
include ActionView::Helpers::FormHelper #select_tag check_box_tag
|
49
|
+
include ActionView::Helpers::FormOptionsHelper #options_for_select
|
50
|
+
include ActionView::Helpers::OutputSafetyHelper #delete via ajax
|
51
|
+
|
52
|
+
def prepare_and_show_index_action
|
53
|
+
@app_exceptions = GlobalErrorHandler::AppException.all(params[:start], params[:filter_by], get_filter)
|
54
|
+
@all_classes = GlobalErrorHandler::AppException.filters_for('class')
|
55
|
+
@all_messages = GlobalErrorHandler::AppException.filters_for('message')
|
56
|
+
show_view :index
|
57
|
+
end
|
58
|
+
|
59
|
+
def truncate_and_redirect_to_exceptions
|
60
|
+
GlobalErrorHandler::AppException.truncate(get_filter, field: params[:filter_by])
|
61
|
+
redirect exceptions_path
|
62
|
+
end
|
63
|
+
|
64
|
+
def redirect_to_exceptions
|
65
|
+
redirect exceptions_path(params[:start], params[:filter_by], params[:filter])
|
66
|
+
end
|
67
|
+
|
68
|
+
def show_view(filename = :index)
|
69
|
+
erb haml( File.read(File.join(GEH_VIEW_PATH, "#{filename}.html.haml")) )
|
70
|
+
end
|
71
|
+
|
72
|
+
def geh_public_view(filename, dir='')
|
73
|
+
file = File.join(GEH_PUBLIC_PATH, dir, filename)
|
74
|
+
begin
|
75
|
+
cache_control :public, :max_age => 1800
|
76
|
+
send_file file
|
77
|
+
rescue Errno::ENOENT
|
78
|
+
404
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def exceptions_path(start = nil, filter_by = nil, filter = nil)
|
83
|
+
path = "/resque/exceptions"
|
84
|
+
path += "/filter/#{filter_by}/#{URI.escape(filter)}" if filter_by && filter
|
85
|
+
path += "?start=#{start}" if start
|
86
|
+
path
|
87
|
+
end
|
88
|
+
|
89
|
+
def exception_path(id, start=nil, filter_by = nil, filter = nil)
|
90
|
+
path = "/resque/exceptions/#{id}"
|
91
|
+
path_params = []
|
92
|
+
path_params.push "start=#{start}" if start
|
93
|
+
path_params.push "filter_by=#{filter_by}&filter=#{URI.escape(filter)}" if filter_by && filter
|
94
|
+
path += '?' + path_params.join('&') if path_params.size > 0
|
95
|
+
path
|
96
|
+
end
|
97
|
+
|
98
|
+
def apps_size
|
99
|
+
@apps_size ||= GlobalErrorHandler::AppException.count(params[:filter_by], get_filter).to_i
|
100
|
+
end
|
101
|
+
|
102
|
+
def apps_start_at
|
103
|
+
return 0 if apps_size < 1
|
104
|
+
params[:start].to_i + 1
|
105
|
+
end
|
106
|
+
|
107
|
+
def apps_per_page
|
108
|
+
10
|
109
|
+
end
|
110
|
+
|
111
|
+
def apps_end_at
|
112
|
+
if apps_start_at + apps_per_page > apps_size
|
113
|
+
apps_size
|
114
|
+
else
|
115
|
+
apps_start_at + apps_per_page - 1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def each_app_exception(&block)
|
120
|
+
return unless block_given?
|
121
|
+
@app_exceptions.try(:each) do |app_exception|
|
122
|
+
yield app_exception
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def pagination(options = {})
|
127
|
+
start = options[:start] || 0
|
128
|
+
per_page = apps_per_page
|
129
|
+
total = options[:total] || 0
|
130
|
+
return if total < per_page
|
131
|
+
|
132
|
+
markup = ""
|
133
|
+
if start - per_page >= 0
|
134
|
+
markup << link_to(raw("« less"), exceptions_path(start - per_page), :class => 'btn less')
|
135
|
+
elsif start > 0 && start < per_page
|
136
|
+
markup << link_to(raw("« less"), exceptions_path(0), :class => 'btn less')
|
137
|
+
end
|
138
|
+
|
139
|
+
markup << pages_markup(start, per_page, total)
|
140
|
+
|
141
|
+
if start + per_page < total
|
142
|
+
markup << link_to(raw("more »"), exceptions_path(start + per_page), :class => 'btn more')
|
143
|
+
end
|
144
|
+
markup
|
145
|
+
end
|
146
|
+
|
147
|
+
def pages_markup(start, per_page, total)
|
148
|
+
pages_count = ((total - 1)/ per_page).ceil
|
149
|
+
return '' if pages_count < 1
|
150
|
+
|
151
|
+
left_ind = start / per_page
|
152
|
+
markups = [left_ind.to_s]
|
153
|
+
while (left_ind -= 1) >= 0 && (start/per_page - left_ind <= max_side_links || pages_count < max_links)
|
154
|
+
markups.unshift link_to(left_ind, exceptions_path(left_ind * per_page, params[:filter_by], params[:filter]), :class => 'btn pages')
|
155
|
+
end
|
156
|
+
right_ind = start / per_page
|
157
|
+
if right_ind > max_side_links && pages_count >= max_links
|
158
|
+
markups.unshift '...' if right_ind - max_side_links > 1
|
159
|
+
markups.unshift link_to(0, exceptions_path(0, params[:filter_by], params[:filter]), :class => 'btn pages')
|
160
|
+
end
|
161
|
+
while (right_ind +=1) * per_page < total && (right_ind - start / per_page <= max_side_links || pages_count < max_links)
|
162
|
+
markups.push link_to(right_ind, exceptions_path(per_page * right_ind, params[:filter_by], params[:filter]), :class => 'btn pages')
|
163
|
+
end
|
164
|
+
if pages_count >= max_links && pages_count >= right_ind
|
165
|
+
markups.push '...' if pages_count - right_ind >= 1
|
166
|
+
markups.push link_to(pages_count, exceptions_path(pages_count * per_page, params[:filter_by], params[:filter]), :class => 'btn pages')
|
167
|
+
end
|
168
|
+
markups.join(' ')
|
169
|
+
end
|
170
|
+
|
171
|
+
def max_side_links
|
172
|
+
4
|
173
|
+
end
|
174
|
+
|
175
|
+
def max_links
|
176
|
+
max_side_links * 2 + 1
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_filter
|
180
|
+
URI.unescape(params[:filter]) if params[:filter]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class GlobalErrorHandler::Handler
|
2
|
+
def initialize(env, exception)
|
3
|
+
@env = env
|
4
|
+
@exception = exception
|
5
|
+
@controller = @env['action_controller.instance']
|
6
|
+
@parsed_error = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def process_exception!
|
10
|
+
return if @env['global_error_handler.proceed_time']
|
11
|
+
@env['global_error_handler.proceed_time'] = Time.now.utc
|
12
|
+
parse_exception
|
13
|
+
store_exception
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parse_exception
|
19
|
+
@parsed_error = GlobalErrorHandler::Parser.new(@env, @exception, @controller).parse
|
20
|
+
end
|
21
|
+
|
22
|
+
def store_exception
|
23
|
+
GlobalErrorHandler::Redis.store(@parsed_error.info_hash)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class GlobalErrorHandler::Middleware
|
2
|
+
def initialize(app)
|
3
|
+
@app = app
|
4
|
+
end
|
5
|
+
|
6
|
+
def call(env)
|
7
|
+
exception = nil
|
8
|
+
status, headers, response = @app.call(env)
|
9
|
+
rescue Exception => exception
|
10
|
+
GlobalErrorHandler::Handler.new(env, exception).process_exception!
|
11
|
+
ensure
|
12
|
+
raise exception if exception
|
13
|
+
[status, headers, response]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class GlobalErrorHandler::Parser
|
2
|
+
attr_reader :info_hash
|
3
|
+
|
4
|
+
def initialize(env, exception, controller)
|
5
|
+
@env = env
|
6
|
+
@exception = exception
|
7
|
+
@controller = controller
|
8
|
+
@request = ActionDispatch::Request.new(@env)
|
9
|
+
@info_hash = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse
|
13
|
+
@info_hash[:error_class] = @exception.class.to_s.strip
|
14
|
+
@info_hash[:error_message] = @exception.message.to_s.strip
|
15
|
+
@info_hash[:error_trace] = @exception.backtrace.join("\n")
|
16
|
+
@info_hash[:request_method] = @request.method
|
17
|
+
@info_hash[:request_params] = @request.params
|
18
|
+
@info_hash[:target_url] = @request.url
|
19
|
+
@info_hash[:referer_url] = @request.referer
|
20
|
+
@info_hash[:user_agent] = @request.user_agent
|
21
|
+
@info_hash[:user_info] = user_info
|
22
|
+
@info_hash[:timestamp] = Time.now.utc
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def user_info
|
29
|
+
{
|
30
|
+
Orig_IP_Address: get_remote_ip,
|
31
|
+
IP_Address: @request.ip,
|
32
|
+
Remote_Address: @request.remote_addr
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_remote_ip
|
37
|
+
@request.remote_ip
|
38
|
+
rescue => e
|
39
|
+
e.message
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class GlobalErrorHandler::Railtie < Rails::Railtie
|
2
|
+
initializer 'global_error_handler.configure_rails_initialization' do
|
3
|
+
insert_middleware
|
4
|
+
end
|
5
|
+
|
6
|
+
def insert_middleware
|
7
|
+
if defined? ActionDispatch::DebugExceptions
|
8
|
+
Rails.application.middleware.insert_after ActionDispatch::DebugExceptions, GlobalErrorHandler::Middleware
|
9
|
+
else
|
10
|
+
Rails.application.middleware.use GlobalErrorHandler::Middleware
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class GlobalErrorHandler::Redis
|
2
|
+
CURRENT_ID_KEY = 'global_error_handler:current_id'
|
3
|
+
EXCEPTIONS_REDIS_KEY = 'global_error_handler:exceptions'
|
4
|
+
EXCEPTION_KEY_PREFIX = 'global_error_handler:exception'
|
5
|
+
FILTER_KEY_PREFIX = 'global_error_handler:filter'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def store(info_hash)
|
9
|
+
redis_key = exception_key(next_id!)
|
10
|
+
redis.hmset redis_key, info_hash.merge(id: current_id).to_a.flatten
|
11
|
+
redis.rpush EXCEPTIONS_REDIS_KEY, redis_key
|
12
|
+
%w(error_class error_message).each do |field|
|
13
|
+
redis.rpush filter_key(field, info_hash[field.to_sym]), redis_key
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def redis
|
18
|
+
@redis ||= begin
|
19
|
+
unless $redis_global_exception_handler.is_a? Redis
|
20
|
+
redis_config = YAML.load_file(File.join(Rails.root, 'config', 'redis.yml'))[Rails.env]
|
21
|
+
$redis_global_exception_handler = Redis.new(redis_config['global_exception_handler'])
|
22
|
+
end
|
23
|
+
$redis_global_exception_handler
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_id
|
28
|
+
redis.get(CURRENT_ID_KEY)
|
29
|
+
end
|
30
|
+
|
31
|
+
# def sort(field, direction = 'ASC', page = 0, per_page = 1000)
|
32
|
+
# redis.sort(EXCEPTIONS_REDIS_KEY, by: "#{EXCEPTION_KEY_PREFIX}_*->#{field}_*", order: "#{direction}", limit: [page, per_page])
|
33
|
+
# end
|
34
|
+
|
35
|
+
def exceptions_count
|
36
|
+
redis.llen EXCEPTIONS_REDIS_KEY
|
37
|
+
end
|
38
|
+
|
39
|
+
def filtered_exceptions_count(field, filter)
|
40
|
+
redis.llen filter_key(field, filter)
|
41
|
+
end
|
42
|
+
|
43
|
+
def exception_keys(page = 0, per_page = 10)
|
44
|
+
redis.lrange EXCEPTIONS_REDIS_KEY, page.to_i, per_page.to_i + page.to_i - 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def filter_exception_keys(page = 0, field = nil, filter = nil, per_page = 10)
|
48
|
+
redis.lrange filter_key(field, filter), page.to_i, per_page.to_i + page.to_i - 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def filter_keys_for(field)
|
52
|
+
redis.keys filter_key(field, '*')
|
53
|
+
end
|
54
|
+
|
55
|
+
def find(key)
|
56
|
+
Hashie::Mash.new redis.hgetall(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_all(keys)
|
60
|
+
keys.map { |key| find(key) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete(key)
|
64
|
+
redis.lrem EXCEPTIONS_REDIS_KEY, 1, key
|
65
|
+
clear_filters key
|
66
|
+
redis.del key
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete_all(keys)
|
70
|
+
keys.each { |key| delete(key) rescue next }
|
71
|
+
end
|
72
|
+
|
73
|
+
def truncate!
|
74
|
+
redis.flushdb
|
75
|
+
end
|
76
|
+
|
77
|
+
def exception_key(id = current_id)
|
78
|
+
"#{EXCEPTION_KEY_PREFIX}:#{id}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def filter_key(field, filter)
|
82
|
+
"#{FILTER_KEY_PREFIX}:#{field}:#{filter}"
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def next_id!
|
88
|
+
redis.incr(CURRENT_ID_KEY)
|
89
|
+
end
|
90
|
+
|
91
|
+
def clear_filters(key)
|
92
|
+
%w(error_class error_message).each do |field|
|
93
|
+
filter_keys_for(field).each do |filter_key|
|
94
|
+
redis.lrem filter_key, 1, key
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
$(document).ready(function() {
|
2
|
+
$('.js-link').unbind('click');
|
3
|
+
$('.js-link').bind('click', function(event) {
|
4
|
+
event.preventDefault();
|
5
|
+
event.stopPropagation();
|
6
|
+
var h = document.createElement('input');
|
7
|
+
h.type = 'hidden';
|
8
|
+
h.name = '_method';
|
9
|
+
h.value = this.getAttribute('data-method');
|
10
|
+
var f = document.createElement('form');
|
11
|
+
f.appendChild(h);
|
12
|
+
f.style.display = 'none';
|
13
|
+
|
14
|
+
if(this.getAttribute('data-get-ids')){
|
15
|
+
checkboxes = $('.select-exception:checked').clone();
|
16
|
+
if(checkboxes.length == 0) {
|
17
|
+
alert('Select at least one exception to delete please.');
|
18
|
+
return false;
|
19
|
+
}
|
20
|
+
$(f).append(checkboxes);
|
21
|
+
}
|
22
|
+
|
23
|
+
this.parentNode.appendChild(f);
|
24
|
+
f.method = 'POST';
|
25
|
+
f.action = this.getAttribute('href');
|
26
|
+
if(confirm(this.getAttribute('data-confirm'))) {
|
27
|
+
f.submit();
|
28
|
+
} else {
|
29
|
+
f.parentNode.removeChild(f);
|
30
|
+
}
|
31
|
+
return false;
|
32
|
+
});
|
33
|
+
|
34
|
+
$('.select-all-exceptions').click(function() {
|
35
|
+
$('.select-exception').attr('checked', $(this).attr('checked'));
|
36
|
+
});
|
37
|
+
|
38
|
+
$('.filter-by').change(function() {
|
39
|
+
selected = $(this).val();
|
40
|
+
field = $(this).attr('data-field');
|
41
|
+
if(selected == '')
|
42
|
+
window.location = '/resque/exceptions'
|
43
|
+
else
|
44
|
+
window.location = '/resque/exceptions/filter/' + field + '/' + encodeURIComponent(encodeURIComponent(selected));
|
45
|
+
});
|
46
|
+
})
|
@@ -0,0 +1,45 @@
|
|
1
|
+
- filter_suffix = "filter by Error #{params[:filter_by].capitalize}: #{get_filter}" if params[:filter_by]
|
2
|
+
%h1 App Exceptions
|
3
|
+
%p.sub #{filter_suffix}
|
4
|
+
#app_exceptions
|
5
|
+
.commands
|
6
|
+
%ul
|
7
|
+
%li= link_to 'Delete selected', exception_path('delete', params[:start], params[:filter_by], params[:filter]),
|
8
|
+
method: :delete, class: 'js-link',
|
9
|
+
data: {confirm: 'Are you sure to delete selected Exceptions?', get_ids: true}
|
10
|
+
%li= link_to 'Truncate all', exceptions_path(nil, params[:filter_by], params[:filter]) + '/truncate',
|
11
|
+
method: :delete, class: 'js-link',
|
12
|
+
data: {confirm: 'Are you sure to truncate ALL the Exceptions?'}
|
13
|
+
%li.filter= select_tag 'filter_by_message', options_for_select(@all_messages, params[:filter_by].to_s.eql?('message') ? get_filter : nil), prompt: 'Error Message Filter',
|
14
|
+
class: 'filter-by', data: {field: 'message'}
|
15
|
+
%li.filter= select_tag 'filter_by_class', options_for_select(@all_classes, params[:filter_by].to_s.eql?('class') ? get_filter : nil), prompt: 'Error Class Filter',
|
16
|
+
class: 'filter-by', data: {field: 'class'}
|
17
|
+
|
18
|
+
%p.sub
|
19
|
+
Showing #{apps_start_at} to #{apps_end_at} of
|
20
|
+
%b #{apps_size}
|
21
|
+
exceptions
|
22
|
+
|
23
|
+
%table
|
24
|
+
%thead
|
25
|
+
%tr
|
26
|
+
%th= check_box_tag nil, nil, false, class: 'select-all-exceptions'
|
27
|
+
%th Timestamp
|
28
|
+
%th Error Class
|
29
|
+
%th Error Message
|
30
|
+
%th Error Trace
|
31
|
+
%th
|
32
|
+
%tbody
|
33
|
+
- each_app_exception do |app_exception|
|
34
|
+
%tr
|
35
|
+
%td= check_box_tag 'app_exception_delete_ids[]', app_exception.id, false, class: 'select-exception', id: "select-exception-#{app_exception.id}"
|
36
|
+
%td= link_to app_exception.timestamp, exception_path(app_exception.id)
|
37
|
+
%td= link_to app_exception.error_class, exception_path(app_exception.id)
|
38
|
+
%td= simple_format app_exception.error_message
|
39
|
+
%td= simple_format app_exception.error_trace.try(:split, "\n").try(:[], 0..4).try(:join, "\n")
|
40
|
+
%td= link_to 'Delete', exception_path(app_exception.id, params[:start], params[:filter_by], params[:filter]), method: :delete,
|
41
|
+
data: {confirm: 'Are you sure to remove an Exception?'}, class: 'js-link'
|
42
|
+
%p.pagination= pagination(start: apps_start_at - 1, total: apps_size)
|
43
|
+
%script{type: 'text/javascript', src: '/resque/exceptions?js=global_error_handler.js'}
|
44
|
+
%link{type: 'text/css', href: '/resque/style.css', rel: 'stylesheet'}
|
45
|
+
%link{type: 'text/css', href: '/resque/exceptions?css=global_error_handler.css', rel: 'stylesheet'}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
%h1 #{@app_exception.error_class} at #{@app_exception.timestamp} details
|
2
|
+
|
3
|
+
%table
|
4
|
+
%tr
|
5
|
+
%th Message:
|
6
|
+
%td= simple_format @app_exception.error_message
|
7
|
+
%tr
|
8
|
+
%th Trace:
|
9
|
+
%td= simple_format @app_exception.error_trace
|
10
|
+
%tr
|
11
|
+
%th Request method:
|
12
|
+
%td= @app_exception.request_method
|
13
|
+
%tr
|
14
|
+
%th Request parameters:
|
15
|
+
%td= simple_format @app_exception.request_params
|
16
|
+
%tr
|
17
|
+
%th Target URL:
|
18
|
+
%td= simple_format @app_exception.target_url
|
19
|
+
%tr
|
20
|
+
%th Referer:
|
21
|
+
%td= simple_format @app_exception.referer_url
|
22
|
+
%tr
|
23
|
+
%th User Agent:
|
24
|
+
%td= simple_format @app_exception.user_agent
|
25
|
+
%tr
|
26
|
+
%th User Info:
|
27
|
+
%td= simple_format @app_exception.user_info
|
28
|
+
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: global_error_handler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrii Rudenko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.3'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 10.3.2
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '10.3'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 10.3.2
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: resque
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.25'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.25.1
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '1.25'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.25.1
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: haml
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '4.0'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 4.0.5
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '4.0'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 4.0.5
|
87
|
+
description: On the middleware level catch an exception from Rails app and store in
|
88
|
+
the separated Redis database.
|
89
|
+
email:
|
90
|
+
- kolobock@gmail.com
|
91
|
+
executables: []
|
92
|
+
extensions: []
|
93
|
+
extra_rdoc_files: []
|
94
|
+
files:
|
95
|
+
- ".gitignore"
|
96
|
+
- Gemfile
|
97
|
+
- LICENSE.txt
|
98
|
+
- README.md
|
99
|
+
- Rakefile
|
100
|
+
- config/redis_example.yml
|
101
|
+
- global_error_handler.gemspec
|
102
|
+
- lib/global_error_handler.rb
|
103
|
+
- lib/global_error_handler/app_exception.rb
|
104
|
+
- lib/global_error_handler/global_error_handler.rb
|
105
|
+
- lib/global_error_handler/handler.rb
|
106
|
+
- lib/global_error_handler/middleware.rb
|
107
|
+
- lib/global_error_handler/parser.rb
|
108
|
+
- lib/global_error_handler/rails.rb
|
109
|
+
- lib/global_error_handler/redis.rb
|
110
|
+
- lib/global_error_handler/server/public/css/global_error_handler.css
|
111
|
+
- lib/global_error_handler/server/public/js/global_error_handler.js
|
112
|
+
- lib/global_error_handler/server/views/index.html.haml
|
113
|
+
- lib/global_error_handler/server/views/show.html.haml
|
114
|
+
- lib/global_error_handler/version.rb
|
115
|
+
homepage: https://github.com/kolobock/global_error_handler/
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.2.2
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Records application' exceptions into the separated redis database.
|
139
|
+
test_files: []
|