exception_handling 2.15.0 → 2.17.0.pre.tstarck.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14ca7f66e702ec2ae46aa9f7b7669cd8f8136b9305c0683b76339faafc87558a
4
- data.tar.gz: db7863d32347f5f859971222f2b215b0f84a8c7604eb0326cfd9b656910cd9ff
3
+ metadata.gz: 4074e67f0ede6cb73665a058c3cd09b02e7518e0fb4d0860d0ce4378aa4023b6
4
+ data.tar.gz: e2d3726622a44753b679de1f645d3bbf40780c8d8829f46e7803661a7f583100
5
5
  SHA512:
6
- metadata.gz: 3274bf4d7f67fb83aa2d71633572d19c14c99e3c40746de20e20efea1ff570a0d49f5163efd74c60ce0167af3495ad259d2950bc198c28a219ff6b30dadf551b
7
- data.tar.gz: 858ce36039ecfbd5a79a22c0a56f9accff534e7b7e27e288509ffd9d34feca3ccd0983a092f6d536e554dce8d4312cb877df5f3a6729305aed8828ed0fe36d8a
6
+ metadata.gz: aa6b1574ba37ac1aee58bfb3bbfc9c15a083d255fa031c1b61dc205dad06c7a1bbee62c6fc6a1e5d24d341f88fa01806cef133cba8beb9f1ad34f0278dbe2848
7
+ data.tar.gz: e1f4e2e4dc2ff78d948a31eabc2091d7c853f117f2f03c300ee90b4d2b362d59dfe89590d5ae3f8b2949d40e40a09d60ed418786c4dbee9180fb188b590296b9
@@ -8,25 +8,21 @@ jobs:
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
11
- ruby: [2.5, 2.6, 2.7, '3.0', 3.1]
11
+ ruby: [2.7, '3.0', 3.1, 3.2, 3.3]
12
12
  gemfile:
13
13
  - Gemfile
14
14
  - gemfiles/rails_5.gemfile
15
15
  - gemfiles/rails_6.gemfile
16
16
  - gemfiles/rails_7.gemfile
17
17
  exclude:
18
- - gemfile: Gemfile
19
- ruby: 2.5
20
- - gemfile: gemfiles/rails_7.gemfile
21
- ruby: 2.5
22
- - gemfile: Gemfile
23
- ruby: 2.6
24
- - gemfile: gemfiles/rails_7.gemfile
25
- ruby: 2.6
26
18
  - gemfile: gemfiles/rails_5.gemfile
27
19
  ruby: '3.0'
28
20
  - gemfile: gemfiles/rails_5.gemfile
29
21
  ruby: 3.1
22
+ - gemfile: gemfiles/rails_5.gemfile
23
+ ruby: 3.2
24
+ - gemfile: gemfiles/rails_5.gemfile
25
+ ruby: 3.3
30
26
  env:
31
27
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
32
28
  steps:
@@ -34,7 +30,7 @@ jobs:
34
30
  - uses: ruby/setup-ruby@v1
35
31
  with:
36
32
  ruby-version: ${{ matrix.ruby }}
37
- bundler: 2.2.29
33
+ bundler: 2.3.26
38
34
  bundler-cache: true
39
35
  - name: Unit tests
40
36
  run: bundle exec rspec
data/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
4
 
5
5
  **Note:** this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.17.0] - Unreleased
8
+ ### Added
9
+ - Add interface for tagging exceptions sent to honeybadger with values from the log context.
10
+ - `ExceptionHandling.add_honeybadger_tag_from_log_context`
11
+ - Added Ruby 3.2 and 3.3 to the CI test matrix
12
+
13
+ ### Changed
14
+ - Require ruby version 2.7 or greater
15
+
16
+ ### Removed
17
+ - Removed Ruby versions 2.5 and 2.6 from the CI test matrix.
18
+
19
+
20
+ ## [2.16.0] - 2023-05-01
21
+ ### Added
22
+ - Add interface for automatically adding honeybadger tags `ExceptionHandling.honeybadger_auto_tagger=`
23
+
7
24
  ## [2.15.0] - 2023-03-07
8
25
  ### Added
9
26
  - Added support for ActionMailer 7.x
@@ -109,6 +126,7 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
109
126
  ### Changed
110
127
  - No longer depends on hobo_support. Uses invoca-utils 0.3 instead.
111
128
 
129
+ [2.16.0]: https://github.com/Invoca/exception_handling/compare/v2.15.0...v2.16.0
112
130
  [2.15.0]: https://github.com/Invoca/exception_handling/compare/v2.14.0...v2.15.0
113
131
  [2.14.0]: https://github.com/Invoca/exception_handling/compare/v2.13.0...v2.14.0
114
132
  [2.13.0]: https://github.com/Invoca/exception_handling/compare/v2.12.0...v2.13.0
data/Gemfile CHANGED
@@ -4,6 +4,9 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
+ gem 'actionmailer', '~> 6.0'
8
+ gem 'actionpack', '~> 6.0'
9
+ gem 'activesupport', '~> 6.0'
7
10
  gem 'appraisal', '~> 2.2'
8
11
  gem 'honeybadger', '~> 4.11'
9
12
  gem 'pry'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- exception_handling (2.15.0)
4
+ exception_handling (2.17.0.pre.tstarck.1)
5
5
  actionmailer (>= 5.2)
6
6
  actionpack (>= 5.2)
7
7
  activesupport (>= 5.2)
@@ -15,135 +15,158 @@ PATH
15
15
  GEM
16
16
  remote: https://rubygems.org/
17
17
  specs:
18
- actionmailer (6.0.4.7)
19
- actionpack (= 6.0.4.7)
20
- actionview (= 6.0.4.7)
21
- activejob (= 6.0.4.7)
18
+ actionmailer (6.1.7.6)
19
+ actionpack (= 6.1.7.6)
20
+ actionview (= 6.1.7.6)
21
+ activejob (= 6.1.7.6)
22
+ activesupport (= 6.1.7.6)
22
23
  mail (~> 2.5, >= 2.5.4)
23
24
  rails-dom-testing (~> 2.0)
24
- actionpack (6.0.4.7)
25
- actionview (= 6.0.4.7)
26
- activesupport (= 6.0.4.7)
27
- rack (~> 2.0, >= 2.0.8)
25
+ actionpack (6.1.7.6)
26
+ actionview (= 6.1.7.6)
27
+ activesupport (= 6.1.7.6)
28
+ rack (~> 2.0, >= 2.0.9)
28
29
  rack-test (>= 0.6.3)
29
30
  rails-dom-testing (~> 2.0)
30
31
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
31
- actionview (6.0.4.7)
32
- activesupport (= 6.0.4.7)
32
+ actionview (6.1.7.6)
33
+ activesupport (= 6.1.7.6)
33
34
  builder (~> 3.1)
34
35
  erubi (~> 1.4)
35
36
  rails-dom-testing (~> 2.0)
36
37
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
37
- activejob (6.0.4.7)
38
- activesupport (= 6.0.4.7)
38
+ activejob (6.1.7.6)
39
+ activesupport (= 6.1.7.6)
39
40
  globalid (>= 0.3.6)
40
- activesupport (6.0.4.7)
41
+ activesupport (6.1.7.6)
41
42
  concurrent-ruby (~> 1.0, >= 1.0.2)
42
- i18n (>= 0.7, < 2)
43
- minitest (~> 5.1)
44
- tzinfo (~> 1.1)
45
- zeitwerk (~> 2.2, >= 2.2.2)
46
- appraisal (2.2.0)
43
+ i18n (>= 1.6, < 2)
44
+ minitest (>= 5.1)
45
+ tzinfo (~> 2.0)
46
+ zeitwerk (~> 2.3)
47
+ appraisal (2.5.0)
47
48
  bundler
48
49
  rake
49
50
  thor (>= 0.14.0)
50
- ast (2.4.0)
51
+ ast (2.4.2)
51
52
  builder (3.2.4)
52
53
  byebug (11.1.3)
53
- coderay (1.1.2)
54
- concurrent-ruby (1.1.10)
55
- contextual_logger (1.1.1)
56
- activesupport
54
+ coderay (1.1.3)
55
+ concurrent-ruby (1.2.3)
56
+ contextual_logger (1.3.0)
57
+ activesupport (< 7.1)
57
58
  json
58
59
  crass (1.0.6)
59
- diff-lcs (1.5.0)
60
- erubi (1.10.0)
60
+ date (3.3.4)
61
+ diff-lcs (1.5.1)
62
+ erubi (1.12.0)
61
63
  escalate (0.3.0)
62
64
  eventmachine (1.2.7)
63
- globalid (1.0.0)
64
- activesupport (>= 5.0)
65
- honeybadger (4.11.0)
66
- i18n (1.10.0)
65
+ globalid (1.2.1)
66
+ activesupport (>= 6.1)
67
+ honeybadger (4.12.2)
68
+ i18n (1.14.1)
67
69
  concurrent-ruby (~> 1.0)
68
70
  invoca-utils (0.5.1)
69
71
  activesupport (>= 5.0)
70
- jaro_winkler (1.5.3)
71
- json (2.6.3)
72
- loofah (2.15.0)
72
+ json (2.7.1)
73
+ language_server-protocol (3.17.0.3)
74
+ loofah (2.22.0)
73
75
  crass (~> 1.0.2)
74
- nokogiri (>= 1.5.9)
75
- mail (2.7.1)
76
+ nokogiri (>= 1.12.0)
77
+ mail (2.8.1)
76
78
  mini_mime (>= 0.1.1)
77
- method_source (0.9.2)
78
- mini_mime (1.1.2)
79
- mini_portile2 (2.8.0)
80
- minitest (5.15.0)
81
- net-protocol (0.2.1)
79
+ net-imap
80
+ net-pop
81
+ net-smtp
82
+ method_source (1.0.0)
83
+ mini_mime (1.1.5)
84
+ mini_portile2 (2.8.5)
85
+ minitest (5.22.2)
86
+ net-imap (0.4.10)
87
+ date
88
+ net-protocol
89
+ net-pop (0.1.2)
90
+ net-protocol
91
+ net-protocol (0.2.2)
82
92
  timeout
83
- net-smtp (0.3.3)
93
+ net-smtp (0.4.0.1)
84
94
  net-protocol
85
- nokogiri (1.13.3)
86
- mini_portile2 (~> 2.8.0)
95
+ nokogiri (1.15.5)
96
+ mini_portile2 (~> 2.8.2)
87
97
  racc (~> 1.4)
88
- parallel (1.17.0)
89
- parser (2.6.3.0)
90
- ast (~> 2.4.0)
91
- power_assert (1.2.0)
92
- pry (0.12.2)
93
- coderay (~> 1.1.0)
94
- method_source (~> 0.9.0)
95
- pry-byebug (3.8.0)
98
+ parallel (1.24.0)
99
+ parser (3.3.0.5)
100
+ ast (~> 2.4.1)
101
+ racc
102
+ power_assert (2.0.3)
103
+ pry (0.14.2)
104
+ coderay (~> 1.1)
105
+ method_source (~> 1.0)
106
+ pry-byebug (3.10.1)
96
107
  byebug (~> 11.0)
97
- pry (~> 0.10)
108
+ pry (>= 0.13, < 0.15)
98
109
  psych (3.3.4)
99
- racc (1.6.0)
100
- rack (2.2.3)
101
- rack-test (1.1.0)
102
- rack (>= 1.0, < 3)
103
- rails-dom-testing (2.0.3)
104
- activesupport (>= 4.2.0)
110
+ racc (1.7.3)
111
+ rack (2.2.8)
112
+ rack-test (2.1.0)
113
+ rack (>= 1.3)
114
+ rails-dom-testing (2.2.0)
115
+ activesupport (>= 5.0.0)
116
+ minitest
105
117
  nokogiri (>= 1.6)
106
- rails-html-sanitizer (1.4.2)
107
- loofah (~> 2.3)
108
- rainbow (3.0.0)
109
- rake (13.0.1)
110
- rspec (3.9.0)
111
- rspec-core (~> 3.9.0)
112
- rspec-expectations (~> 3.9.0)
113
- rspec-mocks (~> 3.9.0)
114
- rspec-core (3.9.2)
115
- rspec-support (~> 3.9.3)
116
- rspec-expectations (3.9.2)
118
+ rails-html-sanitizer (1.6.0)
119
+ loofah (~> 2.21)
120
+ nokogiri (~> 1.14)
121
+ rainbow (3.1.1)
122
+ rake (13.1.0)
123
+ regexp_parser (2.9.0)
124
+ rexml (3.2.6)
125
+ rspec (3.13.0)
126
+ rspec-core (~> 3.13.0)
127
+ rspec-expectations (~> 3.13.0)
128
+ rspec-mocks (~> 3.13.0)
129
+ rspec-core (3.13.0)
130
+ rspec-support (~> 3.13.0)
131
+ rspec-expectations (3.13.0)
117
132
  diff-lcs (>= 1.2.0, < 2.0)
118
- rspec-support (~> 3.9.0)
119
- rspec-mocks (3.9.1)
133
+ rspec-support (~> 3.13.0)
134
+ rspec-mocks (3.13.0)
120
135
  diff-lcs (>= 1.2.0, < 2.0)
121
- rspec-support (~> 3.9.0)
122
- rspec-support (3.9.4)
123
- rspec_junit_formatter (0.4.1)
136
+ rspec-support (~> 3.13.0)
137
+ rspec-support (3.13.0)
138
+ rspec_junit_formatter (0.6.0)
124
139
  rspec-core (>= 2, < 4, != 2.12.0)
125
- rubocop (0.74.0)
126
- jaro_winkler (~> 1.5.1)
140
+ rubocop (1.60.2)
141
+ json (~> 2.3)
142
+ language_server-protocol (>= 3.17.0)
127
143
  parallel (~> 1.10)
128
- parser (>= 2.6)
144
+ parser (>= 3.3.0.2)
129
145
  rainbow (>= 2.2.2, < 4.0)
146
+ regexp_parser (>= 1.8, < 3.0)
147
+ rexml (>= 3.2.5, < 4.0)
148
+ rubocop-ast (>= 1.30.0, < 2.0)
130
149
  ruby-progressbar (~> 1.7)
131
- unicode-display_width (>= 1.4.0, < 1.7)
132
- ruby-progressbar (1.10.1)
133
- test-unit (3.3.6)
150
+ unicode-display_width (>= 2.4.0, < 3.0)
151
+ rubocop-ast (1.30.0)
152
+ parser (>= 3.2.1.0)
153
+ ruby-progressbar (1.13.0)
154
+ test-unit (3.6.1)
134
155
  power_assert
135
- thor (1.0.1)
136
- thread_safe (0.3.6)
137
- timeout (0.3.2)
138
- tzinfo (1.2.9)
139
- thread_safe (~> 0.1)
140
- unicode-display_width (1.6.0)
141
- zeitwerk (2.5.4)
156
+ thor (1.3.0)
157
+ timeout (0.4.1)
158
+ tzinfo (2.0.6)
159
+ concurrent-ruby (~> 1.0)
160
+ unicode-display_width (2.5.0)
161
+ zeitwerk (2.6.13)
142
162
 
143
163
  PLATFORMS
144
164
  ruby
145
165
 
146
166
  DEPENDENCIES
167
+ actionmailer (~> 6.0)
168
+ actionpack (~> 6.0)
169
+ activesupport (~> 6.0)
147
170
  appraisal (~> 2.2)
148
171
  exception_handling!
149
172
  honeybadger (~> 4.11)
@@ -156,4 +179,4 @@ DEPENDENCIES
156
179
  test-unit
157
180
 
158
181
  BUNDLED WITH
159
- 2.2.29
182
+ 2.3.22
data/README.md CHANGED
@@ -25,24 +25,27 @@ Or install it yourself as:
25
25
  Add some code to initialize the settings in your application.
26
26
  For example:
27
27
 
28
- require "exception_handling"
29
-
30
- # required
31
- ExceptionHandling.server_name = Cluster['server_name']
32
- ExceptionHandling.sender_address = %("Exceptions" <exceptions@example.com>)
33
- ExceptionHandling.exception_recipients = ['exceptions@example.com']
34
- ExceptionHandling.logger = Rails.logger
35
-
36
- # optional
37
- ExceptionHandling.escalation_recipients = ['escalation@example.com']
38
- ExceptionHandling.filter_list_filename = "#{Rails.root}/config/exception_filters.yml"
39
- ExceptionHandling.email_environment = Rails.env
40
- ExceptionHandling.eventmachine_safe = false
41
- ExceptionHandling.eventmachine_synchrony = false
42
- ExceptionHandling.sensu_host = "127.0.0.1"
43
- ExceptionHandling.sensu_port = 3030
44
- ExceptionHandling.sensu_prefix = ""
45
-
28
+ ```ruby
29
+ require "exception_handling"
30
+
31
+ # required
32
+ ExceptionHandling.server_name = Cluster['server_name']
33
+ ExceptionHandling.sender_address = %("Exceptions" <exceptions@example.com>)
34
+ ExceptionHandling.exception_recipients = ['exceptions@example.com']
35
+ ExceptionHandling.logger = Rails.logger
36
+
37
+ # optional
38
+ ExceptionHandling.escalation_recipients = ['escalation@example.com']
39
+ ExceptionHandling.filter_list_filename = "#{Rails.root}/config/exception_filters.yml"
40
+ ExceptionHandling.email_environment = Rails.env
41
+ ExceptionHandling.eventmachine_safe = false
42
+ ExceptionHandling.eventmachine_synchrony = false
43
+ ExceptionHandling.sensu_host = "127.0.0.1"
44
+ ExceptionHandling.sensu_port = 3030
45
+ ExceptionHandling.sensu_prefix = ""
46
+ ExceptionHandling.honeybadger_auto_tagger = ->(exception) { [] } # See "Automatically Tagging Exceptions" section below for examples
47
+ ExceptionHandling.add_honeybadger_tag_from_log_context("tag-name", path: ["path", "in", "log", "context"])
48
+ ```
46
49
 
47
50
  ## Usage
48
51
 
@@ -62,6 +65,68 @@ Then call any method available in the `ExceptionHandling::Methods` mixin:
62
65
  flash.now['error'] = "A specific error occurred. Support has been notified."
63
66
  end
64
67
 
68
+ ### Tagging Exceptions in Honeybadger
69
+
70
+ ⚠️ Honeybadger differentiates tags by spaces and/or commas, so you should **not** include spaces or commas in your tags.
71
+
72
+ ⚠️ Tags are case-sensitive.
73
+
74
+ #### Manually Tagging Exceptions
75
+
76
+ Add `:honeybadger_tags` to your `log_context` usage with an array of strings.
77
+
78
+ ```ruby
79
+ log_error(ex, "A specific error occurred.", honeybadger_tags: ["critical", "sequoia"])
80
+ ```
81
+
82
+ **Note**: Manual tags will be merged with any automatic tags.
83
+
84
+ #### Automatically Tagging Exceptions via Proc (`honeybadger_auto_tagger=`)
85
+
86
+ Configure exception handling so that you can automatically apply multiple tags to exceptions sent to honeybadger.
87
+
88
+ The Proc must accept an `exception` argument that will be the exception in question and must always return an array of strings (the array can be empty).
89
+
90
+ Example to enable auto-tagging:
91
+ ```ruby
92
+ ExceptionHandling.honeybadger_auto_tagger = ->(exception) do
93
+ exception.message.match?(/fire/) ? ["high-urgency", "danger"] : ["low-urgency"]
94
+ end
95
+ ```
96
+
97
+ Example to disable auto-tagging:
98
+ ```ruby
99
+ ExceptionHandling.honeybadger_auto_tagger = nil
100
+ ```
101
+
102
+ #### Automatically Tagging Exceptions from Log Context (`add_honeybadger_tag_from_log_context`)
103
+
104
+ Add a tag to exceptions sent to honeybadger based on a value in the log context.
105
+
106
+ To configure this, use the `add_honeybadger_tag_from_log_context` method.
107
+ ```ruby
108
+ ExceptionHandling.add_honeybadger_tag_from_log_context("kubernetes_context", path: ["kubernetes", "context"])
109
+ ```
110
+
111
+ This will add a tag to the exception if the log context contains a value at the specified path: "kubernetes" => { "context" => "value" }.
112
+
113
+ For example:
114
+ ```ruby
115
+ ExceptionHandling.logger.with_context("kubernetes" => { "context" => "local" }) do
116
+ log_error(ex, "A specific error occurred.")
117
+ end
118
+ ```
119
+
120
+ This will add the following tag to the exception sent to honeybadger:
121
+ ```
122
+ kubernetes_context:local
123
+ ```
124
+
125
+ To clear all automated tagging from the log context, use the `clear_honeybadger_tags_from_log_context` method.
126
+ ```ruby
127
+ ExceptionHandling.clear_honeybadger_tags_from_log_context
128
+ ```
129
+
65
130
  ## Custom Hooks
66
131
 
67
132
  ### custom_data_hook
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
20
20
  "allowed_push_host" => "https://rubygems.org"
21
21
  }
22
22
 
23
+ spec.required_ruby_version = '>= 2.7.0'
24
+
23
25
  spec.add_dependency 'actionmailer', '>= 5.2'
24
26
  spec.add_dependency 'actionpack', '>= 5.2'
25
27
  spec.add_dependency 'activesupport', '>= 5.2'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExceptionHandling
4
- VERSION = '2.15.0'
4
+ VERSION = '2.17.0.pre.tstarck.1'
5
5
  end
@@ -110,10 +110,12 @@ module ExceptionHandling # never included
110
110
  attr_accessor :sensu_host
111
111
  attr_accessor :sensu_port
112
112
  attr_accessor :sensu_prefix
113
+ attr_reader :honeybadger_log_context_tags
113
114
 
114
115
  attr_reader :filter_list_filename
115
116
  attr_reader :eventmachine_safe
116
117
  attr_reader :eventmachine_synchrony
118
+ attr_reader :honeybadger_auto_tagger
117
119
 
118
120
  @filter_list_filename = "./config/exception_filters.yml"
119
121
  @email_environment = ""
@@ -154,6 +156,30 @@ module ExceptionHandling # never included
154
156
  @exception_catalog ||= ExceptionCatalog.new(@filter_list_filename)
155
157
  end
156
158
 
159
+ # rubocop:disable Style/TrivialAccessors
160
+ # @param value [Proc|nil] Proc that accepts 1 parameter that will be the exception object or nil to disable the auto-tagger.
161
+ # The proc is always expected to return an array of strings. The array can be empty.
162
+ def honeybadger_auto_tagger=(value)
163
+ @honeybadger_auto_tagger = value
164
+ end
165
+ # rubocop:enable Style/TrivialAccessors
166
+
167
+ # @param tag_name [String]
168
+ # @param path [Array]
169
+ def add_honeybadger_tag_from_log_context(tag_name, path:)
170
+ tag_name.is_a?(String) or raise ArgumentError, "tag_name must be a String, #{tag_name.inspect}"
171
+ path.is_a?(Array) or raise ArgumentError, "path must be an Array, #{path.inspect}"
172
+ @honeybadger_log_context_tags ||= {}
173
+ if @honeybadger_log_context_tags.key?(tag_name)
174
+ log_warning("Overwriting existing tag path for '#{tag_name}' from #{@honeybadger_log_context_tags[tag_name]} to #{path}")
175
+ end
176
+ @honeybadger_log_context_tags[tag_name] = path
177
+ end
178
+
179
+ def clear_honeybadger_tags_from_log_context
180
+ @honeybadger_log_context_tags = nil
181
+ end
182
+
157
183
  #
158
184
  # internal settings (don't set directly)
159
185
  #
@@ -270,9 +296,10 @@ module ExceptionHandling # never included
270
296
  def send_exception_to_honeybadger(exception_info)
271
297
  exception = exception_info.exception
272
298
  exception_description = exception_info.exception_description
299
+
273
300
  # Note: Both commas and spaces are treated as delimiters for the :tags string. Space-delimiters are not officially documented.
274
301
  # https://github.com/honeybadger-io/honeybadger-ruby/pull/422
275
- tags = exception_info.honeybadger_tags.join(' ')
302
+ tags = tags_for_honeybadger(exception_info).join(' ')
276
303
  response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
277
304
  error_message: exception.message.to_s,
278
305
  exception: exception,
@@ -434,6 +461,41 @@ module ExceptionHandling # never included
434
461
 
435
462
  private
436
463
 
464
+ # @param exception_info [ExceptionInfo]
465
+ #
466
+ # @return [Array<String>]
467
+ def tags_for_honeybadger(exception_info)
468
+ (
469
+ honeybadger_auto_tags(exception_info.exception) +
470
+ exception_info.honeybadger_tags +
471
+ honeybadger_tags_from_log_context(exception_info.honeybadger_context_data)
472
+ ).uniq
473
+ end
474
+
475
+ # @param exception [Exception]
476
+ #
477
+ # @return [Array<String>]
478
+ def honeybadger_auto_tags(exception)
479
+ @honeybadger_auto_tagger&.call(exception) || []
480
+ rescue => ex
481
+ traces = ex.backtrace.join("\n")
482
+ message = "Unable to execute honeybadger_auto_tags callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n"
483
+ ExceptionHandling.log_info(message)
484
+ []
485
+ end
486
+
487
+ def honeybadger_tags_from_log_context(honeybadger_context_data)
488
+ if @honeybadger_log_context_tags
489
+ @honeybadger_log_context_tags.map do |tag_name, tag_path|
490
+ if (value_from_log_context = honeybadger_context_data.dig(:log_context, *tag_path))
491
+ "#{tag_name}:#{value_from_log_context}"
492
+ end
493
+ end.compact
494
+ else
495
+ []
496
+ end
497
+ end
498
+
437
499
  def execute_custom_log_error_callback(exception_data, exception, treat_like_warning, external_notification_results)
438
500
  if ExceptionHandling.post_log_error_hook
439
501
  honeybadger_status = external_notification_results[:honeybadger_status] || :skipped
data/spec/spec_helper.rb CHANGED
@@ -5,6 +5,7 @@ require 'rspec/mocks'
5
5
  require 'rspec_junit_formatter'
6
6
 
7
7
  require 'pry'
8
+ require 'pry-byebug'
8
9
  require 'honeybadger'
9
10
  require 'contextual_logger'
10
11
 
@@ -22,20 +23,28 @@ class LoggerStub
22
23
  clear
23
24
  end
24
25
 
25
- def debug(message, log_context = {})
26
- logged << { message: message, context: log_context, severity: 'DEBUG' }
26
+ def debug(message, **log_context)
27
+ super.tap do
28
+ logged << { message: message, context: log_context, severity: 'DEBUG' }
29
+ end
27
30
  end
28
31
 
29
- def info(message, log_context = {})
30
- logged << { message: message, context: log_context, severity: 'INFO' }
32
+ def info(message, **log_context)
33
+ super.tap do
34
+ logged << { message: message, context: log_context, severity: 'INFO' }
35
+ end
31
36
  end
32
37
 
33
- def warn(message, log_context = {})
34
- logged << { message: message, context: log_context, severity: 'WARN' }
38
+ def warn(message, **log_context)
39
+ super.tap do
40
+ logged << { message: message, context: log_context, severity: 'WARN' }
41
+ end
35
42
  end
36
43
 
37
- def fatal(message, log_context = {})
38
- logged << { message: message, context: log_context, severity: 'FATAL' }
44
+ def fatal(message, **log_context)
45
+ super.tap do
46
+ logged << { message: message, context: log_context, severity: 'FATAL' }
47
+ end
39
48
  end
40
49
 
41
50
  def clear
@@ -107,6 +107,12 @@ describe ExceptionHandling do
107
107
  class SmtpClientErrbackStub < SmtpClientStub
108
108
  end
109
109
 
110
+ before(:each) do
111
+ # Reset this for every test since they are applied to the class
112
+ ExceptionHandling.honeybadger_auto_tagger = nil
113
+ ExceptionHandling.clear_honeybadger_tags_from_log_context
114
+ end
115
+
110
116
  context "with warn and honeybadger notify stubbed" do
111
117
  before do
112
118
  allow(ExceptionHandling).to receive(:warn).with(any_args)
@@ -149,6 +155,14 @@ describe ExceptionHandling do
149
155
  expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
150
156
  end
151
157
 
158
+ it "passes :honeybadger_tags in log context to honeybadger" do
159
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: " , awesome , totallytubular, " }))
160
+ ExceptionHandling.log_error('This is an Error', 'This is the prefix context', honeybadger_tags: ' , awesome , totallytubular, ')
161
+
162
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: " cool neat" }))
163
+ ExceptionHandling.log_error('This is an Error', 'This is the prefix context', honeybadger_tags: [" ", " cool ", "neat"])
164
+ end
165
+
152
166
  it "logs with Severity::FATAL" do
153
167
  ExceptionHandling.log_error('This is a Warning', service_name: 'exception_handling')
154
168
  expect('FATAL').to eq(logged_excluding_reload_filter.last[:severity])
@@ -1245,6 +1259,156 @@ describe ExceptionHandling do
1245
1259
  expect(logged_excluding_reload_filter.size).to eq(3)
1246
1260
  end
1247
1261
  end
1262
+
1263
+ context "#honeybadger_auto_tagger=" do
1264
+ context "with proc that runs successfully" do
1265
+ before do
1266
+ ExceptionHandling.honeybadger_auto_tagger =
1267
+ ->(exception) do
1268
+ if exception.message =~ /donuts/
1269
+ ['donut-error', 'high-urgency']
1270
+ else
1271
+ ['low-urgency']
1272
+ end
1273
+ end
1274
+ end
1275
+
1276
+ context "without manually passed tags" do
1277
+ let(:exception) { StandardError.new("We are out of chocolate milk") }
1278
+
1279
+ it "adds tags from autotagger" do
1280
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "low-urgency" }))
1281
+ ExceptionHandling.log_error(exception, nil)
1282
+ end
1283
+ end
1284
+
1285
+ context "with manually passed tags" do
1286
+ let(:exception) { StandardError.new("The donuts are burning") }
1287
+
1288
+ it "merges tags from autotagger with manually passed tags" do
1289
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "donut-error high-urgency upset-customers" }))
1290
+ ExceptionHandling.log_error(exception, nil, honeybadger_tags: ["upset-customers"])
1291
+ end
1292
+ end
1293
+ end
1294
+ end
1295
+
1296
+ context "with proc that raises an exception" do
1297
+ let(:exception) { StandardError.new("Something else occurred") }
1298
+
1299
+ before do
1300
+ ExceptionHandling.honeybadger_auto_tagger = ->(_exception) { raise StandardError, "boom" }
1301
+ end
1302
+
1303
+ it "logs a message and returns [] for the tags" do
1304
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "some-other-tag" }))
1305
+ expect(ExceptionHandling).to receive(:log_info).with(/Unable to execute honeybadger_auto_tags callback. boom/)
1306
+ ExceptionHandling.log_error(exception, nil, honeybadger_tags: ["some-other-tag"])
1307
+ end
1308
+ end
1309
+
1310
+ describe "#add_honeybadger_tag_from_log_context" do
1311
+ subject(:add_hb_tag) { ExceptionHandling.add_honeybadger_tag_from_log_context(tag_name, path: path) }
1312
+ let(:tag_name) { "sample-tag" }
1313
+ let(:path) { ["sample", "path"] }
1314
+
1315
+ it "sets the honeybadger log context tags to the tag name and path" do
1316
+ add_hb_tag
1317
+ expect(ExceptionHandling.honeybadger_log_context_tags).to include("sample-tag" => ["sample", "path"])
1318
+ end
1319
+
1320
+ context "when path is not an array" do
1321
+ let(:path) { "not an array" }
1322
+
1323
+ it "raises an argument error" do
1324
+ expect { add_hb_tag }.to raise_error(ArgumentError, /path must be an Array/)
1325
+ end
1326
+ end
1327
+
1328
+ context "when tag_name is not a string" do
1329
+ let(:tag_name) { 1 }
1330
+
1331
+ it "raises an argument error" do
1332
+ expect { add_hb_tag }.to raise_error(ArgumentError, /tag_name must be a String/)
1333
+ end
1334
+ end
1335
+
1336
+ context "when there already exists a tag with the same name" do
1337
+ before(:each) do
1338
+ ExceptionHandling.add_honeybadger_tag_from_log_context(tag_name, path: ["other", "path"])
1339
+ end
1340
+
1341
+ it "overwrites the existing tag to the new one" do
1342
+ add_hb_tag
1343
+ expect(ExceptionHandling.honeybadger_log_context_tags).to include("sample-tag" => ["sample", "path"])
1344
+ end
1345
+
1346
+ it "logs a warning" do
1347
+ expect(ExceptionHandling.logger).to receive(:warn).with(/Overwriting existing tag path for 'sample-tag'/, **{})
1348
+ add_hb_tag
1349
+ end
1350
+ end
1351
+ end
1352
+
1353
+ describe "honey badger tags" do
1354
+ context "with log context tags" do
1355
+ before(:each) do
1356
+ ExceptionHandling.add_honeybadger_tag_from_log_context("kubernetes_context", path: ["kubernetes", "context"])
1357
+ end
1358
+
1359
+ context "when error is logged within a log context that matches the path" do
1360
+ it "notifies honeybadger with the tags from the log context" do
1361
+ ExceptionHandling.logger.with_context("kubernetes" => { "context" => "local" }) do
1362
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "kubernetes_context:local" }))
1363
+ ExceptionHandling.log_error(StandardError.new("Error"), nil)
1364
+ end
1365
+ end
1366
+ end
1367
+
1368
+ context "when error is logged within a log context that doesn't match the path" do
1369
+ it "does not specify any tags in the honeybadger notify" do
1370
+ ExceptionHandling.logger.with_context("kubernetes" => { "pod" => "frontend-abc" }) do
1371
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "" }))
1372
+ ExceptionHandling.log_error(StandardError.new("Error"), nil)
1373
+ end
1374
+ end
1375
+ end
1376
+
1377
+ context "when error is logged outside of a log context block" do
1378
+ it "does not specify any tags in the honeybadger notify" do
1379
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "" }))
1380
+ ExceptionHandling.log_error(StandardError.new("Error"), nil)
1381
+ end
1382
+ end
1383
+ end
1384
+
1385
+ context "with all different honeybadger tagging" do
1386
+ subject(:log_error) { ExceptionHandling.log_error(StandardError.new("Error"), nil, honeybadger_tags: inline_tags) }
1387
+ let(:inline_tags) { ["inline-tag"] }
1388
+ before(:each) do
1389
+ ExceptionHandling.honeybadger_auto_tagger = ->(_exception) { ["auto-tag"] }
1390
+ ExceptionHandling.add_honeybadger_tag_from_log_context("log-context", path: ["inside", "context"])
1391
+ end
1392
+
1393
+ it "combines all the tags from different sources" do
1394
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "auto-tag inline-tag log-context:tag" }))
1395
+ ExceptionHandling.logger.with_context("inside" => { "context" => "tag" }) do
1396
+ log_error
1397
+ end
1398
+ end
1399
+
1400
+ context "when there are duplicate tags" do
1401
+ let(:inline_tags) { ["auto-tag"] }
1402
+
1403
+ it "notifies honeybadger with the set of tags" do
1404
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "auto-tag log-context:tag" }))
1405
+ ExceptionHandling.logger.with_context("inside" => { "context" => "tag" }) do
1406
+ log_error
1407
+ end
1408
+ end
1409
+ end
1410
+ end
1411
+ end
1248
1412
  end
1249
1413
 
1250
1414
  context "ExceptionHandling < 3.0 " do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exception_handling
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.0
4
+ version: 2.17.0.pre.tstarck.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-07 00:00:00.000000000 Z
11
+ date: 2024-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionmailer
@@ -204,14 +204,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
204
204
  requirements:
205
205
  - - ">="
206
206
  - !ruby/object:Gem::Version
207
- version: '0'
207
+ version: 2.7.0
208
208
  required_rubygems_version: !ruby/object:Gem::Requirement
209
209
  requirements:
210
- - - ">="
210
+ - - ">"
211
211
  - !ruby/object:Gem::Version
212
- version: '0'
212
+ version: 1.3.1
213
213
  requirements: []
214
- rubygems_version: 3.4.7
214
+ rubygems_version: 3.4.12
215
215
  signing_key:
216
216
  specification_version: 4
217
217
  summary: Invoca's exception handling logger/emailer layer, based on exception_notifier.