exception_logger 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +2 -0
  4. data/app/assets/javascripts/exception_logger/application.js +15 -0
  5. data/app/assets/javascripts/exception_logger/exception_logger.js +69 -0
  6. data/app/assets/javascripts/exception_logger/logged_exceptions.js +2 -0
  7. data/app/assets/stylesheets/exception_logger/application.css +13 -0
  8. data/app/assets/stylesheets/exception_logger/exception_logger.css +301 -0
  9. data/app/assets/stylesheets/exception_logger/logged_exceptions.css +4 -0
  10. data/app/controllers/exception_logger/application_controller.rb +4 -0
  11. data/app/controllers/exception_logger/logged_exceptions_controller.rb +105 -0
  12. data/app/helpers/exception_logger/application_helper.rb +4 -0
  13. data/app/helpers/exception_logger/logged_exceptions_helper.rb +38 -0
  14. data/app/models/exception_logger/logged_exception.rb +99 -0
  15. data/app/views/exception_logger/logged_exceptions/_exceptions.html.erb +32 -0
  16. data/app/views/exception_logger/logged_exceptions/_feed.html.erb +3 -0
  17. data/app/views/exception_logger/logged_exceptions/_show.html.erb +33 -0
  18. data/app/views/exception_logger/logged_exceptions/destroy.js.erb +2 -0
  19. data/app/views/exception_logger/logged_exceptions/destroy_all.js.erb +2 -0
  20. data/app/views/exception_logger/logged_exceptions/feed.rss.builder +20 -0
  21. data/app/views/exception_logger/logged_exceptions/index.html.erb +46 -0
  22. data/app/views/exception_logger/logged_exceptions/index.js.erb +2 -0
  23. data/app/views/exception_logger/logged_exceptions/query.js.erb +2 -0
  24. data/app/views/exception_logger/logged_exceptions/show.html.erb +8 -0
  25. data/app/views/exception_logger/logged_exceptions/show.js.erb +2 -0
  26. data/app/views/layouts/exception_logger/application.html.erb +20 -0
  27. data/config/initializers/date_formats.rb +5 -0
  28. data/config/locales/en.yml +16 -0
  29. data/config/routes.rb +11 -0
  30. data/db/migrate/20120507081835_create_exception_logger_logged_exceptions.rb +14 -0
  31. data/lib/exception_logger/engine.rb +5 -0
  32. data/lib/exception_logger/version.rb +3 -0
  33. data/lib/exception_logger.rb +90 -0
  34. data/lib/tasks/exception_logger_tasks.rake +4 -0
  35. data/test/dummy/README.rdoc +261 -0
  36. data/test/dummy/Rakefile +7 -0
  37. data/test/dummy/app/assets/javascripts/application.js +15 -0
  38. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  39. data/test/dummy/app/controllers/application_controller.rb +3 -0
  40. data/test/dummy/app/controllers/simulate_controller.rb +10 -0
  41. data/test/dummy/app/helpers/application_helper.rb +2 -0
  42. data/test/dummy/app/helpers/simulate_helper.rb +2 -0
  43. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  44. data/test/dummy/app/views/simulate/failure.html.erb +2 -0
  45. data/test/dummy/config/application.rb +50 -0
  46. data/test/dummy/config/boot.rb +10 -0
  47. data/test/dummy/config/database.yml +25 -0
  48. data/test/dummy/config/environment.rb +5 -0
  49. data/test/dummy/config/environments/development.rb +29 -0
  50. data/test/dummy/config/environments/production.rb +63 -0
  51. data/test/dummy/config/environments/test.rb +33 -0
  52. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  53. data/test/dummy/config/initializers/inflections.rb +15 -0
  54. data/test/dummy/config/initializers/mime_types.rb +5 -0
  55. data/test/dummy/config/initializers/secret_token.rb +7 -0
  56. data/test/dummy/config/initializers/session_store.rb +8 -0
  57. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  58. data/test/dummy/config/locales/en.yml +5 -0
  59. data/test/dummy/config/routes.rb +6 -0
  60. data/test/dummy/config.ru +4 -0
  61. data/test/dummy/db/migrate/20120507083836_create_exception_logger_logged_exceptions.exception_logger.rb +15 -0
  62. data/test/dummy/db/schema.rb +27 -0
  63. data/test/dummy/public/404.html +26 -0
  64. data/test/dummy/public/422.html +26 -0
  65. data/test/dummy/public/500.html +25 -0
  66. data/test/dummy/public/favicon.ico +0 -0
  67. data/test/dummy/script/rails +6 -0
  68. data/test/dummy/test/functional/simulate_controller_test.rb +9 -0
  69. data/test/dummy/test/unit/helpers/simulate_helper_test.rb +4 -0
  70. data/test/exception_logger_test.rb +7 -0
  71. data/test/fixtures/exception_logger/logged_exceptions.yml +11 -0
  72. data/test/functional/exception_logger/logged_exceptions_controller_test.rb +9 -0
  73. data/test/integration/navigation_test.rb +10 -0
  74. data/test/test_helper.rb +10 -0
  75. data/test/unit/exception_logger/logged_exception_test.rb +9 -0
  76. data/test/unit/helpers/exception_logger/logged_exceptions_helper_test.rb +6 -0
  77. metadata +204 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3adf48e140b77393b73afd456d83d6aa4edafd35fc5aaf938b0af5f8f5a89133
4
+ data.tar.gz: 02310da2ad3bcc7ead37f4424c9a74f482ddb6161391f9a552c6acff7b6fbc5b
5
+ SHA512:
6
+ metadata.gz: ddef079c384857ce0097e79330b20898d274f8b5569f1f633aeb4c00884ed81dcde3edbefe96877a5b0cfbbefe3c7b6616f589b66984fb0d0a3a7894e46ef9f4
7
+ data.tar.gz: 642e4072c86165c26823494ae6e964cc7891fc94cfcb5f847a82eea0c73f2d8bd002c3eaf78ca6a9ea04ff3f821d0e063a8ab414494a70adac67da477ceb3f2e
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,69 @@
1
+ $(function () {
2
+ function CSRFProtection(xhr) {
3
+ var token = $('meta[name="csrf-token"]').attr('content');
4
+ if (token) xhr.setRequestHeader('X-CSRF-Token', token);
5
+ };
6
+
7
+ function ajax_headers() {
8
+ if ('ajaxPrefilter' in $) $.ajaxPrefilter(function(options, originalOptions, xhr){ CSRFProtection(xhr) });
9
+ else $(document).ajaxSend(function(e, xhr){ CSRFProtection(xhr) });
10
+ };
11
+
12
+ ajax_headers();
13
+
14
+ $(document).on('click', '.show_link', function(event) {
15
+ $.get($(this).attr("href"));
16
+ return false;
17
+ });
18
+
19
+ $(document).on('click', '.close_link', function(event) {
20
+ $("#showpage").hide();
21
+ return false;
22
+ });
23
+
24
+ $(document).on('click', '.delete_link', function(event) {
25
+ $.ajax({
26
+ url: $(this).attr("href"),
27
+ type: 'DELETE'
28
+ });
29
+ return false;
30
+ });
31
+
32
+ $(document).on('click', '.delete_visible_link', function(event) {
33
+ var arr=$('tr.exception').map(function() { var id = $(this).attr("id"); return parseInt(id.replace(/^\w+_/, '')); }).get();
34
+ $.ajax({
35
+ url: $(this).attr("href"),
36
+ type: 'POST',
37
+ data: $.param({ids: arr}),
38
+ dataType: 'script'
39
+ });
40
+ return false;
41
+ });
42
+
43
+ $(document).on('click', '.filter_link', function(event) {
44
+ $('.filter_link').removeClass('selected');
45
+ $(this).addClass('selected');
46
+ $.ajax({
47
+ url: $(this).attr("href"),
48
+ type: 'POST',
49
+ dataType: 'script'
50
+ });
51
+ return false;
52
+ });
53
+
54
+ $(document).on('click', '#query-form :submit', function(event) {
55
+ $.ajax({
56
+ url: $("#query-form").attr("action"),
57
+ type: 'POST',
58
+ data: $("#query-form").serialize(),
59
+ dataType: 'script'
60
+ });
61
+ return false;
62
+ });
63
+
64
+ $(document).on('click', ".pagination a", function() {
65
+ $.getScript(this.href);
66
+ return false;
67
+ });
68
+ });
69
+
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,301 @@
1
+ body
2
+ {
3
+ margin:0;
4
+ padding:0;
5
+ background:#e8e8e8;
6
+ font-family: 'Lucida Grande', Arial, Helvetica, sans-serif;
7
+ font-size: 12px;
8
+ color:white;
9
+ }
10
+
11
+ #container
12
+ {
13
+ margin:0 auto;
14
+ min-width:800px;
15
+ }
16
+
17
+ #left
18
+ {
19
+ margin-right:235px;
20
+ }
21
+
22
+ #left .page
23
+ {
24
+ background:#e8e8e8;
25
+ border:2px solid #999;
26
+ border-width:0 2px 2px 0;
27
+ padding:25px;
28
+ color:black;
29
+ }
30
+
31
+ #right
32
+ {
33
+ margin-top:1em;
34
+ float:right;
35
+ xwidth:22%;
36
+ width:200px;
37
+ margin-right:1.25em;
38
+ }
39
+
40
+ #right hr
41
+ {
42
+ border:0;
43
+ border-top:1px solid #222;
44
+ }
45
+
46
+ #search
47
+ {
48
+ margin-top:2em;
49
+ background:#e8e8e8;
50
+ padding:10px 5px;
51
+ border:1px solid #222;
52
+ border-width:1px 0;
53
+ }
54
+
55
+
56
+ ul.filters
57
+ {
58
+ list-style-type:none;
59
+ padding:0;
60
+ margin:0;
61
+ }
62
+ ul.filters li { margin-bottom:0.2em;}
63
+
64
+
65
+ ul.filters a,
66
+ ul.filters a:visited {
67
+ display:block;
68
+ padding:1px 7px;
69
+ color:#000;
70
+ text-decoration:none;
71
+ }
72
+
73
+ ul.filters a:hover
74
+ {
75
+ background:#666;
76
+ color:white;
77
+ }
78
+
79
+ ul.filters a.selected,
80
+ ul.filters a.selected:visited,
81
+ ul.filters a:active {
82
+ color:gold;
83
+ background-color:#333;
84
+ text-decoration:none;
85
+ font-weight:bold;
86
+ }
87
+
88
+
89
+ onclick a:hover
90
+ {
91
+ color:#fff;
92
+ text-decoration:none;
93
+ }
94
+
95
+ #exceptions table
96
+ {
97
+ width:99%;
98
+ border-collapse:collapse;
99
+ }
100
+
101
+ td
102
+ {
103
+ padding:5px 10px;
104
+ xborder:1px solid #ddd;
105
+ }
106
+
107
+ #backtrace
108
+ {
109
+ overflow:auto;
110
+ margin-top:0.25em;
111
+ }
112
+
113
+ h2
114
+ {
115
+ background-color:#ddd;
116
+ font-size:14px;
117
+ padding:3px 10px;
118
+ }
119
+
120
+ form {margin:0;}
121
+
122
+ h3 {
123
+ font-size:14px;
124
+ color:#ddd;
125
+ background:#222;
126
+ padding:3px 7px;
127
+ }
128
+
129
+ div.date
130
+ {
131
+ color:#666;
132
+ }
133
+
134
+ h1
135
+ {
136
+ margin-top:0.25em;
137
+ font-size:18px;
138
+ padding-bottom:5px;
139
+ border-bottom:2px solid #ddd;
140
+ }
141
+
142
+ h1 span
143
+ {
144
+ color:#aaa;
145
+ font-weight:normal;
146
+ }
147
+
148
+ a
149
+ {
150
+ color:#369;
151
+ text-decoration:none;
152
+ }
153
+ a:hover
154
+ {
155
+ color:blue;
156
+ text-decoration:underline;
157
+ }
158
+
159
+ th
160
+ {
161
+ text-align:left;
162
+ xbackground:#333;
163
+ xcolor:gold;
164
+ padding:2px 10px;
165
+ }
166
+
167
+ tr { xcursor:pointer; }
168
+
169
+ tr.eor td
170
+ {
171
+ background:#e7e7e7;
172
+
173
+ }
174
+
175
+ /*
176
+ tr:hover td,
177
+ tr.eor:hover td
178
+ {
179
+ background:#333;
180
+ color:white;
181
+ }
182
+ tr:hover td a,
183
+ tr.eor:hover td a { color:gold; }
184
+ */
185
+
186
+ .message
187
+ {
188
+ color: #555;
189
+ font-size: 11px;
190
+ }
191
+
192
+ a.util
193
+ {
194
+ color:#c00;
195
+ }
196
+
197
+ .pipe
198
+ {
199
+ color:#999;
200
+ }
201
+
202
+ .tools { float:right; }
203
+
204
+ .time
205
+ {
206
+ color:#666;
207
+ xvertical-align:top;
208
+ }
209
+
210
+
211
+ .expclass
212
+ {
213
+ xcolor:#999;
214
+ }
215
+
216
+ tr.deleted td {
217
+ color:#aaa;
218
+ text-decoration: line-through;
219
+ }
220
+ tr.deleted td a { color:#aaa; }
221
+
222
+ .pages { float:right; margin-right:1em; }
223
+ .pages a { text-decoration:underline; }
224
+ .pages-bottom { border-top:2px solid #ddd; text-align:right; float:none;
225
+ padding-top:0.4em;
226
+ margin-top:0.4em;
227
+ padding-right:1em;
228
+ margin-right:0;
229
+ }
230
+ .pages nav { display: inline; }
231
+ .pagination { display: inline; list-style: none; padding: 0; }
232
+ .pagination > .page-item { display: inline; }
233
+
234
+ /* right */
235
+
236
+ #right h4
237
+ {
238
+ xbackground:#171717;
239
+ xbackground:#333;
240
+ color:#999;
241
+ padding:3px 5px;
242
+ margin-bottom:0.5em;
243
+ font-weight:normal;
244
+ }
245
+
246
+ /* tabs */
247
+
248
+ ul.tabs
249
+ {
250
+ list-style-type:none;
251
+ padding:0;
252
+ margin:1em 0;
253
+ float:left;
254
+ }
255
+ ul.tabs li { float:left; display:inline; }
256
+ ul.tabs li a
257
+ {
258
+ padding:3px 7px;
259
+ margin-right:1em;
260
+ text-decoration:none;
261
+ color:black;
262
+ }
263
+
264
+ ul.tabs li a:hover
265
+ {
266
+ background:#666;
267
+ color:white;
268
+ }
269
+ ul.tabs li.selected a
270
+ {
271
+ background:black;
272
+ color:white;
273
+ }
274
+
275
+ #activity {
276
+ position:fixed;
277
+ top:0; right:18px;
278
+ width:200px;
279
+ padding:5px 0;
280
+ text-align:center;
281
+ background-color:#cf1313;
282
+ color:#fff;
283
+ opacity:.8;
284
+ }
285
+
286
+ #feed {
287
+ margin-top: 15px;
288
+ }
289
+
290
+ /* html5 changes */
291
+
292
+ #exceptions table { border-collapse: separate; border-spacing: 0 8px; }
293
+ #exceptions table thead { display: none; }
294
+ #exceptions table tbody tr.exception td.time { white-space: nowrap }
295
+ #exceptions table td { background: #ddd; box-shadow: 2px 2px 1px #ccc; padding: 10px 15px; }
296
+ #exceptions table tr td:first-child { border-top-left-radius: 4px; }
297
+ #exceptions table tr td:last-child { border-top-right-radius: 4px; }
298
+ #exceptions table tr td:first-child { border-bottom-left-radius: 4px; }
299
+ #exceptions table tr td:last-child { border-bottom-right-radius: 4px; }
300
+
301
+ #container #left #showpage { margin-bottom:1em; }
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,4 @@
1
+ module ExceptionLogger
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,105 @@
1
+ module ExceptionLogger
2
+ class LoggedExceptionsController < ApplicationController
3
+ layout 'exception_logger/application'
4
+
5
+ cattr_accessor :application_name
6
+
7
+ helper_method :params_filters
8
+
9
+ # ApplicationController.class_eval do
10
+ # rescue_from Exception, with: :log_exception_handler
11
+ # end
12
+
13
+ def index
14
+ @exception_names = LoggedException.class_names
15
+ @controller_actions = LoggedException.controller_actions
16
+ query
17
+ end
18
+
19
+ def query
20
+ exceptions = LoggedException.sorted
21
+ unless params[:id].blank?
22
+ exceptions = exceptions.where(id: params[:id])
23
+ end
24
+ unless params[:query].blank?
25
+ exceptions = exceptions.message_like(params[:query])
26
+ end
27
+ unless params[:date_ranges_filter].blank?
28
+ exceptions = exceptions.days_old(params[:date_ranges_filter])
29
+ end
30
+ unless params[:exception_names_filter].blank?
31
+ exceptions = exceptions.by_exception_class(params[:exception_names_filter])
32
+ end
33
+ unless params[:controller_actions_filter].blank?
34
+ c_a_params = params[:controller_actions_filter].split('/')
35
+ controller_filter = c_a_params.first.underscore
36
+ action_filter = c_a_params.last.downcase
37
+ exceptions = exceptions.by_controller(controller_filter)
38
+ exceptions = exceptions.by_action(action_filter)
39
+ end
40
+ @exceptions = exceptions.paginate(page: params[:page], per_page: 30)
41
+
42
+ respond_to do |format|
43
+ format.html { redirect_to action: 'index' unless action_name == 'index' }
44
+ format.js
45
+ end
46
+ end
47
+
48
+ def feed
49
+ @exceptions = LoggedException.all
50
+
51
+ respond_to do |format|
52
+ format.rss { render layout: false }
53
+ end
54
+ end
55
+
56
+ def show
57
+ @exception = LoggedException.where(id: params[:id]).first
58
+
59
+ respond_to do |format|
60
+ format.js
61
+ format.html
62
+ end
63
+ end
64
+
65
+ def destroy
66
+ @exception = LoggedException.where(id: params[:id]).first
67
+ @exception.destroy
68
+ end
69
+
70
+ def destroy_all
71
+ LoggedException.where(id: params[:ids]).delete_all unless params[:ids].blank?
72
+ query
73
+ end
74
+
75
+ def clear
76
+ LoggedException.delete_all
77
+ redirect_back fallback_location: request.referer
78
+ end
79
+
80
+ private
81
+
82
+ def params_filters
83
+ {
84
+ query: params[:query],
85
+ date_ranges_filter: params[:date_ranges_filter],
86
+ exception_names_filter: params[:exception_names_filter],
87
+ controller_actions_filter: params[:controller_actions_filter],
88
+ }
89
+ end
90
+
91
+ def access_denied_with_basic_auth
92
+ headers["Status"] = "Unauthorized"
93
+ headers["WWW-Authenticate"] = %(Basic realm="Web Password")
94
+ render text: "Could't authenticate you", status: '401 Unauthorized'
95
+ end
96
+
97
+ @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
98
+ # gets BASIC auth info
99
+ def get_auth_data
100
+ auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
101
+ auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
102
+ return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,4 @@
1
+ module ExceptionLogger
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,38 @@
1
+ module ExceptionLogger
2
+ module LoggedExceptionsHelper
3
+ def pretty_exception_date(exception)
4
+ if Date.today == exception.created_at.to_date
5
+ "#{I18n.t('exception_logger.logged_exceptions.index.today')}, #{exception.created_at.strftime(Time::DATE_FORMATS[:exc_time])}"
6
+ else
7
+ exception.created_at.strftime(Time::DATE_FORMATS[:exc_date])
8
+ end
9
+ end
10
+
11
+ def filtered?
12
+ [:query, :date_ranges_filter, :exception_names_filter, :controller_actions_filter].any? { |p| params[p] }
13
+ end
14
+
15
+ def listify(text)
16
+ list_items = text.scan(/^\s*\* (.+)/).map {|match| content_tag(:li, match.first) }
17
+ content_tag(:ul, list_items)
18
+ end
19
+
20
+ def page_title(text)
21
+ title = ""
22
+ unless controller.application_name.blank?
23
+ title << "#{controller.application_name} :: "
24
+ end
25
+ title << text.to_s
26
+ content_for(:title, title.to_s)
27
+ end
28
+
29
+ # Rescue textilize call if RedCloth is not available.
30
+ def pretty_format(text)
31
+ begin
32
+ textilize(text).html_safe
33
+ rescue
34
+ simple_format(text).html_safe
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,99 @@
1
+ module ExceptionLogger
2
+ class LoggedException < ActiveRecord::Base
3
+ scope :by_exception_class, ->(exception_class) { where(exception_class: exception_class) }
4
+ scope :by_controller_and_action, lambda { |controller_name, action_name|
5
+ where(controller_name: controller_name, action_name: action_name)
6
+ }
7
+ scope :by_controller, ->(controller_name) { where(controller_name: controller_name) }
8
+ scope :by_action, ->(action_name) { where(action_name: action_name) }
9
+ scope :message_like, ->(query) { where('message like ?', "%#{query}%") }
10
+ scope :days_old, ->(day_number) { where('created_at >= ?', day_number.to_f.days.ago.utc) }
11
+ scope :sorted, -> { order('created_at DESC') }
12
+
13
+ # Class methods
14
+
15
+ self.table_name = 'logged_exceptions'
16
+
17
+ def self.create_from_exception(controller, exception, data)
18
+ message = exception.message.inspect
19
+ message << "\n* Extra Data\n#{data}" unless data.blank?
20
+ if exception.class.name != 'ActiveRecord::RecordNotFound'
21
+ e = create!(
22
+ exception_class: exception.class.name,
23
+ controller_name: controller.controller_path,
24
+ action_name: controller.action_name,
25
+ message: message,
26
+ backtrace: exception.backtrace,
27
+ request: controller.request
28
+ )
29
+ else
30
+ self
31
+ end
32
+ end
33
+
34
+ def self.class_names
35
+ select('DISTINCT exception_class')
36
+ .order(:exception_class)
37
+ .collect(&:exception_class)
38
+ end
39
+
40
+ def self.controller_actions
41
+ select('DISTINCT controller_name, action_name')
42
+ .order(:controller_name, :action_name)
43
+ .collect(&:controller_action)
44
+ end
45
+
46
+ def self.host_name
47
+ `hostname -s`.chomp
48
+ end
49
+
50
+ # Instance methods
51
+
52
+ def name
53
+ "#{exception_class} in #{controller_action}"
54
+ end
55
+
56
+ def controller_action
57
+ @controller_action ||= "#{controller_name.camelcase}/#{action_name}"
58
+ end
59
+
60
+ # Setter methods
61
+
62
+ def backtrace=(trace)
63
+ trace = sanitize_backtrace(trace) * "\n" unless trace.is_a?(String)
64
+ write_attribute :backtrace, trace
65
+ end
66
+
67
+ def request=(request)
68
+ if request.is_a?(String)
69
+ write_attribute :request, request
70
+ else
71
+ max = request.env.keys.max { |a, b| a.length <=> b.length }
72
+ env = request.env.keys.sort.inject [] do |env, key|
73
+ env << '* ' + format('%-*s: %s', max.length, key, request.env[key].to_s.strip)
74
+ end
75
+ write_attribute(:environment, (env << "* Process: #{$$}" << "* Server : #{self.class.host_name}") * "\n")
76
+
77
+ write_attribute(:request, [
78
+ "* URL:#{unless request.get?
79
+ " #{request.method.to_s.upcase}"
80
+ end} #{request.protocol}#{request.env['HTTP_HOST']}#{request.fullpath}",
81
+ "* Format: #{request.format}",
82
+ "* Parameters: #{request.parameters.inspect}",
83
+ "* Rails Root: #{rails_root}"
84
+ ].join("\n"))
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def sanitize_backtrace(trace)
91
+ backtrace_regex = /^#{Regexp.escape(rails_root)}/
92
+ trace.collect { |line| Pathname.new(line.gsub(backtrace_regex, '[RAILS_ROOT]')).cleanpath.to_s }
93
+ end
94
+
95
+ def rails_root
96
+ @rails_root ||= Pathname.new(Rails.root).cleanpath.to_s
97
+ end
98
+ end
99
+ end