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 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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in global_error_handler.gemspec
4
+ gemspec
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,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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("&laquo; less"), exceptions_path(start - per_page), :class => 'btn less')
135
+ elsif start > 0 && start < per_page
136
+ markup << link_to(raw("&laquo; 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 &raquo;"), 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,8 @@
1
+ .commands li { display: inline-block; }
2
+ .commands li.filter {
3
+ float: right;
4
+ }
5
+
6
+ .commands li.filter select {
7
+ max-width: 300px;
8
+ }
@@ -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
+
@@ -0,0 +1,3 @@
1
+ module GlobalErrorHandler
2
+ VERSION = "0.0.2"
3
+ end
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: []