exception_handling 3.0.0.pre.2 → 3.1.0.pre.tstarck.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac0b3024c051127bd91c798fe917137f450b3db8ebc315434ecfade3b8775279
4
- data.tar.gz: 829cd3c05ca289525a789a5b8b93e61a1b996a7a407d91a182f0a126a5a15344
3
+ metadata.gz: 96028409f54a9874bebf77dc4a8def9e1bd811bc2ad2c6eab73fb54260649f66
4
+ data.tar.gz: 631fa0c4d1ca5a27eff2d075d104dfb175cf13ed32c33aec2ce7fe9cb1378209
5
5
  SHA512:
6
- metadata.gz: a6ff724591f83d687cd332e35b18a9407a97fd99182e7d255ec34e4a0eaed2dd729446df7c8639734d0338c816c12ed4803adf040a1252a0912e8d0c8035d4b2
7
- data.tar.gz: aac19d62e8bd367eed9bb5dd3e80903161af053410f0ab4a5c251f5115e520ebf3d73d3f86e3789968234f2df2ed69b86454483ff029938854805c733246017e
6
+ metadata.gz: 8358fec9f37c24c604586c8f4bb59444f9bb621d07e7aaf3e189500b488c0a19e5631716a431721ea01f7533e30fa093bd04b3975755e210d62e19bc526da0d2
7
+ data.tar.gz: b4ec51fb3db3d65e7698950eeab8c88667ed5d0cb3ea1a53973e12e3b02a2aa58c7d650e416f43fb250ba7b36362c027ff045b171a598928959cee13a476ad8a
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.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.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.pre.2'
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.pre.2
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-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
  - - ">"