exception_handling 3.0.0 → 3.1.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: 9cd3c67b205a5e3899457a65c2a048be75ac9d4dcd4518bda6cdba4937a7d1a6
4
- data.tar.gz: dc4f282f106933bd23c38a31b461af27a0aa693a89bc63575a1efd6d0cfcb447
3
+ metadata.gz: 96028409f54a9874bebf77dc4a8def9e1bd811bc2ad2c6eab73fb54260649f66
4
+ data.tar.gz: 631fa0c4d1ca5a27eff2d075d104dfb175cf13ed32c33aec2ce7fe9cb1378209
5
5
  SHA512:
6
- metadata.gz: 249f1f5465383162973c0f2eea532e6ba1735b5be87de4abca7f9f29a2155aacb887c9d83f9aa340ad9b4b99fb8f1ab83888efb20906caeaba1ff30ed9559998
7
- data.tar.gz: ee2b289edc88dad251fd9cfb4c9ec8f513818011089704aa4bb4dafc475930b0a56b7b56675b7df7d0a81ff7803f53e155ffc17db9ec732ad6e8c82feeb19b60
6
+ metadata.gz: 8358fec9f37c24c604586c8f4bb59444f9bb621d07e7aaf3e189500b488c0a19e5631716a431721ea01f7533e30fa093bd04b3975755e210d62e19bc526da0d2
7
+ data.tar.gz: b4ec51fb3db3d65e7698950eeab8c88667ed5d0cb3ea1a53973e12e3b02a2aa58c7d650e416f43fb250ba7b36362c027ff045b171a598928959cee13a476ad8a
data/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ 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.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
+
7
15
  ## [3.0.0] - 2024-03-01
8
16
  ### Added
9
17
  - Added explicit testing and support for Ruby 3.2 and 3.3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- exception_handling (3.0.0)
4
+ exception_handling (3.1.0.pre.tstarck.1)
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)
@@ -35,12 +35,11 @@ GEM
35
35
  concurrent-ruby (~> 1.0)
36
36
  invoca-utils (0.5.1)
37
37
  activesupport (>= 5.0)
38
- jaro_winkler (1.5.3)
39
38
  json (2.7.1)
40
39
  method_source (1.0.0)
41
40
  minitest (5.15.0)
42
41
  parallel (1.17.0)
43
- parser (2.6.3.0)
42
+ parser (2.7.1.3)
44
43
  ast (~> 2.4.0)
45
44
  power_assert (1.2.0)
46
45
  pry (0.14.2)
@@ -52,11 +51,13 @@ GEM
52
51
  psych (3.3.4)
53
52
  rainbow (3.0.0)
54
53
  rake (13.0.1)
54
+ regexp_parser (2.9.0)
55
+ rexml (3.2.6)
55
56
  rspec (3.9.0)
56
57
  rspec-core (~> 3.9.0)
57
58
  rspec-expectations (~> 3.9.0)
58
59
  rspec-mocks (~> 3.9.0)
59
- rspec-core (3.9.2)
60
+ rspec-core (3.9.3)
60
61
  rspec-support (~> 3.9.3)
61
62
  rspec-expectations (3.9.2)
62
63
  diff-lcs (>= 1.2.0, < 2.0)
@@ -65,17 +66,21 @@ GEM
65
66
  diff-lcs (>= 1.2.0, < 2.0)
66
67
  rspec-support (~> 3.9.0)
67
68
  rspec-support (3.9.4)
68
- rspec_junit_formatter (0.4.1)
69
+ rspec_junit_formatter (0.6.0)
69
70
  rspec-core (>= 2, < 4, != 2.12.0)
70
- rubocop (0.74.0)
71
- jaro_winkler (~> 1.5.1)
71
+ rubocop (0.89.0)
72
72
  parallel (~> 1.10)
73
- parser (>= 2.6)
73
+ parser (>= 2.7.1.1)
74
74
  rainbow (>= 2.2.2, < 4.0)
75
+ regexp_parser (>= 1.7)
76
+ rexml
77
+ rubocop-ast (>= 0.1.0, < 1.0)
75
78
  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)
79
+ unicode-display_width (>= 1.4.0, < 2.0)
80
+ rubocop-ast (0.2.0)
81
+ parser (>= 2.7.0.1)
82
+ ruby-progressbar (1.13.0)
83
+ test-unit (3.6.1)
79
84
  power_assert
80
85
  thor (1.0.1)
81
86
  thread_safe (0.3.6)
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'
4
+ VERSION = '3.1.0.pre.tstarck.1'
5
5
  end
@@ -78,8 +78,10 @@ module ExceptionHandling # never included
78
78
 
79
79
  attr_reader :filter_list_filename
80
80
  attr_reader :honeybadger_auto_tagger
81
+ attr_reader :honeybadger_log_context_tags
81
82
 
82
83
  @filter_list_filename = "./config/exception_filters.yml"
84
+ @honeybadger_log_context_tags = {}
83
85
 
84
86
  def filter_list_filename=(filename)
85
87
  @filter_list_filename = filename
@@ -98,6 +100,21 @@ module ExceptionHandling # never included
98
100
  end
99
101
  # rubocop:enable Style/TrivialAccessors
100
102
 
103
+ # @param tag_name [String]
104
+ # @param path [Array<String>]
105
+ def add_honeybadger_tag_from_log_context(tag_name, path:)
106
+ tag_name.is_a?(String) or raise ArgumentError, "tag_name must be a String, #{tag_name.inspect}"
107
+ (path.is_a?(Array) && path.all? { _1.is_a?(String) }) or raise ArgumentError, "path must be an Array<String>, #{path.inspect}"
108
+ if @honeybadger_log_context_tags.key?(tag_name)
109
+ log_warning("Overwriting existing tag path for #{tag_name.inspect} from #{@honeybadger_log_context_tags[tag_name]} to #{path}")
110
+ end
111
+ @honeybadger_log_context_tags[tag_name] = path
112
+ end
113
+
114
+ def clear_honeybadger_tags_from_log_context
115
+ @honeybadger_log_context_tags = {}
116
+ end
117
+
101
118
  #
102
119
  # internal settings (don't set directly)
103
120
  #
@@ -217,7 +234,7 @@ module ExceptionHandling # never included
217
234
 
218
235
  # Note: Both commas and spaces are treated as delimiters for the :tags string. Space-delimiters are not officially documented.
219
236
  # https://github.com/honeybadger-io/honeybadger-ruby/pull/422
220
- tags = (honeybadger_auto_tags(exception) + exception_info.honeybadger_tags).join(' ')
237
+ tags = tags_for_honeybadger(exception_info).join(' ')
221
238
  response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
222
239
  error_message: exception.message.to_s,
223
240
  exception: exception,
@@ -231,18 +248,6 @@ module ExceptionHandling # never included
231
248
  :failure
232
249
  end
233
250
 
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
251
  #
247
252
  # Check if Honeybadger defined.
248
253
  #
@@ -346,6 +351,41 @@ module ExceptionHandling # never included
346
351
 
347
352
  private
348
353
 
354
+ # @param exception_info [ExceptionInfo]
355
+ #
356
+ # @return [Array<String>]
357
+ def tags_for_honeybadger(exception_info)
358
+ (
359
+ honeybadger_auto_tags(exception_info.exception) +
360
+ exception_info.honeybadger_tags +
361
+ honeybadger_tags_from_log_context(exception_info.honeybadger_context_data)
362
+ ).uniq
363
+ end
364
+
365
+ # @param exception [Exception]
366
+ #
367
+ # @return [Array<String>]
368
+ def honeybadger_auto_tags(exception)
369
+ @honeybadger_auto_tagger&.call(exception) || []
370
+ rescue => ex
371
+ traces = ex.backtrace.join("\n")
372
+ message = "Unable to execute honeybadger_auto_tags callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n"
373
+ ExceptionHandling.log_info(message)
374
+ []
375
+ end
376
+
377
+ def honeybadger_tags_from_log_context(honeybadger_context_data)
378
+ if @honeybadger_log_context_tags
379
+ @honeybadger_log_context_tags.map do |tag_name, tag_path|
380
+ if (value_from_log_context = honeybadger_context_data.dig(:log_context, *tag_path))
381
+ "#{tag_name}:#{value_from_log_context}"
382
+ end
383
+ end.compact
384
+ else
385
+ []
386
+ end
387
+ end
388
+
349
389
  def execute_custom_log_error_callback(exception_data, exception, treat_like_warning, external_notification_results)
350
390
  if ExceptionHandling.post_log_error_hook
351
391
  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
4
+ version: 3.1.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: 2024-03-01 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,12 +139,12 @@ 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
+ - - ">"
146
146
  - !ruby/object:Gem::Version
147
- version: '0'
147
+ version: 1.3.1
148
148
  requirements: []
149
149
  rubygems_version: 3.3.7
150
150
  signing_key: