exception_handling 1.2.1 → 2.2.1
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 +5 -5
- data/.ruby-version +1 -1
- data/Gemfile +17 -0
- data/Gemfile.lock +142 -102
- data/README.md +11 -2
- data/config/exception_filters.yml +2 -0
- data/exception_handling.gemspec +6 -10
- data/lib/exception_handling.rb +222 -313
- data/lib/exception_handling/exception_catalog.rb +8 -6
- data/lib/exception_handling/exception_description.rb +8 -6
- data/lib/exception_handling/exception_info.rb +272 -0
- data/lib/exception_handling/honeybadger_callbacks.rb +42 -0
- data/lib/exception_handling/log_stub_error.rb +18 -3
- data/lib/exception_handling/mailer.rb +14 -0
- data/lib/exception_handling/methods.rb +26 -8
- data/lib/exception_handling/testing.rb +2 -2
- data/lib/exception_handling/version.rb +3 -1
- data/test/helpers/controller_helpers.rb +27 -0
- data/test/helpers/exception_helpers.rb +11 -0
- data/test/test_helper.rb +42 -19
- data/test/unit/exception_handling/exception_catalog_test.rb +19 -0
- data/test/unit/exception_handling/exception_description_test.rb +12 -1
- data/test/unit/exception_handling/exception_info_test.rb +501 -0
- data/test/unit/exception_handling/honeybadger_callbacks_test.rb +85 -0
- data/test/unit/exception_handling/log_error_stub_test.rb +26 -3
- data/test/unit/exception_handling/mailer_test.rb +39 -14
- data/test/unit/exception_handling/methods_test.rb +40 -18
- data/test/unit/exception_handling_test.rb +947 -539
- data/views/exception_handling/mailer/escalate_custom.html.erb +17 -0
- data/views/exception_handling/mailer/exception_notification.html.erb +1 -1
- data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +1 -1
- metadata +28 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 925c7332d4385df7c8ccd52f2df818d887e31d64a643c99cc5e682d89ddba535
|
4
|
+
data.tar.gz: 54df02d0d0cedea56c2495de72b2bcf706d2f703111bb46cb80deb34a026d8df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd8d879e6ea219dbaa979d7436ab30f409f1942277b275fcc6d957d84095da84e14bf6620c4b834d5562012b5abcf6767da3d3a872142d5251f927ffe129191a
|
7
|
+
data.tar.gz: 1a2f9e4c18b47d01919e50ab6c69ef182068b38ff5f9e4656fd6123fcac53d404ce0042c6b23a2aef19791f02dffc248c03a08067a386050ccdd02fb1055657b
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.4.2
|
data/Gemfile
CHANGED
@@ -2,3 +2,20 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in attr_default.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
gem 'hobo_support', '2.0.1', git: 'git@github.com:Invoca/hobosupport', ref: 'b9086322274b474a2b5bae507c4885e55d4aa050'
|
7
|
+
gem 'invoca-utils', git: 'git@github.com:Invoca/invoca-utils', ref: '891b8f7e1af0f6324bf85601046907143122e204'
|
8
|
+
gem 'activesupport', '>= 4.2.11.1'
|
9
|
+
gem 'actionpack', '>= 4.2.11.1'
|
10
|
+
gem 'actionmailer', '>= 4.2.11.1'
|
11
|
+
|
12
|
+
group :development do
|
13
|
+
gem 'rake', '>=0.9'
|
14
|
+
gem 'shoulda', '> 3.1.1'
|
15
|
+
gem 'rr'
|
16
|
+
gem 'pry'
|
17
|
+
end
|
18
|
+
|
19
|
+
group :test do
|
20
|
+
gem 'honeybadger', '3.3.1-1', git: 'git@github.com:Invoca/honeybadger-ruby', ref: 'bb5f2b8a86e4147c38a6270d39ad610fab4dd5e6'
|
21
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,124 +1,164 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git@github.com:Invoca/hobosupport
|
3
|
+
revision: b9086322274b474a2b5bae507c4885e55d4aa050
|
4
|
+
ref: b9086322274b474a2b5bae507c4885e55d4aa050
|
5
|
+
specs:
|
6
|
+
hobo_support (2.0.1)
|
7
|
+
rails (~> 4.2.10)
|
8
|
+
|
9
|
+
GIT
|
10
|
+
remote: git@github.com:Invoca/honeybadger-ruby
|
11
|
+
revision: bb5f2b8a86e4147c38a6270d39ad610fab4dd5e6
|
12
|
+
ref: bb5f2b8a86e4147c38a6270d39ad610fab4dd5e6
|
13
|
+
specs:
|
14
|
+
honeybadger (3.3.1.pre.1)
|
15
|
+
|
16
|
+
GIT
|
17
|
+
remote: git@github.com:Invoca/invoca-utils
|
18
|
+
revision: 891b8f7e1af0f6324bf85601046907143122e204
|
19
|
+
ref: 891b8f7e1af0f6324bf85601046907143122e204
|
20
|
+
specs:
|
21
|
+
invoca-utils (0.0.3)
|
22
|
+
|
1
23
|
PATH
|
2
24
|
remote: .
|
3
25
|
specs:
|
4
|
-
exception_handling (
|
5
|
-
actionmailer (~>
|
6
|
-
actionpack (~>
|
7
|
-
activesupport (~>
|
8
|
-
|
26
|
+
exception_handling (2.2.1)
|
27
|
+
actionmailer (~> 4.2)
|
28
|
+
actionpack (~> 4.2)
|
29
|
+
activesupport (~> 4.2)
|
30
|
+
contextual_logger
|
31
|
+
eventmachine (~> 1.0)
|
9
32
|
hobo_support
|
10
|
-
invoca-utils (~> 0.0
|
33
|
+
invoca-utils (~> 0.0)
|
11
34
|
|
12
35
|
GEM
|
13
36
|
remote: https://rubygems.org/
|
14
37
|
specs:
|
15
|
-
actionmailer (
|
16
|
-
actionpack (=
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
38
|
+
actionmailer (4.2.11.1)
|
39
|
+
actionpack (= 4.2.11.1)
|
40
|
+
actionview (= 4.2.11.1)
|
41
|
+
activejob (= 4.2.11.1)
|
42
|
+
mail (~> 2.5, >= 2.5.4)
|
43
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
44
|
+
actionpack (4.2.11.1)
|
45
|
+
actionview (= 4.2.11.1)
|
46
|
+
activesupport (= 4.2.11.1)
|
47
|
+
rack (~> 1.6)
|
48
|
+
rack-test (~> 0.6.2)
|
49
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
50
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
51
|
+
actionview (4.2.11.1)
|
52
|
+
activesupport (= 4.2.11.1)
|
53
|
+
builder (~> 3.1)
|
22
54
|
erubis (~> 2.7.0)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
activemodel (
|
29
|
-
activesupport (=
|
30
|
-
builder (~> 3.
|
31
|
-
activerecord (
|
32
|
-
activemodel (=
|
33
|
-
activesupport (=
|
34
|
-
arel (~>
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
55
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
56
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
57
|
+
activejob (4.2.11.1)
|
58
|
+
activesupport (= 4.2.11.1)
|
59
|
+
globalid (>= 0.3.0)
|
60
|
+
activemodel (4.2.11.1)
|
61
|
+
activesupport (= 4.2.11.1)
|
62
|
+
builder (~> 3.1)
|
63
|
+
activerecord (4.2.11.1)
|
64
|
+
activemodel (= 4.2.11.1)
|
65
|
+
activesupport (= 4.2.11.1)
|
66
|
+
arel (~> 6.0)
|
67
|
+
activesupport (4.2.11.1)
|
68
|
+
i18n (~> 0.7)
|
69
|
+
minitest (~> 5.1)
|
70
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
71
|
+
tzinfo (~> 1.1)
|
72
|
+
arel (6.0.4)
|
73
|
+
builder (3.2.3)
|
74
|
+
coderay (1.1.2)
|
75
|
+
concurrent-ruby (1.1.5)
|
76
|
+
contextual_logger (0.2.1)
|
77
|
+
json
|
78
|
+
crass (1.0.4)
|
47
79
|
erubis (2.7.0)
|
48
|
-
eventmachine (1.
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
pry (0.10.0)
|
80
|
+
eventmachine (1.2.7)
|
81
|
+
globalid (0.4.2)
|
82
|
+
activesupport (>= 4.2.0)
|
83
|
+
i18n (0.9.5)
|
84
|
+
concurrent-ruby (~> 1.0)
|
85
|
+
json (2.2.0)
|
86
|
+
loofah (2.2.3)
|
87
|
+
crass (~> 1.0.2)
|
88
|
+
nokogiri (>= 1.5.9)
|
89
|
+
mail (2.7.1)
|
90
|
+
mini_mime (>= 0.1.1)
|
91
|
+
method_source (0.9.2)
|
92
|
+
mini_mime (1.0.1)
|
93
|
+
mini_portile2 (2.4.0)
|
94
|
+
minitest (5.11.3)
|
95
|
+
nokogiri (1.10.3)
|
96
|
+
mini_portile2 (~> 2.4.0)
|
97
|
+
pry (0.12.2)
|
67
98
|
coderay (~> 1.1.0)
|
68
|
-
method_source (~> 0.
|
69
|
-
|
70
|
-
rack (
|
71
|
-
rack-cache (1.2)
|
72
|
-
rack (>= 0.4)
|
73
|
-
rack-ssl (1.3.4)
|
74
|
-
rack
|
75
|
-
rack-test (0.6.2)
|
99
|
+
method_source (~> 0.9.0)
|
100
|
+
rack (1.6.11)
|
101
|
+
rack-test (0.6.3)
|
76
102
|
rack (>= 1.0)
|
77
|
-
rails (
|
78
|
-
actionmailer (=
|
79
|
-
actionpack (=
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
103
|
+
rails (4.2.11.1)
|
104
|
+
actionmailer (= 4.2.11.1)
|
105
|
+
actionpack (= 4.2.11.1)
|
106
|
+
actionview (= 4.2.11.1)
|
107
|
+
activejob (= 4.2.11.1)
|
108
|
+
activemodel (= 4.2.11.1)
|
109
|
+
activerecord (= 4.2.11.1)
|
110
|
+
activesupport (= 4.2.11.1)
|
111
|
+
bundler (>= 1.3.0, < 2.0)
|
112
|
+
railties (= 4.2.11.1)
|
113
|
+
sprockets-rails
|
114
|
+
rails-deprecated_sanitizer (1.0.3)
|
115
|
+
activesupport (>= 4.2.0.alpha)
|
116
|
+
rails-dom-testing (1.0.9)
|
117
|
+
activesupport (>= 4.2.0, < 5.0)
|
118
|
+
nokogiri (~> 1.6)
|
119
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
120
|
+
rails-html-sanitizer (1.0.4)
|
121
|
+
loofah (~> 2.2, >= 2.2.2)
|
122
|
+
railties (4.2.11.1)
|
123
|
+
actionpack (= 4.2.11.1)
|
124
|
+
activesupport (= 4.2.11.1)
|
89
125
|
rake (>= 0.8.7)
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
shoulda (
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
tilt (1.4.1)
|
111
|
-
treetop (1.4.15)
|
112
|
-
polyglot
|
113
|
-
polyglot (>= 0.3.1)
|
114
|
-
tzinfo (0.3.42)
|
126
|
+
thor (>= 0.18.1, < 2.0)
|
127
|
+
rake (12.3.2)
|
128
|
+
rr (1.2.1)
|
129
|
+
shoulda (3.6.0)
|
130
|
+
shoulda-context (~> 1.0, >= 1.0.1)
|
131
|
+
shoulda-matchers (~> 3.0)
|
132
|
+
shoulda-context (1.2.2)
|
133
|
+
shoulda-matchers (3.1.3)
|
134
|
+
activesupport (>= 4.0.0)
|
135
|
+
sprockets (3.7.2)
|
136
|
+
concurrent-ruby (~> 1.0)
|
137
|
+
rack (> 1, < 3)
|
138
|
+
sprockets-rails (3.2.1)
|
139
|
+
actionpack (>= 4.0)
|
140
|
+
activesupport (>= 4.0)
|
141
|
+
sprockets (>= 3.0.0)
|
142
|
+
thor (0.20.3)
|
143
|
+
thread_safe (0.3.6)
|
144
|
+
tzinfo (1.2.5)
|
145
|
+
thread_safe (~> 0.1)
|
115
146
|
|
116
147
|
PLATFORMS
|
117
148
|
ruby
|
118
149
|
|
119
150
|
DEPENDENCIES
|
151
|
+
actionmailer (>= 4.2.11.1)
|
152
|
+
actionpack (>= 4.2.11.1)
|
153
|
+
activesupport (>= 4.2.11.1)
|
120
154
|
exception_handling!
|
155
|
+
hobo_support (= 2.0.1)!
|
156
|
+
honeybadger (= 3.3.1.pre.1)!
|
157
|
+
invoca-utils!
|
121
158
|
pry
|
122
159
|
rake (>= 0.9)
|
123
160
|
rr
|
124
|
-
shoulda (
|
161
|
+
shoulda (> 3.1.1)
|
162
|
+
|
163
|
+
BUNDLED WITH
|
164
|
+
1.16.1
|
data/README.md
CHANGED
@@ -84,12 +84,21 @@ Then tie this in using the `custom_data_hook`:
|
|
84
84
|
|
85
85
|
There is another hook available intended for custom actions after an error email is sent. This can be used to send information about errors to your alerting subsystem. For example:
|
86
86
|
|
87
|
-
def log_error_metrics(exception_data, exception)
|
88
|
-
if
|
87
|
+
def log_error_metrics(exception_data, exception, treat_like_warning, honeybadger_status)
|
88
|
+
if treat_like_warning
|
89
89
|
Invoca::Metrics::Client.metrics.counter("exception_handling/warning")
|
90
90
|
else
|
91
91
|
Invoca::Metrics::Client.metrics.counter("exception_handling/exception")
|
92
92
|
end
|
93
|
+
|
94
|
+
case honeybadger_status
|
95
|
+
when :success
|
96
|
+
Invoca::Metrics::Client.metrics.counter("exception_handling.honeybadger.success")
|
97
|
+
when :failure
|
98
|
+
Invoca::Metrics::Client.metrics.counter("exception_handling.honeybadger.failure")
|
99
|
+
when :skipped
|
100
|
+
Invoca::Metrics::Client.metrics.counter("exception_handling.honeybadger.skipped")
|
101
|
+
end
|
93
102
|
end
|
94
103
|
ExceptionHandling.post_log_error_hook = method(:log_error_metrics)
|
95
104
|
|
@@ -3,6 +3,7 @@ Test BS:
|
|
3
3
|
error: "Some BS"
|
4
4
|
send_email: true
|
5
5
|
notes: "this is used by a test"
|
6
|
+
send_to_honeybadger: true
|
6
7
|
|
7
8
|
All script kiddies:
|
8
9
|
error: "ScriptKiddie suspected because of HTTP request without a referer"
|
@@ -48,6 +49,7 @@ InvalidAuth:
|
|
48
49
|
|
49
50
|
NoRoute:
|
50
51
|
error: "No route matches"
|
52
|
+
send_to_honeybadger: true
|
51
53
|
|
52
54
|
SearchWebPlacementsJson:
|
53
55
|
error: "search_web_placement Invalid JSON response from yahoo api"
|
data/exception_handling.gemspec
CHANGED
@@ -14,15 +14,11 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.require_paths = ["lib"]
|
15
15
|
spec.version = ExceptionHandling::VERSION
|
16
16
|
|
17
|
-
spec.add_dependency 'eventmachine',
|
18
|
-
spec.add_dependency 'activesupport', '~>
|
19
|
-
spec.add_dependency 'actionpack',
|
20
|
-
spec.add_dependency 'actionmailer',
|
21
|
-
spec.add_dependency 'invoca-utils',
|
17
|
+
spec.add_dependency 'eventmachine', '~> 1.0'
|
18
|
+
spec.add_dependency 'activesupport', '~> 4.2'
|
19
|
+
spec.add_dependency 'actionpack', '~> 4.2'
|
20
|
+
spec.add_dependency 'actionmailer', '~> 4.2'
|
21
|
+
spec.add_dependency 'invoca-utils', '~> 0.0'
|
22
22
|
spec.add_dependency 'hobo_support'
|
23
|
-
|
24
|
-
spec.add_development_dependency 'rake', '>=0.9'
|
25
|
-
spec.add_development_dependency 'shoulda', '=3.1.1'
|
26
|
-
spec.add_development_dependency 'rr'
|
27
|
-
spec.add_development_dependency 'pry'
|
23
|
+
spec.add_dependency 'contextual_logger'
|
28
24
|
end
|
data/lib/exception_handling.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'timeout'
|
2
2
|
require 'active_support'
|
3
3
|
require 'active_support/core_ext/hash'
|
4
|
+
require 'contextual_logger'
|
4
5
|
|
5
6
|
require 'invoca/utils'
|
6
7
|
|
@@ -10,6 +11,8 @@ require "exception_handling/methods"
|
|
10
11
|
require "exception_handling/log_stub_error"
|
11
12
|
require "exception_handling/exception_description"
|
12
13
|
require "exception_handling/exception_catalog"
|
14
|
+
require "exception_handling/exception_info"
|
15
|
+
require "exception_handling/honeybadger_callbacks.rb"
|
13
16
|
|
14
17
|
_ = ActiveSupport::HashWithIndifferentAccess
|
15
18
|
|
@@ -22,64 +25,17 @@ module ExceptionHandling # never included
|
|
22
25
|
SUMMARY_THRESHOLD = 5
|
23
26
|
SUMMARY_PERIOD = 60*60 # 1.hour
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
ENVIRONMENT_WHITELIST = [
|
28
|
-
/^HTTP_/,
|
29
|
-
/^QUERY_/,
|
30
|
-
/^REQUEST_/,
|
31
|
-
/^SERVER_/
|
32
|
-
]
|
33
|
-
|
34
|
-
ENVIRONMENT_OMIT =(
|
35
|
-
<<EOF
|
36
|
-
CONTENT_TYPE: application/x-www-form-urlencoded
|
37
|
-
GATEWAY_INTERFACE: CGI/1.2
|
38
|
-
HTTP_ACCEPT: */*
|
39
|
-
HTTP_ACCEPT: */*, text/javascript, text/html, application/xml, text/xml, */*
|
40
|
-
HTTP_ACCEPT_CHARSET: ISO-8859-1,utf-8;q=0.7,*;q=0.7
|
41
|
-
HTTP_ACCEPT_ENCODING: gzip, deflate
|
42
|
-
HTTP_ACCEPT_ENCODING: gzip,deflate
|
43
|
-
HTTP_ACCEPT_LANGUAGE: en-us
|
44
|
-
HTTP_CACHE_CONTROL: no-cache
|
45
|
-
HTTP_CONNECTION: Keep-Alive
|
46
|
-
HTTP_HOST: www.invoca.com
|
47
|
-
HTTP_MAX_FORWARDS: 10
|
48
|
-
HTTP_UA_CPU: x86
|
49
|
-
HTTP_VERSION: HTTP/1.1
|
50
|
-
HTTP_X_FORWARDED_HOST: www.invoca.com
|
51
|
-
HTTP_X_FORWARDED_SERVER: www2.invoca.com
|
52
|
-
HTTP_X_REQUESTED_WITH: XMLHttpRequest
|
53
|
-
LANG:
|
54
|
-
LANG:
|
55
|
-
PATH: /sbin:/usr/sbin:/bin:/usr/bin
|
56
|
-
PWD: /
|
57
|
-
RAILS_ENV: production
|
58
|
-
RAW_POST_DATA: id=500
|
59
|
-
REMOTE_ADDR: 10.251.34.225
|
60
|
-
SCRIPT_NAME: /
|
61
|
-
SERVER_NAME: www.invoca.com
|
62
|
-
SERVER_PORT: 80
|
63
|
-
SERVER_PROTOCOL: HTTP/1.1
|
64
|
-
SERVER_SOFTWARE: Mongrel 1.1.4
|
65
|
-
SHLVL: 1
|
66
|
-
TERM: linux
|
67
|
-
TERM: xterm-color
|
68
|
-
_: /usr/bin/mongrel_cluster_ctl
|
69
|
-
EOF
|
70
|
-
).split("\n")
|
71
|
-
|
72
|
-
AUTHENTICATION_HEADERS = ['HTTP_AUTHORIZATION','X-HTTP_AUTHORIZATION','X_HTTP_AUTHORIZATION','REDIRECT_X_HTTP_AUTHORIZATION']
|
28
|
+
AUTHENTICATION_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'].freeze
|
29
|
+
HONEYBADGER_STATUSES = [:success, :failure, :skipped].freeze
|
73
30
|
|
74
31
|
class << self
|
75
32
|
|
76
33
|
#
|
77
34
|
# required settings
|
78
35
|
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
attr_accessor :logger
|
36
|
+
attr_writer :server_name
|
37
|
+
attr_writer :sender_address
|
38
|
+
attr_writer :exception_recipients
|
83
39
|
|
84
40
|
def server_name
|
85
41
|
@server_name or raise ArgumentError, "You must assign a value to #{self.name}.server_name"
|
@@ -97,15 +53,41 @@ EOF
|
|
97
53
|
@logger or raise ArgumentError, "You must assign a value to #{self.name}.logger"
|
98
54
|
end
|
99
55
|
|
56
|
+
def logger=(logger)
|
57
|
+
@logger = logger.is_a?(ContextualLogger) ? logger : ContextualLogger.new(logger)
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_metric_name(exception_data, exception, treat_like_warning)
|
61
|
+
metric_name = if exception_data['metric_name']
|
62
|
+
exception_data['metric_name']
|
63
|
+
elsif exception.is_a?(ExceptionHandling::Warning)
|
64
|
+
"warning"
|
65
|
+
elsif treat_like_warning
|
66
|
+
exception_name = "_#{exception.class.name.split('::').last}" if exception.present?
|
67
|
+
"unforwarded_exception#{exception_name}"
|
68
|
+
else
|
69
|
+
"exception"
|
70
|
+
end
|
71
|
+
|
72
|
+
"exception_handling.#{metric_name}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def default_honeybadger_metric_name(honeybadger_status)
|
76
|
+
metric_name = if honeybadger_status.in?(HONEYBADGER_STATUSES)
|
77
|
+
honeybadger_status
|
78
|
+
else
|
79
|
+
:unknown_status
|
80
|
+
end
|
81
|
+
"exception_handling.honeybadger.#{metric_name}"
|
82
|
+
end
|
83
|
+
|
100
84
|
#
|
101
85
|
# optional settings
|
102
86
|
#
|
87
|
+
attr_accessor :production_support_recipients
|
103
88
|
attr_accessor :escalation_recipients
|
104
89
|
attr_accessor :email_environment
|
105
|
-
attr_accessor :filter_list_filename
|
106
90
|
attr_accessor :mailer_send_enabled
|
107
|
-
attr_accessor :eventmachine_safe
|
108
|
-
attr_accessor :eventmachine_synchrony
|
109
91
|
attr_accessor :custom_data_hook
|
110
92
|
attr_accessor :post_log_error_hook
|
111
93
|
attr_accessor :stub_handler
|
@@ -113,6 +95,10 @@ EOF
|
|
113
95
|
attr_accessor :sensu_port
|
114
96
|
attr_accessor :sensu_prefix
|
115
97
|
|
98
|
+
attr_reader :filter_list_filename
|
99
|
+
attr_reader :eventmachine_safe
|
100
|
+
attr_reader :eventmachine_synchrony
|
101
|
+
|
116
102
|
@filter_list_filename = "./config/exception_filters.yml"
|
117
103
|
@mailer_send_enabled = true
|
118
104
|
@email_environment = ""
|
@@ -142,6 +128,15 @@ EOF
|
|
142
128
|
@eventmachine_synchrony = bool
|
143
129
|
end
|
144
130
|
|
131
|
+
def filter_list_filename=(filename)
|
132
|
+
@filter_list_filename = filename
|
133
|
+
@exception_catalog = nil
|
134
|
+
end
|
135
|
+
|
136
|
+
def exception_catalog
|
137
|
+
@exception_catalog ||= ExceptionCatalog.new(@filter_list_filename)
|
138
|
+
end
|
139
|
+
|
145
140
|
#
|
146
141
|
# internal settings (don't set directly)
|
147
142
|
#
|
@@ -159,10 +154,10 @@ EOF
|
|
159
154
|
#
|
160
155
|
def log_error_rack(exception, env, rack_filter)
|
161
156
|
timestamp = set_log_error_timestamp
|
162
|
-
|
157
|
+
exception_info = ExceptionInfo.new(exception, env, timestamp)
|
163
158
|
|
164
159
|
if stub_handler
|
165
|
-
return stub_handler.handle_stub_log_error(
|
160
|
+
return stub_handler.handle_stub_log_error(exception_info.data)
|
166
161
|
end
|
167
162
|
|
168
163
|
# TODO: add a more interesting custom description, like:
|
@@ -172,91 +167,94 @@ EOF
|
|
172
167
|
write_exception_to_log(exception, custom_description, timestamp)
|
173
168
|
|
174
169
|
if honeybadger?
|
175
|
-
send_exception_to_honeybadger(
|
170
|
+
send_exception_to_honeybadger(exception_info)
|
176
171
|
end
|
177
172
|
|
178
173
|
if should_send_email?
|
179
|
-
controller = env['action_controller.instance']
|
180
174
|
# controller may not exist in some cases (like most 404 errors)
|
181
|
-
if controller
|
182
|
-
|
183
|
-
controller.session["last_exception_timestamp"] = ExceptionHandling.last_exception_timestamp
|
175
|
+
if (controller = exception_info.controller)
|
176
|
+
controller.session["last_exception_timestamp"] = last_exception_timestamp
|
184
177
|
end
|
185
|
-
log_error_email(
|
178
|
+
log_error_email(exception_info)
|
186
179
|
end
|
187
180
|
end
|
188
181
|
|
189
182
|
#
|
190
183
|
# Normal Operation:
|
191
184
|
# Called directly by our code, usually from rescue blocks.
|
192
|
-
# Does
|
185
|
+
# Does three things: write to log file and send an email, may also send to honeybadger if defined
|
193
186
|
#
|
194
187
|
# Functional Test Operation:
|
195
188
|
# Calls into handle_stub_log_error and returns. no log file. no email.
|
196
189
|
#
|
197
|
-
def log_error(exception_or_string, exception_context = '', controller = nil,
|
190
|
+
def log_error(exception_or_string, exception_context = '', controller = nil, treat_like_warning: false, **log_context, &data_callback)
|
198
191
|
begin
|
199
192
|
ex = make_exception(exception_or_string)
|
200
193
|
timestamp = set_log_error_timestamp
|
201
|
-
|
194
|
+
exception_info = ExceptionInfo.new(ex, exception_context, timestamp, controller || current_controller, data_callback)
|
202
195
|
|
203
196
|
if stub_handler
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
if treat_as_local
|
214
|
-
return
|
215
|
-
end
|
216
|
-
|
217
|
-
if should_send_email?
|
218
|
-
controller ||= current_controller
|
219
|
-
|
220
|
-
if block_given?
|
221
|
-
# the expectation is that if the caller passed a block then they will be
|
222
|
-
# doing their own merge of hash values into data
|
223
|
-
begin
|
224
|
-
yield data
|
225
|
-
rescue Exception => ex
|
226
|
-
data.merge!(:environment => "Exception in yield: #{ex.class}:#{ex}")
|
227
|
-
end
|
228
|
-
elsif controller
|
229
|
-
# most of the time though, this method will not get passed a block
|
230
|
-
# and additional hash data is extracted from the controller
|
231
|
-
extract_and_merge_controller_data(controller, data)
|
232
|
-
end
|
233
|
-
|
234
|
-
log_error_email(data, ex)
|
197
|
+
stub_handler.handle_stub_log_error(exception_info.data)
|
198
|
+
else
|
199
|
+
write_exception_to_log(ex, exception_context, timestamp, **log_context)
|
200
|
+
external_notification_results = unless treat_like_warning || ex.is_a?(Warning)
|
201
|
+
send_external_notifications(exception_info)
|
202
|
+
end || {}
|
203
|
+
execute_custom_log_error_callback(exception_info.enhanced_data, exception_info.exception, treat_like_warning, external_notification_results)
|
204
|
+
nil
|
235
205
|
end
|
236
|
-
|
237
206
|
rescue LogErrorStub::UnexpectedExceptionLogged, LogErrorStub::ExpectedExceptionNotLogged
|
238
207
|
raise
|
239
208
|
rescue Exception => ex
|
240
|
-
$stderr.puts("
|
241
|
-
write_exception_to_log(ex, "
|
209
|
+
$stderr.puts("ExceptionHandlingError: log_error rescued exception while logging #{exception_context}: #{exception_or_string}:\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}")
|
210
|
+
write_exception_to_log(ex, "ExceptionHandlingError: log_error rescued exception while logging #{exception_context}: #{exception_or_string}", timestamp)
|
242
211
|
end
|
243
212
|
end
|
244
213
|
|
245
214
|
#
|
246
215
|
# Write an exception out to the log file using our own custom format.
|
247
216
|
#
|
248
|
-
def write_exception_to_log(ex, exception_context, timestamp)
|
217
|
+
def write_exception_to_log(ex, exception_context, timestamp, **log_context)
|
249
218
|
ActiveSupport::Deprecation.silence do
|
250
|
-
ExceptionHandling.logger.fatal("\
|
219
|
+
ExceptionHandling.logger.fatal("\nExceptionHandlingError (Error:#{timestamp}) #{ex.class} #{exception_context} (#{encode_utf8(ex.message.to_s)}):\n " + clean_backtrace(ex).join("\n ") + "\n\n", **log_context)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# Send notifications to configured external services
|
225
|
+
#
|
226
|
+
def send_external_notifications(exception_info)
|
227
|
+
results = {}
|
228
|
+
if honeybadger?
|
229
|
+
results[:honeybadger_status] = send_exception_to_honeybadger(exception_info)
|
230
|
+
end
|
231
|
+
|
232
|
+
if should_send_email?
|
233
|
+
log_error_email(exception_info)
|
251
234
|
end
|
235
|
+
results
|
252
236
|
end
|
253
237
|
|
254
238
|
#
|
255
239
|
# Log exception to honeybadger.io.
|
256
240
|
#
|
257
|
-
def send_exception_to_honeybadger(
|
258
|
-
|
259
|
-
|
241
|
+
def send_exception_to_honeybadger(exception_info)
|
242
|
+
exception = exception_info.exception
|
243
|
+
exception_description = exception_info.exception_description
|
244
|
+
if exception_info.send_to_honeybadger?
|
245
|
+
response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
|
246
|
+
error_message: exception.message.to_s,
|
247
|
+
exception: exception,
|
248
|
+
context: exception_info.honeybadger_context_data)
|
249
|
+
response ? :success : :failure
|
250
|
+
else
|
251
|
+
log_info("Filtered exception using '#{exception_description.filter_name}'; not sending notification to Honeybadger")
|
252
|
+
:skipped
|
253
|
+
end
|
254
|
+
rescue Exception => ex
|
255
|
+
$stderr.puts("ExceptionHandling.send_exception_to_honeybadger rescued exception while logging #{exception_info.exception_context}:\n#{exception.class}: #{exception.message}:\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}")
|
256
|
+
write_exception_to_log(ex, "ExceptionHandling.send_exception_to_honeybadger rescued exception while logging #{exception_info.exception_context}:\n#{exception.class}: #{exception.message}", exception_info.timestamp)
|
257
|
+
:failure
|
260
258
|
end
|
261
259
|
|
262
260
|
#
|
@@ -267,74 +265,77 @@ EOF
|
|
267
265
|
end
|
268
266
|
|
269
267
|
#
|
270
|
-
#
|
268
|
+
# Expects passed in hash to only include keys which be directly set on the Honeybadger config
|
271
269
|
#
|
272
|
-
def
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
controller.session[:fault_in_session]
|
281
|
-
data[:session] = {
|
282
|
-
:key => controller.request.session_options[:id],
|
283
|
-
:data => controller.session.dup
|
284
|
-
}
|
270
|
+
def enable_honeybadger(config = {})
|
271
|
+
Bundler.require(:honeybadger)
|
272
|
+
HoneybadgerCallbacks.register_callbacks
|
273
|
+
Honeybadger.configure do |config_klass|
|
274
|
+
config.each do |k, v|
|
275
|
+
config_klass.send(:"#{k}=", v)
|
276
|
+
end
|
277
|
+
end
|
285
278
|
end
|
286
279
|
|
287
|
-
def log_warning(
|
288
|
-
|
280
|
+
def log_warning(message, **log_context)
|
281
|
+
warning = Warning.new(message)
|
282
|
+
warning.set_backtrace([])
|
283
|
+
log_error(warning, **log_context)
|
289
284
|
end
|
290
285
|
|
291
|
-
def log_info(
|
292
|
-
ExceptionHandling.logger.info(
|
286
|
+
def log_info(message, **log_context)
|
287
|
+
ExceptionHandling.logger.info(message, **log_context)
|
293
288
|
end
|
294
289
|
|
295
|
-
def log_debug(
|
296
|
-
ExceptionHandling.logger.debug(
|
290
|
+
def log_debug(message, **log_context)
|
291
|
+
ExceptionHandling.logger.debug(message, **log_context)
|
297
292
|
end
|
298
293
|
|
299
|
-
def ensure_safe(
|
294
|
+
def ensure_safe(exception_context = "", **log_context)
|
300
295
|
yield
|
301
296
|
rescue => ex
|
302
|
-
log_error ex, exception_context
|
297
|
+
log_error ex, exception_context, **log_context
|
303
298
|
return nil
|
304
299
|
end
|
305
300
|
|
306
|
-
def ensure_completely_safe(
|
301
|
+
def ensure_completely_safe(exception_context = "", **log_context)
|
307
302
|
yield
|
308
303
|
rescue SystemExit, SystemStackError, NoMemoryError, SecurityError, SignalException
|
309
304
|
raise
|
310
305
|
rescue Exception => ex
|
311
|
-
log_error ex, exception_context
|
306
|
+
log_error ex, exception_context, log_context
|
312
307
|
end
|
313
308
|
|
314
|
-
def
|
309
|
+
def escalate_to_production_support(exception_or_string, email_subject)
|
310
|
+
production_support_recipients or raise ArgumentError, "In order to escalate to production support, you must set #{self.name}.production_recipients"
|
315
311
|
ex = make_exception(exception_or_string)
|
316
|
-
|
317
|
-
escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
|
312
|
+
escalate_custom(email_subject, ex, last_exception_timestamp, production_support_recipients)
|
318
313
|
end
|
319
314
|
|
320
|
-
def
|
315
|
+
def escalate_error(exception_or_string, email_subject, **log_context)
|
316
|
+
ex = make_exception(exception_or_string)
|
317
|
+
log_error(ex, **log_context)
|
318
|
+
escalate(email_subject, ex, last_exception_timestamp)
|
319
|
+
end
|
320
|
+
|
321
|
+
def escalate_warning(message, email_subject, **log_context)
|
321
322
|
ex = Warning.new(message)
|
322
|
-
log_error(ex)
|
323
|
-
escalate(email_subject, ex,
|
323
|
+
log_error(ex, **log_context)
|
324
|
+
escalate(email_subject, ex, last_exception_timestamp)
|
324
325
|
end
|
325
326
|
|
326
|
-
def ensure_escalation(email_subject)
|
327
|
+
def ensure_escalation(email_subject, **log_context)
|
327
328
|
begin
|
328
329
|
yield
|
329
330
|
rescue => ex
|
330
|
-
escalate_error(ex, email_subject)
|
331
|
+
escalate_error(ex, email_subject, **log_context)
|
331
332
|
nil
|
332
333
|
end
|
333
334
|
end
|
334
335
|
|
335
|
-
def alert_warning(exception_or_string, alert_name, exception_context)
|
336
|
+
def alert_warning(exception_or_string, alert_name, exception_context, **log_context)
|
336
337
|
ex = make_exception(exception_or_string)
|
337
|
-
log_error(ex, exception_context)
|
338
|
+
log_error(ex, exception_context, **log_context)
|
338
339
|
begin
|
339
340
|
ExceptionHandling::Sensu.generate_event(alert_name, exception_context.to_s + "\n" + encode_utf8(ex.message.to_s))
|
340
341
|
rescue => send_ex
|
@@ -342,11 +343,11 @@ EOF
|
|
342
343
|
end
|
343
344
|
end
|
344
345
|
|
345
|
-
def ensure_alert(alert_name, exception_context)
|
346
|
+
def ensure_alert(alert_name, exception_context, **log_context)
|
346
347
|
begin
|
347
348
|
yield
|
348
349
|
rescue => ex
|
349
|
-
alert_warning(ex, alert_name, exception_context)
|
350
|
+
alert_warning(ex, alert_name, exception_context, **log_context)
|
350
351
|
nil
|
351
352
|
end
|
352
353
|
end
|
@@ -368,62 +369,82 @@ EOF
|
|
368
369
|
result
|
369
370
|
end
|
370
371
|
|
371
|
-
def log_periodically(exception_key, interval, message)
|
372
|
+
def log_periodically(exception_key, interval, message, **log_context)
|
372
373
|
self.periodic_exception_intervals ||= {}
|
373
374
|
last_logged = self.periodic_exception_intervals[exception_key]
|
374
|
-
if !last_logged || (
|
375
|
-
log_error(
|
375
|
+
if !last_logged || ((last_logged + interval) < Time.now)
|
376
|
+
log_error(message, **log_context)
|
376
377
|
self.periodic_exception_intervals[exception_key] = Time.now
|
377
378
|
end
|
378
379
|
end
|
379
380
|
|
380
|
-
def
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
# can't call log_error here or we will blow the call stack
|
386
|
-
log_info( "Unable to execute custom custom_data_hook callback. #{encode_utf8(ex.message)} #{ex.backtrace.each {|l| "#{l}\n"}}" )
|
387
|
-
end
|
381
|
+
def encode_utf8(string)
|
382
|
+
string.encode('UTF-8',
|
383
|
+
replace: '?',
|
384
|
+
undef: :replace,
|
385
|
+
invalid: :replace)
|
388
386
|
end
|
389
387
|
|
390
|
-
|
388
|
+
def clean_backtrace(exception)
|
389
|
+
backtrace = if exception.backtrace.nil?
|
390
|
+
['<no backtrace>']
|
391
|
+
elsif exception.is_a?(ClientLoggingError)
|
392
|
+
exception.backtrace
|
393
|
+
elsif defined?(Rails) && defined?(Rails.backtrace_cleaner)
|
394
|
+
Rails.backtrace_cleaner.clean(exception.backtrace)
|
395
|
+
else
|
396
|
+
exception.backtrace
|
397
|
+
end
|
391
398
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
399
|
+
# The rails backtrace cleaner returns an empty array for a backtrace if the exception was raised outside the app (inside a gem for instance)
|
400
|
+
if backtrace.is_a?(Array) && backtrace.empty?
|
401
|
+
exception.backtrace
|
402
|
+
else
|
403
|
+
backtrace
|
404
|
+
end
|
405
|
+
end
|
396
406
|
|
397
|
-
|
407
|
+
private
|
398
408
|
|
399
|
-
|
400
|
-
|
409
|
+
def log_error_email(exception_info)
|
410
|
+
data = exception_info.enhanced_data
|
411
|
+
exception_description = exception_info.exception_description
|
401
412
|
|
402
413
|
if exception_description && !exception_description.send_email
|
403
|
-
ExceptionHandling.logger.warn(
|
414
|
+
ExceptionHandling.logger.warn(
|
415
|
+
"Filtered exception using '#{exception_description.filter_name}'; not sending email to notify"
|
416
|
+
)
|
404
417
|
else
|
405
|
-
if summarize_exception(
|
406
|
-
deliver(ExceptionHandling::Mailer.exception_notification(
|
418
|
+
if summarize_exception(data) != :Summarized
|
419
|
+
deliver(ExceptionHandling::Mailer.exception_notification(data))
|
407
420
|
end
|
408
421
|
end
|
409
|
-
|
410
|
-
execute_custom_log_error_callback(merged_data, exc)
|
411
422
|
nil
|
423
|
+
rescue Exception => ex
|
424
|
+
$stderr.puts("ExceptionHandling.log_error_email rescued exception while logging #{exception_info.exception_context}: #{exception_info.exception}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
|
425
|
+
write_exception_to_log(ex, "ExceptionHandling.log_error_email rescued exception while logging #{exception_info.exception_context}: #{exception_info.exception}", exception_info.timestamp)
|
412
426
|
end
|
413
427
|
|
414
|
-
def execute_custom_log_error_callback(exception_data, exception)
|
415
|
-
|
416
|
-
|
417
|
-
ExceptionHandling.post_log_error_hook.call(exception_data, exception)
|
418
|
-
rescue Exception => ex
|
419
|
-
# can't call log_error here or we will blow the call stack
|
420
|
-
log_info( "Unable to execute custom log_error callback. #{encode_utf8(ex.message)} #{ex.backtrace.each {|l| "#{l}\n"}}" )
|
428
|
+
def execute_custom_log_error_callback(exception_data, exception, treat_like_warning, external_notification_results)
|
429
|
+
if ExceptionHandling.post_log_error_hook
|
430
|
+
honeybadger_status = external_notification_results[:honeybadger_status] || :skipped
|
431
|
+
ExceptionHandling.post_log_error_hook.call(exception_data, exception, treat_like_warning, honeybadger_status)
|
421
432
|
end
|
433
|
+
rescue Exception => ex
|
434
|
+
# can't call log_error here or we will blow the call stack
|
435
|
+
ex_message = encode_utf8(ex.message.to_s)
|
436
|
+
ex_backtrace = ex.backtrace.each { |l| "#{l}\n" }
|
437
|
+
log_info("Unable to execute custom log_error callback. #{ex_message} #{ex_backtrace}")
|
422
438
|
end
|
423
439
|
|
424
|
-
def escalate(
|
425
|
-
|
426
|
-
deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, data))
|
440
|
+
def escalate(email_subject, ex, timestamp)
|
441
|
+
exception_info = ExceptionInfo.new(ex, nil, timestamp)
|
442
|
+
deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, exception_info.data))
|
443
|
+
end
|
444
|
+
|
445
|
+
def escalate_custom(email_subject, ex, timestamp, recipients)
|
446
|
+
exception_info = ExceptionInfo.new(ex, nil, timestamp)
|
447
|
+
deliver(ExceptionHandling::Mailer.escalate_custom(email_subject, exception_info.data, recipients))
|
427
448
|
end
|
428
449
|
|
429
450
|
def deliver(mail_object)
|
@@ -431,23 +452,27 @@ EOF
|
|
431
452
|
EventMachine.schedule do # in case we're running outside the reactor
|
432
453
|
async_send_method = ExceptionHandling.eventmachine_synchrony ? :asend : :send
|
433
454
|
smtp_settings = ActionMailer::Base.smtp_settings
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
455
|
+
dns_deferrable = EventMachine::DNS::Resolver.resolve(smtp_settings[:address])
|
456
|
+
dns_deferrable.callback do |addrs|
|
457
|
+
send_deferrable = EventMachine::Protocols::SmtpClient.__send__(
|
458
|
+
async_send_method,
|
459
|
+
{
|
460
|
+
:host => addrs.first,
|
461
|
+
:port => smtp_settings[:port],
|
462
|
+
:domain => smtp_settings[:domain],
|
463
|
+
:auth => {:type=>:plain, :username=>smtp_settings[:user_name], :password=>smtp_settings[:password]},
|
464
|
+
:from => mail_object['from'].to_s,
|
465
|
+
:to => mail_object['to'].to_s,
|
466
|
+
:content => "#{mail_object}\r\n.\r\n"
|
467
|
+
}
|
468
|
+
)
|
469
|
+
send_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to email by SMTP: #{err.inspect}") }
|
470
|
+
end
|
471
|
+
dns_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to resolv DNS for #{smtp_settings[:address]}: #{err.inspect}") }
|
447
472
|
end
|
448
473
|
else
|
449
474
|
safe_email_deliver do
|
450
|
-
mail_object.
|
475
|
+
mail_object.deliver_now
|
451
476
|
end
|
452
477
|
end
|
453
478
|
end
|
@@ -458,80 +483,7 @@ EOF
|
|
458
483
|
end
|
459
484
|
rescue StandardError, MailerTimeout => ex
|
460
485
|
#$stderr.puts("ExceptionHandling::safe_email_deliver rescued: #{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
|
461
|
-
log_error(
|
462
|
-
end
|
463
|
-
|
464
|
-
def clean_exception_data( data )
|
465
|
-
if (as_array = data[:backtrace].to_a).size == 1
|
466
|
-
data[:backtrace] = as_array.first.to_s.split(/\n\s*/)
|
467
|
-
end
|
468
|
-
|
469
|
-
if data[:request].is_a?(Hash) && data[:request][:params].is_a?(Hash)
|
470
|
-
data[:request][:params] = clean_params(data[:request][:params])
|
471
|
-
end
|
472
|
-
|
473
|
-
if data[:environment].is_a?(Hash)
|
474
|
-
data[:environment] = clean_environment(data[:environment])
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
def normalize_exception_data( data )
|
479
|
-
if data[:location].nil?
|
480
|
-
data[:location] = {}
|
481
|
-
if data[:request] && data[:request].key?( :params )
|
482
|
-
data[:location][:controller] = data[:request][:params]['controller']
|
483
|
-
data[:location][:action] = data[:request][:params]['action']
|
484
|
-
end
|
485
|
-
end
|
486
|
-
if data[:backtrace] && data[:backtrace].first
|
487
|
-
first_line = data[:backtrace].first
|
488
|
-
|
489
|
-
# template exceptions have the line number and filename as the first element in backtrace
|
490
|
-
if matched = first_line.match( /on line #(\d*) of (.*)/i )
|
491
|
-
backtrace_hash = {}
|
492
|
-
backtrace_hash[:line] = matched[1]
|
493
|
-
backtrace_hash[:file] = matched[2]
|
494
|
-
else
|
495
|
-
backtrace_hash = Hash[* [:file, :line].zip( first_line.split( ':' )[0..1]).flatten ]
|
496
|
-
end
|
497
|
-
|
498
|
-
data[:location].merge!( backtrace_hash )
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
def clean_params params
|
503
|
-
params.each do |k, v|
|
504
|
-
params[k] = "[FILTERED]" if k =~ /password/
|
505
|
-
end
|
506
|
-
end
|
507
|
-
|
508
|
-
def clean_environment env
|
509
|
-
Hash[ env.map do |k, v|
|
510
|
-
[k, v] if !"#{k}: #{v}".in?(ENVIRONMENT_OMIT) && ENVIRONMENT_WHITELIST.any? { |regex| k =~ regex }
|
511
|
-
end.compact ]
|
512
|
-
end
|
513
|
-
|
514
|
-
def exception_catalog
|
515
|
-
@exception_catalog ||= ExceptionCatalog.new( ExceptionHandling.filter_list_filename )
|
516
|
-
end
|
517
|
-
|
518
|
-
def clean_backtrace(exception)
|
519
|
-
backtrace = if exception.backtrace.nil?
|
520
|
-
['<no backtrace>']
|
521
|
-
elsif exception.is_a?(ClientLoggingError)
|
522
|
-
exception.backtrace
|
523
|
-
elsif defined?(Rails)
|
524
|
-
Rails.backtrace_cleaner.clean(exception.backtrace)
|
525
|
-
else
|
526
|
-
exception.backtrace
|
527
|
-
end
|
528
|
-
|
529
|
-
# The rails backtrace cleaner returns an empty array for a backtrace if the exception was raised outside the app (inside a gem for instance)
|
530
|
-
if backtrace.is_a?(Array) && backtrace.empty?
|
531
|
-
exception.backtrace
|
532
|
-
else
|
533
|
-
backtrace
|
534
|
-
end
|
486
|
+
log_error(ex, "ExceptionHandling::safe_email_deliver", nil, treat_like_warning: true)
|
535
487
|
end
|
536
488
|
|
537
489
|
def clear_exception_summary
|
@@ -539,8 +491,8 @@ EOF
|
|
539
491
|
end
|
540
492
|
|
541
493
|
# Returns :Summarized iff exception has been added to summary and therefore should not be sent.
|
542
|
-
def summarize_exception(
|
543
|
-
if @last_exception
|
494
|
+
def summarize_exception(data)
|
495
|
+
if defined?(@last_exception) && @last_exception
|
544
496
|
same_signature = @last_exception[:backtrace] == data[:backtrace]
|
545
497
|
|
546
498
|
case @last_exception[:state]
|
@@ -582,36 +534,15 @@ EOF
|
|
582
534
|
nil
|
583
535
|
end
|
584
536
|
|
585
|
-
def send_exception_summary(
|
537
|
+
def send_exception_summary(exception_data, first_seen, occurrences)
|
586
538
|
Timeout::timeout 30, MailerTimeout do
|
587
539
|
deliver(ExceptionHandling::Mailer.exception_notification(exception_data, first_seen, occurrences))
|
588
540
|
end
|
589
541
|
rescue StandardError, MailerTimeout => ex
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
def add_to_s( data_section )
|
595
|
-
data_section[:to_s] = dump_hash( data_section )
|
596
|
-
end
|
597
|
-
|
598
|
-
def exception_to_data( exception, exception_context, timestamp )
|
599
|
-
data = ActiveSupport::HashWithIndifferentAccess.new
|
600
|
-
data[:error_class] = exception.class.name
|
601
|
-
data[:error_string]= "#{data[:error_class]}: #{encode_utf8(exception.message)}"
|
602
|
-
data[:timestamp] = timestamp
|
603
|
-
data[:backtrace] = clean_backtrace(exception)
|
604
|
-
if exception_context && exception_context.is_a?(Hash)
|
605
|
-
# if we are a hash, then we got called from the DebugExceptions rack middleware filter
|
606
|
-
# and we need to do some things different to get the info we want
|
607
|
-
data[:error] = "#{data[:error_class]}: #{encode_utf8(exception.message)}"
|
608
|
-
data[:session] = exception_context['rack.session']
|
609
|
-
data[:environment] = exception_context
|
610
|
-
else
|
611
|
-
data[:error] = "#{data[:error_string]}#{': ' + exception_context.to_s unless exception_context.blank?}"
|
612
|
-
data[:environment] = { :message => exception_context }
|
613
|
-
end
|
614
|
-
data
|
542
|
+
original_error = exception_data[:error_string]
|
543
|
+
log_prefix = "ExceptionHandling.log_error_email rescued exception while logging #{original_error}"
|
544
|
+
$stderr.puts("#{log_prefix}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
|
545
|
+
log_info(log_prefix)
|
615
546
|
end
|
616
547
|
|
617
548
|
def make_exception(exception_or_string)
|
@@ -626,27 +557,5 @@ EOF
|
|
626
557
|
end
|
627
558
|
end
|
628
559
|
end
|
629
|
-
|
630
|
-
def dump_hash( h, indent_level = 0 )
|
631
|
-
result = ""
|
632
|
-
h.sort { |a, b| a.to_s <=> b.to_s }.each do |key, value|
|
633
|
-
result << ' ' * (2 * indent_level)
|
634
|
-
result << "#{key}:"
|
635
|
-
case value
|
636
|
-
when Hash
|
637
|
-
result << "\n" << dump_hash( value, indent_level + 1 )
|
638
|
-
else
|
639
|
-
result << " #{value}\n"
|
640
|
-
end
|
641
|
-
end unless h.nil?
|
642
|
-
result
|
643
|
-
end
|
644
|
-
|
645
|
-
def encode_utf8(string)
|
646
|
-
string.encode('UTF-8',
|
647
|
-
replace: '?',
|
648
|
-
undef: :replace,
|
649
|
-
invalid: :replace)
|
650
|
-
end
|
651
560
|
end
|
652
561
|
end
|