global_error_handler 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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: []
|