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.
Files changed (32) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +142 -102
  5. data/README.md +11 -2
  6. data/config/exception_filters.yml +2 -0
  7. data/exception_handling.gemspec +6 -10
  8. data/lib/exception_handling.rb +222 -313
  9. data/lib/exception_handling/exception_catalog.rb +8 -6
  10. data/lib/exception_handling/exception_description.rb +8 -6
  11. data/lib/exception_handling/exception_info.rb +272 -0
  12. data/lib/exception_handling/honeybadger_callbacks.rb +42 -0
  13. data/lib/exception_handling/log_stub_error.rb +18 -3
  14. data/lib/exception_handling/mailer.rb +14 -0
  15. data/lib/exception_handling/methods.rb +26 -8
  16. data/lib/exception_handling/testing.rb +2 -2
  17. data/lib/exception_handling/version.rb +3 -1
  18. data/test/helpers/controller_helpers.rb +27 -0
  19. data/test/helpers/exception_helpers.rb +11 -0
  20. data/test/test_helper.rb +42 -19
  21. data/test/unit/exception_handling/exception_catalog_test.rb +19 -0
  22. data/test/unit/exception_handling/exception_description_test.rb +12 -1
  23. data/test/unit/exception_handling/exception_info_test.rb +501 -0
  24. data/test/unit/exception_handling/honeybadger_callbacks_test.rb +85 -0
  25. data/test/unit/exception_handling/log_error_stub_test.rb +26 -3
  26. data/test/unit/exception_handling/mailer_test.rb +39 -14
  27. data/test/unit/exception_handling/methods_test.rb +40 -18
  28. data/test/unit/exception_handling_test.rb +947 -539
  29. data/views/exception_handling/mailer/escalate_custom.html.erb +17 -0
  30. data/views/exception_handling/mailer/exception_notification.html.erb +1 -1
  31. data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +1 -1
  32. metadata +28 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b1a9a9df6c3ca8dd4f61b7e35f62271deec138d9
4
- data.tar.gz: 8fb61925651d34146c4d86394f4ebb638d45032c
2
+ SHA256:
3
+ metadata.gz: 925c7332d4385df7c8ccd52f2df818d887e31d64a643c99cc5e682d89ddba535
4
+ data.tar.gz: 54df02d0d0cedea56c2495de72b2bcf706d2f703111bb46cb80deb34a026d8df
5
5
  SHA512:
6
- metadata.gz: d43b9121fbffa0b17b2b9ac117d719a8409300c956eb961624e5550dc908b7f6aae0697a7e196ad95d4f9308bbfd137f8dbd0fa937e2f4d316809c9dbf452be3
7
- data.tar.gz: 6be898b1d470b87e43e5164954ad2e51e68b6bf01477677da1df75bd50781303e56b14587734162844ebdc4320b70a6732a5e0f475ca0add816911d9b46f19a3
6
+ metadata.gz: bd8d879e6ea219dbaa979d7436ab30f409f1942277b275fcc6d957d84095da84e14bf6620c4b834d5562012b5abcf6767da3d3a872142d5251f927ffe129191a
7
+ data.tar.gz: 1a2f9e4c18b47d01919e50ab6c69ef182068b38ff5f9e4656fd6123fcac53d404ce0042c6b23a2aef19791f02dffc248c03a08067a386050ccdd02fb1055657b
@@ -1 +1 @@
1
- 2.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
@@ -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 (1.2.0)
5
- actionmailer (~> 3.2)
6
- actionpack (~> 3.2)
7
- activesupport (~> 3.2)
8
- eventmachine (>= 0.12.10)
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.2)
33
+ invoca-utils (~> 0.0)
11
34
 
12
35
  GEM
13
36
  remote: https://rubygems.org/
14
37
  specs:
15
- actionmailer (3.2.19)
16
- actionpack (= 3.2.19)
17
- mail (~> 2.5.4)
18
- actionpack (3.2.19)
19
- activemodel (= 3.2.19)
20
- activesupport (= 3.2.19)
21
- builder (~> 3.0.0)
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
- journey (~> 1.0.4)
24
- rack (~> 1.4.5)
25
- rack-cache (~> 1.2)
26
- rack-test (~> 0.6.1)
27
- sprockets (~> 2.2.1)
28
- activemodel (3.2.19)
29
- activesupport (= 3.2.19)
30
- builder (~> 3.0.0)
31
- activerecord (3.2.19)
32
- activemodel (= 3.2.19)
33
- activesupport (= 3.2.19)
34
- arel (~> 3.0.2)
35
- tzinfo (~> 0.3.29)
36
- activeresource (3.2.19)
37
- activemodel (= 3.2.19)
38
- activesupport (= 3.2.19)
39
- activesupport (3.2.19)
40
- i18n (~> 0.6, >= 0.6.4)
41
- multi_json (~> 1.0)
42
- arel (3.0.3)
43
- bourne (1.3.0)
44
- mocha (= 0.13.0)
45
- builder (3.0.4)
46
- coderay (1.1.0)
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.0.3)
49
- hike (1.2.3)
50
- hobo_support (2.0.1)
51
- rails (~> 3.2.0)
52
- i18n (0.6.11)
53
- invoca-utils (0.0.2)
54
- journey (1.0.4)
55
- json (1.8.1)
56
- mail (2.5.4)
57
- mime-types (~> 1.16)
58
- treetop (~> 1.4.8)
59
- metaclass (0.0.1)
60
- method_source (0.8.2)
61
- mime-types (1.25.1)
62
- mocha (0.13.0)
63
- metaclass (~> 0.0.1)
64
- multi_json (1.10.1)
65
- polyglot (0.3.5)
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.8.1)
69
- slop (~> 3.4)
70
- rack (1.4.5)
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 (3.2.19)
78
- actionmailer (= 3.2.19)
79
- actionpack (= 3.2.19)
80
- activerecord (= 3.2.19)
81
- activeresource (= 3.2.19)
82
- activesupport (= 3.2.19)
83
- bundler (~> 1.0)
84
- railties (= 3.2.19)
85
- railties (3.2.19)
86
- actionpack (= 3.2.19)
87
- activesupport (= 3.2.19)
88
- rack-ssl (~> 1.3.2)
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
- rdoc (~> 3.4)
91
- thor (>= 0.14.6, < 2.0)
92
- rake (10.1.0)
93
- rdoc (3.12.2)
94
- json (~> 1.4)
95
- rr (1.1.2)
96
- shoulda (3.1.1)
97
- shoulda-context (~> 1.0)
98
- shoulda-matchers (~> 1.2)
99
- shoulda-context (1.1.4)
100
- shoulda-matchers (1.5.6)
101
- activesupport (>= 3.0.0)
102
- bourne (~> 1.3)
103
- slop (3.6.0)
104
- sprockets (2.2.2)
105
- hike (~> 1.2)
106
- multi_json (~> 1.0)
107
- rack (~> 1.0)
108
- tilt (~> 1.1, != 1.3.0)
109
- thor (0.19.1)
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 (= 3.1.1)
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 exception.is_a?(ExceptionHandling::Warning)
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"
@@ -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', '>=0.12.10'
18
- spec.add_dependency 'activesupport', '~> 3.2'
19
- spec.add_dependency 'actionpack', '~> 3.2'
20
- spec.add_dependency 'actionmailer', '~> 3.2'
21
- spec.add_dependency 'invoca-utils', '~> 0.0.2'
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
@@ -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
- SECTIONS = [:request, :session, :environment, :backtrace, :event_response]
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
- attr_accessor :server_name
80
- attr_accessor :sender_address
81
- attr_accessor :exception_recipients
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
- exception_data = exception_to_data(exception, env, timestamp)
157
+ exception_info = ExceptionInfo.new(exception, env, timestamp)
163
158
 
164
159
  if stub_handler
165
- return stub_handler.handle_stub_log_error(exception_data)
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(exception, nil, timestamp)
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
- extract_and_merge_controller_data(controller, exception_data)
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(exception_data, exception)
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 two things: write to log file and send an email
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, treat_as_local = false)
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
- data = exception_to_data(ex, exception_context, timestamp)
194
+ exception_info = ExceptionInfo.new(ex, exception_context, timestamp, controller || current_controller, data_callback)
202
195
 
203
196
  if stub_handler
204
- return stub_handler.handle_stub_log_error(data)
205
- end
206
-
207
- write_exception_to_log(ex, exception_context, timestamp)
208
-
209
- if honeybadger?
210
- send_exception_to_honeybadger(ex, exception_context, timestamp)
211
- end
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("ExceptionHandling.log_error rescued exception while logging #{exception_context}: #{exception_or_string}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
241
- write_exception_to_log(ex, "ExceptionHandling.log_error rescued exception while logging #{exception_context}: #{exception_or_string}", timestamp)
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("\n(Error:#{timestamp}) #{ex.class} #{exception_context} (#{encode_utf8(ex.message)}):\n " + clean_backtrace(ex).join("\n ") + "\n\n")
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(ex, exception_context, timestamp)
258
- custom_message = "(Error:#{timestamp}) #{ex.class} #{exception_context} (#{encode_utf8(ex.message)}): " + clean_backtrace(ex).join(" ")
259
- Honeybadger.notify(ex, context: { custom_message: custom_message })
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
- # Pull certain fields out of the controller and add to the data hash.
268
+ # Expects passed in hash to only include keys which be directly set on the Honeybadger config
271
269
  #
272
- def extract_and_merge_controller_data(controller, data)
273
- data[:request] = {
274
- :params => controller.request.parameters.to_hash,
275
- :rails_root => defined?(Rails) ? Rails.root : "Rails.root not defined. Is this a test environment?",
276
- :url => controller.complete_request_uri
277
- }
278
- data[:environment].merge!(controller.request.env.to_hash)
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( message )
288
- log_error( Warning.new(message) )
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( message )
292
- ExceptionHandling.logger.info( message )
286
+ def log_info(message, **log_context)
287
+ ExceptionHandling.logger.info(message, **log_context)
293
288
  end
294
289
 
295
- def log_debug( message )
296
- ExceptionHandling.logger.debug( message )
290
+ def log_debug(message, **log_context)
291
+ ExceptionHandling.logger.debug(message, **log_context)
297
292
  end
298
293
 
299
- def ensure_safe( exception_context = "" )
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( exception_context = "" )
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 escalate_error(exception_or_string, email_subject)
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
- log_error(ex)
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 escalate_warning(message, email_subject)
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, ExceptionHandling.last_exception_timestamp)
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 || ( (last_logged + interval) < Time.now )
375
- log_error( message )
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 enhance_exception_data(data)
381
- return if ! ExceptionHandling.custom_data_hook
382
- begin
383
- ExceptionHandling.custom_data_hook.call(data)
384
- rescue Exception => ex
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
- private
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
- def log_error_email( data, exc )
393
- enhance_exception_data( data )
394
- normalize_exception_data( data )
395
- clean_exception_data( data )
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
- SECTIONS.each { |section| add_to_s( data[section] ) if data[section].is_a? Hash }
407
+ private
398
408
 
399
- exception_description = exception_catalog.find( data )
400
- merged_data = exception_description ? ActiveSupport::HashWithIndifferentAccess.new(exception_description.exception_data.merge(data)) : data
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( "Filtered exception using '#{exception_description.filter_name}'; not sending email to notify" )
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( merged_data ) != :Summarized
406
- deliver(ExceptionHandling::Mailer.exception_notification(merged_data))
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
- return if ! ExceptionHandling.post_log_error_hook
416
- begin
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( email_subject, ex, timestamp )
425
- data = exception_to_data( ex, nil, timestamp )
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
- send_deferrable = EventMachine::Protocols::SmtpClient.__send__(
435
- async_send_method,
436
- {
437
- :host => smtp_settings[:address],
438
- :port => smtp_settings[:port],
439
- :domain => smtp_settings[:domain],
440
- :auth => {:type=>:plain, :username=>smtp_settings[:user_name], :password=>smtp_settings[:password]},
441
- :from => mail_object['from'].to_s,
442
- :to => mail_object['to'].to_s,
443
- :content => "#{mail_object}\r\n.\r\n"
444
- }
445
- )
446
- send_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to email by SMTP: #{err.inspect}") }
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.deliver
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( ex, "ExceptionHandling::safe_email_deliver", nil, true )
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( data )
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( exception_data, first_seen, occurrences )
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
- $stderr.puts("ExceptionHandling.log_error_email rescued exception while logging #{exception_context}: #{exception_or_string}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
591
- log_error(ex, "ExceptionHandling::log_error_email rescued exception while logging #{exception_context}: #{exception_or_string}", nil, true)
592
- end
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