exception_logger 0.2.0

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