exception_handling 3.0.0.pre.2 → 3.1.0.pre.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac0b3024c051127bd91c798fe917137f450b3db8ebc315434ecfade3b8775279
4
- data.tar.gz: 829cd3c05ca289525a789a5b8b93e61a1b996a7a407d91a182f0a126a5a15344
3
+ metadata.gz: 31594865f3096183f28bd10c1310bbca3df0170b97b09f51dd1231f02736e50f
4
+ data.tar.gz: 0b638eff8f75896da524b9c1ced003e72e95b5576c0dcba7db0e1e2c8f80b5e8
5
5
  SHA512:
6
- metadata.gz: a6ff724591f83d687cd332e35b18a9407a97fd99182e7d255ec34e4a0eaed2dd729446df7c8639734d0338c816c12ed4803adf040a1252a0912e8d0c8035d4b2
7
- data.tar.gz: aac19d62e8bd367eed9bb5dd3e80903161af053410f0ab4a5c251f5115e520ebf3d73d3f86e3789968234f2df2ed69b86454483ff029938854805c733246017e
6
+ metadata.gz: 5300573e63bd5d9039676ab35b4299812d0a870f60eb63b6039546df8eecdc92d30931537e96a4105295968b87183746d46abeaaa67cdd47d94bd347d8b7631b
7
+ data.tar.gz: 444bde3024de65b88ea660392c22dd624298c362e96a2af8ad4a94fd3f6513d7ed6f32709cdc9244cd8532410529cd0b923f9cb4086e7faf2e2c11a571116bf0
data/CHANGELOG.md CHANGED
@@ -4,7 +4,15 @@ 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
- ## [3.0.0] - Unreleased
7
+ ## [3.1.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
+
12
+ ### Changed
13
+ - Require ruby version 2.7 or greater
14
+
15
+ ## [3.0.0] - 2024-03-01
8
16
  ### Added
9
17
  - Added explicit testing and support for Ruby 3.2 and 3.3
10
18
 
data/Gemfile CHANGED
@@ -6,6 +6,7 @@ gemspec
6
6
 
7
7
  gem 'activesupport'
8
8
  gem 'appraisal', '~> 2.2'
9
+ gem 'gem-release'
9
10
  gem 'honeybadger', '~> 4.11'
10
11
  gem 'pry'
11
12
  gem 'pry-byebug'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- exception_handling (3.0.0.pre.2)
4
+ exception_handling (3.1.0.pre.2)
5
5
  activesupport (>= 5.2)
6
6
  contextual_logger (~> 1.0)
7
7
  escalate (~> 0.3)
@@ -17,7 +17,7 @@ GEM
17
17
  minitest (~> 5.1)
18
18
  tzinfo (~> 1.1)
19
19
  zeitwerk (~> 2.2, >= 2.2.2)
20
- appraisal (2.2.0)
20
+ appraisal (2.5.0)
21
21
  bundler
22
22
  rake
23
23
  thor (>= 0.14.0)
@@ -30,17 +30,17 @@ GEM
30
30
  json
31
31
  diff-lcs (1.5.0)
32
32
  escalate (0.3.0)
33
+ gem-release (2.2.2)
33
34
  honeybadger (4.11.0)
34
35
  i18n (1.10.0)
35
36
  concurrent-ruby (~> 1.0)
36
37
  invoca-utils (0.5.1)
37
38
  activesupport (>= 5.0)
38
- jaro_winkler (1.5.3)
39
39
  json (2.7.1)
40
40
  method_source (1.0.0)
41
41
  minitest (5.15.0)
42
42
  parallel (1.17.0)
43
- parser (2.6.3.0)
43
+ parser (2.7.1.3)
44
44
  ast (~> 2.4.0)
45
45
  power_assert (1.2.0)
46
46
  pry (0.14.2)
@@ -52,11 +52,13 @@ GEM
52
52
  psych (3.3.4)
53
53
  rainbow (3.0.0)
54
54
  rake (13.0.1)
55
+ regexp_parser (2.9.0)
56
+ rexml (3.2.6)
55
57
  rspec (3.9.0)
56
58
  rspec-core (~> 3.9.0)
57
59
  rspec-expectations (~> 3.9.0)
58
60
  rspec-mocks (~> 3.9.0)
59
- rspec-core (3.9.2)
61
+ rspec-core (3.9.3)
60
62
  rspec-support (~> 3.9.3)
61
63
  rspec-expectations (3.9.2)
62
64
  diff-lcs (>= 1.2.0, < 2.0)
@@ -65,17 +67,21 @@ GEM
65
67
  diff-lcs (>= 1.2.0, < 2.0)
66
68
  rspec-support (~> 3.9.0)
67
69
  rspec-support (3.9.4)
68
- rspec_junit_formatter (0.4.1)
70
+ rspec_junit_formatter (0.6.0)
69
71
  rspec-core (>= 2, < 4, != 2.12.0)
70
- rubocop (0.74.0)
71
- jaro_winkler (~> 1.5.1)
72
+ rubocop (0.89.0)
72
73
  parallel (~> 1.10)
73
- parser (>= 2.6)
74
+ parser (>= 2.7.1.1)
74
75
  rainbow (>= 2.2.2, < 4.0)
76
+ regexp_parser (>= 1.7)
77
+ rexml
78
+ rubocop-ast (>= 0.1.0, < 1.0)
75
79
  ruby-progressbar (~> 1.7)
76
- unicode-display_width (>= 1.4.0, < 1.7)
77
- ruby-progressbar (1.10.1)
78
- test-unit (3.3.6)
80
+ unicode-display_width (>= 1.4.0, < 2.0)
81
+ rubocop-ast (0.2.0)
82
+ parser (>= 2.7.0.1)
83
+ ruby-progressbar (1.13.0)
84
+ test-unit (3.6.1)
79
85
  power_assert
80
86
  thor (1.0.1)
81
87
  thread_safe (0.3.6)
@@ -91,6 +97,7 @@ DEPENDENCIES
91
97
  activesupport
92
98
  appraisal (~> 2.2)
93
99
  exception_handling!
100
+ gem-release
94
101
  honeybadger (~> 4.11)
95
102
  pry
96
103
  pry-byebug
data/README.md CHANGED
@@ -36,6 +36,7 @@ ExceptionHandling.logger = Rails.logger
36
36
  ExceptionHandling.filter_list_filename = "#{Rails.root}/config/exception_filters.yml"
37
37
  ExceptionHandling.environment = Rails.env
38
38
  ExceptionHandling.honeybadger_auto_tagger = ->(exception) { [] } # See "Automatically Tagging Exceptions" section below for examples
39
+ ExceptionHandling.add_honeybadger_tag_from_log_context("tag-name", path: ["path", "in", "log", "context"])
39
40
  ```
40
41
 
41
42
  ## Usage
@@ -72,7 +73,7 @@ log_error(ex, "A specific error occurred.", honeybadger_tags: ["critical", "sequ
72
73
 
73
74
  **Note**: Manual tags will be merged with any automatic tags.
74
75
 
75
- #### Automatically Tagging Exceptions (`honeybadger_auto_tagger=`)
76
+ #### Automatically Tagging Exceptions via Proc (`honeybadger_auto_tagger=`)
76
77
 
77
78
  Configure exception handling so that you can automatically apply multiple tags to exceptions sent to honeybadger.
78
79
 
@@ -90,6 +91,34 @@ Example to disable auto-tagging:
90
91
  ExceptionHandling.honeybadger_auto_tagger = nil
91
92
  ```
92
93
 
94
+ #### Automatically Tagging Exceptions from Log Context (`add_honeybadger_tag_from_log_context`)
95
+
96
+ Add a tag to exceptions sent to honeybadger based on a value in the log context.
97
+
98
+ To configure this, use the `add_honeybadger_tag_from_log_context` method.
99
+ ```ruby
100
+ ExceptionHandling.add_honeybadger_tag_from_log_context("kubernetes_context", path: ["kubernetes", "context"])
101
+ ```
102
+
103
+ This will add a tag to the exception if the log context contains a value at the specified path: "kubernetes" => { "context" => "value" }.
104
+
105
+ For example:
106
+ ```ruby
107
+ ExceptionHandling.logger.with_context("kubernetes" => { "context" => "local" }) do
108
+ log_error(ex, "A specific error occurred.")
109
+ end
110
+ ```
111
+
112
+ This will add the following tag to the exception sent to honeybadger:
113
+ ```
114
+ kubernetes_context:local
115
+ ```
116
+
117
+ To clear all automated tagging from the log context, use the `clear_honeybadger_tags_from_log_context` method.
118
+ ```ruby
119
+ ExceptionHandling.clear_honeybadger_tags_from_log_context
120
+ ```
121
+
93
122
  ## Custom Hooks
94
123
 
95
124
  ### custom_data_hook
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
19
19
  "source_code_uri" => "https://github.com/Invoca/exception_handling",
20
20
  "allowed_push_host" => "https://rubygems.org"
21
21
  }
22
+ spec.required_ruby_version = '>= 2.7'
22
23
 
23
24
  spec.add_dependency 'activesupport', '>= 5.2'
24
25
  spec.add_dependency 'contextual_logger', '~> 1.0'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExceptionHandling
4
- VERSION = '3.0.0.pre.2'
4
+ VERSION = '3.1.0.pre.2'
5
5
  end
@@ -28,6 +28,8 @@ module ExceptionHandling # never included
28
28
  AUTHENTICATION_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'].freeze
29
29
  HONEYBADGER_STATUSES = [:success, :failure, :skipped].freeze
30
30
 
31
+ @honeybadger_log_context_tags = {}
32
+
31
33
  class << self
32
34
 
33
35
  #
@@ -78,6 +80,7 @@ module ExceptionHandling # never included
78
80
 
79
81
  attr_reader :filter_list_filename
80
82
  attr_reader :honeybadger_auto_tagger
83
+ attr_reader :honeybadger_log_context_tags
81
84
 
82
85
  @filter_list_filename = "./config/exception_filters.yml"
83
86
 
@@ -98,6 +101,21 @@ module ExceptionHandling # never included
98
101
  end
99
102
  # rubocop:enable Style/TrivialAccessors
100
103
 
104
+ # @param tag_name [String]
105
+ # @param path [Array<String>]
106
+ def add_honeybadger_tag_from_log_context(tag_name, path:)
107
+ tag_name.is_a?(String) or raise ArgumentError, "tag_name must be a String, #{tag_name.inspect}"
108
+ (path.is_a?(Array) && path.all? { _1.is_a?(String) }) or raise ArgumentError, "path must be an Array<String>, #{path.inspect}"
109
+ if @honeybadger_log_context_tags.key?(tag_name)
110
+ log_warning("Overwriting existing tag path for #{tag_name.inspect} from #{@honeybadger_log_context_tags[tag_name]} to #{path}")
111
+ end
112
+ @honeybadger_log_context_tags[tag_name] = path
113
+ end
114
+
115
+ def clear_honeybadger_tags_from_log_context
116
+ @honeybadger_log_context_tags = {}
117
+ end
118
+
101
119
  #
102
120
  # internal settings (don't set directly)
103
121
  #
@@ -217,7 +235,7 @@ module ExceptionHandling # never included
217
235
 
218
236
  # Note: Both commas and spaces are treated as delimiters for the :tags string. Space-delimiters are not officially documented.
219
237
  # https://github.com/honeybadger-io/honeybadger-ruby/pull/422
220
- tags = (honeybadger_auto_tags(exception) + exception_info.honeybadger_tags).join(' ')
238
+ tags = tags_for_honeybadger(exception_info).join(' ')
221
239
  response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
222
240
  error_message: exception.message.to_s,
223
241
  exception: exception,
@@ -231,18 +249,6 @@ module ExceptionHandling # never included
231
249
  :failure
232
250
  end
233
251
 
234
- # @param exception [Exception]
235
- #
236
- # @return [Array<String>]
237
- def honeybadger_auto_tags(exception)
238
- @honeybadger_auto_tagger&.call(exception) || []
239
- rescue => ex
240
- traces = ex.backtrace.join("\n")
241
- message = "Unable to execute honeybadger_auto_tags callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n"
242
- ExceptionHandling.log_info(message)
243
- []
244
- end
245
-
246
252
  #
247
253
  # Check if Honeybadger defined.
248
254
  #
@@ -346,6 +352,41 @@ module ExceptionHandling # never included
346
352
 
347
353
  private
348
354
 
355
+ # @param exception_info [ExceptionInfo]
356
+ #
357
+ # @return [Array<String>]
358
+ def tags_for_honeybadger(exception_info)
359
+ (
360
+ honeybadger_auto_tags(exception_info.exception) +
361
+ exception_info.honeybadger_tags +
362
+ honeybadger_tags_from_log_context(exception_info.honeybadger_context_data)
363
+ ).uniq
364
+ end
365
+
366
+ # @param exception [Exception]
367
+ #
368
+ # @return [Array<String>]
369
+ def honeybadger_auto_tags(exception)
370
+ @honeybadger_auto_tagger&.call(exception) || []
371
+ rescue => ex
372
+ traces = ex.backtrace.join("\n")
373
+ message = "Unable to execute honeybadger_auto_tags callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n"
374
+ ExceptionHandling.log_info(message)
375
+ []
376
+ end
377
+
378
+ def honeybadger_tags_from_log_context(honeybadger_context_data)
379
+ if @honeybadger_log_context_tags
380
+ @honeybadger_log_context_tags.map do |tag_name, tag_path|
381
+ if (value_from_log_context = honeybadger_context_data.dig(:log_context, *tag_path))
382
+ "#{tag_name}:#{value_from_log_context}"
383
+ end
384
+ end.compact
385
+ else
386
+ []
387
+ end
388
+ end
389
+
349
390
  def execute_custom_log_error_callback(exception_data, exception, treat_like_warning, external_notification_results)
350
391
  if ExceptionHandling.post_log_error_hook
351
392
  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,9 +107,10 @@ describe ExceptionHandling do
107
107
  class SmtpClientErrbackStub < SmtpClientStub
108
108
  end
109
109
 
110
- before(:each) do
110
+ before do
111
111
  # Reset this for every test since they are applied to the class
112
112
  ExceptionHandling.honeybadger_auto_tagger = nil
113
+ ExceptionHandling.clear_honeybadger_tags_from_log_context
113
114
  end
114
115
 
115
116
  context "with warn and honeybadger notify stubbed" do
@@ -1029,6 +1030,117 @@ describe ExceptionHandling do
1029
1030
  ExceptionHandling.log_error(exception, nil, honeybadger_tags: ["some-other-tag"])
1030
1031
  end
1031
1032
  end
1033
+
1034
+ describe "#add_honeybadger_tag_from_log_context" do
1035
+ subject(:add_hb_tag) { ExceptionHandling.add_honeybadger_tag_from_log_context(tag_name, path: path) }
1036
+ let(:tag_name) { "sample-tag" }
1037
+ let(:path) { ["sample", "path"] }
1038
+
1039
+ it "sets the honeybadger log context tags to the tag name and path" do
1040
+ add_hb_tag
1041
+ expect(ExceptionHandling.honeybadger_log_context_tags).to include("sample-tag" => ["sample", "path"])
1042
+ end
1043
+
1044
+ context "when path is not an array" do
1045
+ let(:path) { "not an array" }
1046
+
1047
+ it "raises an argument error" do
1048
+ expect { add_hb_tag }.to raise_error(ArgumentError, /path must be an Array/)
1049
+ end
1050
+ end
1051
+
1052
+ context "when path doesn't contain only strings" do
1053
+ let(:path) { [:sample, :path] }
1054
+
1055
+ it "raises an argument error" do
1056
+ expect { add_hb_tag }.to raise_error(ArgumentError, /path must be an Array<String>/)
1057
+ end
1058
+ end
1059
+
1060
+ context "when tag_name is not a string" do
1061
+ let(:tag_name) { 1 }
1062
+
1063
+ it "raises an argument error" do
1064
+ expect { add_hb_tag }.to raise_error(ArgumentError, /tag_name must be a String/)
1065
+ end
1066
+ end
1067
+
1068
+ context "when there already exists a tag with the same name" do
1069
+ before do
1070
+ ExceptionHandling.add_honeybadger_tag_from_log_context(tag_name, path: ["other", "path"])
1071
+ end
1072
+
1073
+ it "overwrites the existing tag to the new one" do
1074
+ add_hb_tag
1075
+ expect(ExceptionHandling.honeybadger_log_context_tags).to include("sample-tag" => ["sample", "path"])
1076
+ end
1077
+
1078
+ it "logs a warning" do
1079
+ expect(ExceptionHandling.logger).to receive(:warn).with(/Overwriting existing tag path for "sample-tag"/, **{})
1080
+ add_hb_tag
1081
+ end
1082
+ end
1083
+ end
1084
+
1085
+ describe "honey badger tags" do
1086
+ context "with log context tags" do
1087
+ before do
1088
+ ExceptionHandling.add_honeybadger_tag_from_log_context("kubernetes_context", path: ["kubernetes", "context"])
1089
+ end
1090
+
1091
+ context "when error is logged within a log context that matches the path" do
1092
+ it "notifies honeybadger with the tags from the log context" do
1093
+ ExceptionHandling.logger.with_context("kubernetes" => { "context" => "local" }) do
1094
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "kubernetes_context:local" }))
1095
+ ExceptionHandling.log_error(StandardError.new("Error"), nil)
1096
+ end
1097
+ end
1098
+ end
1099
+
1100
+ context "when error is logged within a log context that doesn't match the path" do
1101
+ it "does not specify any tags in the honeybadger notify" do
1102
+ ExceptionHandling.logger.with_context("kubernetes" => { "pod" => "frontend-abc" }) do
1103
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "" }))
1104
+ ExceptionHandling.log_error(StandardError.new("Error"), nil)
1105
+ end
1106
+ end
1107
+ end
1108
+
1109
+ context "when error is logged outside of a log context block" do
1110
+ it "does not specify any tags in the honeybadger notify" do
1111
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "" }))
1112
+ ExceptionHandling.log_error(StandardError.new("Error"), nil)
1113
+ end
1114
+ end
1115
+ end
1116
+
1117
+ context "with all different honeybadger tagging" do
1118
+ subject(:log_error) { ExceptionHandling.log_error(StandardError.new("Error"), nil, honeybadger_tags: inline_tags) }
1119
+ let(:inline_tags) { ["inline-tag"] }
1120
+ before do
1121
+ ExceptionHandling.honeybadger_auto_tagger = ->(_exception) { ["auto-tag"] }
1122
+ ExceptionHandling.add_honeybadger_tag_from_log_context("log-context", path: ["inside", "context"])
1123
+ end
1124
+
1125
+ it "combines all the tags from different sources" do
1126
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "auto-tag inline-tag log-context:tag" }))
1127
+ ExceptionHandling.logger.with_context("inside" => { "context" => "tag" }) do
1128
+ log_error
1129
+ end
1130
+ end
1131
+
1132
+ context "when there are duplicate tags" do
1133
+ let(:inline_tags) { ["auto-tag"] }
1134
+
1135
+ it "notifies honeybadger with the set of tags" do
1136
+ expect(Honeybadger).to receive(:notify).with(hash_including({ tags: "auto-tag log-context:tag" }))
1137
+ ExceptionHandling.logger.with_context("inside" => { "context" => "tag" }) do
1138
+ log_error
1139
+ end
1140
+ end
1141
+ end
1142
+ end
1143
+ end
1032
1144
  end
1033
1145
 
1034
1146
 
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: 3.0.0.pre.2
4
+ version: 3.1.0.pre.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-29 00:00:00.000000000 Z
11
+ date: 2024-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -139,7 +139,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
139
139
  requirements:
140
140
  - - ">="
141
141
  - !ruby/object:Gem::Version
142
- version: '0'
142
+ version: '2.7'
143
143
  required_rubygems_version: !ruby/object:Gem::Requirement
144
144
  requirements:
145
145
  - - ">"