exception_handling 1.2.1 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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