lumberjack_capture_device 1.2.1 → 2.0.0

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: ee398d1d45c445f0afb2469285cfbbfc6896313251078684619ca5a12bbcc0e3
4
- data.tar.gz: c81bc5da733d88a2213e7b6459d8b0be81a9501484862043dea0d3761c364b57
3
+ metadata.gz: be90765c9b26b0ca9c501504e83c2a58cf1f2ce74edffe0b5e3d8e2515194277
4
+ data.tar.gz: '0825d9fa6621df4c5cdea31de794e1c8f263249d3f42f0e17945806ccb7f5f69'
5
5
  SHA512:
6
- metadata.gz: 790bc14321e6e30426098616d96ee5425d63b58b9041e251001856dc0b1abb8549a220f892570df4c1c85ca398002a3e94de37f395f45fb84b406c5331ccf655
7
- data.tar.gz: 2512ec2c40dbefe1242abe07d3c7b0a391e9a425c18abc54247d9a76195f73a548f6a080d51d8463caa92512c34b96d9bff81e06179b60fc0569d98c4b1fc50f
6
+ metadata.gz: 5423feff017c2b44974d7e75268c39388353165128632d0b7533619b5271ff17aeb1e9250d7ceda017de56f272baf0bda9b6e2a180a98397b13520b128f5449b
7
+ data.tar.gz: 3bbdb6b6ca43683a3ec20ead94fa93e557b33194c4ee58d5f36c478e50b01e7ff5abed29a3001fce885f47f304f62d3bcbeec5cddd00708401f377db7d775bf1
data/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 2.0.0
8
+
9
+ ### Added
10
+
11
+ - Captured log entries are now passed through to the underlying logging device when the capture block is finished. This can be disabled by passing `write_to_original: false` to the `capture` method. You can then write the captured entries to the underlying device manually by calling `write_to_underlying_device`.
12
+ - Added `capture_logger_around_example` RSpec helper method to simplify capturing log entries in an `around` hook.
13
+
14
+ ### Changed
15
+
16
+ - Depends on `lumberjack` 2.0 or greater.
17
+
18
+ ### Deprecated
19
+
20
+ - The `:level` and `:tags` options on the matching methods (`include?`, `match`, `closest_match`, and `extract`) has been deprecated in favor of `:severity` and `:attributes`.
21
+
22
+ ## 1.2.2
23
+
24
+ ### Changed
25
+
26
+ - Improved failure message on RSpec matcher.
27
+
28
+ ### Added
29
+
30
+ - `Lumberjack::CaptureDevice` now acts as an enumerable object.
31
+ - Exposed helper methods for formatting log entries.
32
+
7
33
  ## 1.2.1
8
34
 
9
35
  ### Changed
data/README.md CHANGED
@@ -44,17 +44,17 @@ logs = Lumberjack::CaptureDevice.capture(Rails.logger) { do_something }
44
44
  assert(logs.include?(level: :info, message: "Something happened"))
45
45
  ```
46
46
 
47
- You can filter the logs on level, message, and tags.
47
+ You can filter the logs on level, message, and attributes.
48
48
 
49
49
  - The level option can take either a label (i.e. `:warn`) or a constant (i.e. `Logger::WARN`).
50
50
  - The message filter can be either an exact string or a regular expression, or any matcher supported by your test library.
51
- - The tags argument can match tags with a Hash mapping tag names to the matcher values. If tags are nested, you can use dot notation on tag names to reference nested tags.
51
+ - The attributes argument can match attributes with a Hash mapping attribute names to the matcher values. If attributes are nested, you can use dot notation on attribute names to reference nested attributes.
52
52
 
53
53
  ```ruby
54
54
  expect(logs).to include(level: :info, message: /something/i)
55
- expect(logs).to include(level: Logger::INFO, tags: {foo: "bar"})
56
- expect(logs).to include(tags: {foo: anything, count: {one: 1}})
57
- expect(logs).to include(tags: {foo: anything, "count.one" => 1})
55
+ expect(logs).to include(level: Logger::INFO, attributes: {foo: "bar"})
56
+ expect(logs).to include(attributes: {foo: anything, count: {one: 1}})
57
+ expect(logs).to include(attributes: {foo: anything, "count.one" => 1})
58
58
  ```
59
59
 
60
60
  You can also use the `Lumberjack::CaptureDevice#extract` method with the same arguments as used by `include?` to extract all log entries that match the filters. You can get all of the log entries with `Lumberjack::CaptureDevice#buffer`.
@@ -72,12 +72,32 @@ This will give you a `capture_logger` method and `include_log_entry` matcher. Th
72
72
  ```ruby
73
73
  describe MyClass do
74
74
  it "logs information" do
75
- logs = capture_logger { MyClass.do_something }
75
+ logs = capture_logger(Rails.logger) { MyClass.do_something }
76
76
  expect(logs).to include_log_entry(message: "Something")
77
77
  end
78
78
  end
79
79
  ```
80
80
 
81
+ You can also set up log capturing around each example with the `capture_logger_around_example` method.
82
+
83
+ ```ruby
84
+ describe MyClass do
85
+ around do |example|
86
+ capture_logger_around_example(Rails.logger, example)
87
+ end
88
+
89
+ it "logs information" do
90
+ MyClass.do_something
91
+ expect(Rails.logger).to include_log_entry(message: "Something")
92
+ end
93
+ end
94
+ ```
95
+
96
+ > [!TIP]
97
+ > Add `capture_logger_around_example` as a global `around` hook in your RSpec configuration to automatically capture log entries for every example.
98
+ >
99
+ > This will also suppress all log output during tests unless an example fails which can reduce noise in the logs from tests that don't fail. This is especially useful in CI environments where you can save the logs as an artifact for failed test runs.
100
+
81
101
  ## Installation
82
102
 
83
103
  Add this line to your application's Gemfile:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.1
1
+ 2.0.0
@@ -1,14 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Class responsible for scoring and matching log entries against filters
3
+ # Class responsible for scoring and matching log entries against filters.
4
+ # This class provides fuzzy matching capabilities to find the best matching
5
+ # log entry when exact matches are not available.
4
6
  class Lumberjack::CaptureDevice::EntryScore
5
7
  # Minimum score threshold for considering a match (30% match)
6
8
  MIN_SCORE_THRESHOLD = 0.3
7
9
 
8
10
  class << self
9
- # Calculate the overall match score for an entry against all provided filters
10
- # Returns a score between 0.0 and 1.0
11
- def calculate_match_score(entry, message_filter, level_filter, tags_filter, progname_filter)
11
+ # Calculate the overall match score for an entry against all provided filters.
12
+ # Returns a score between 0.0 and 1.0, where 1.0 represents a perfect match.
13
+ #
14
+ # @param entry [Lumberjack::LogEntry] The log entry to score.
15
+ # @param message_filter [String, Regexp, nil] The message filter to match against.
16
+ # @param severity_filter [Integer, nil] The severity level to match against.
17
+ # @param attributes_filter [Hash, nil] The attributes hash to match against.
18
+ # @param progname_filter [String, nil] The program name to match against.
19
+ # @return [Float] A score between 0.0 and 1.0 indicating match quality.
20
+ def calculate_match_score(entry, message_filter, severity_filter, attributes_filter, progname_filter)
12
21
  scores = []
13
22
  weights = []
14
23
 
@@ -19,14 +28,14 @@ class Lumberjack::CaptureDevice::EntryScore
19
28
  weights << 0.4 # Weight message matching highly
20
29
  end
21
30
 
22
- # Check level match
23
- if level_filter
24
- level_score = if entry.severity == level_filter
25
- 1.0 # Exact level match
31
+ # Check severity match
32
+ if severity_filter
33
+ severity_score = if entry.severity == severity_filter
34
+ 1.0 # Exact severity match
26
35
  else
27
- level_proximity_score(entry.severity, level_filter) # Partial level match
36
+ severity_proximity_score(entry.severity, severity_filter) # Partial severity match
28
37
  end
29
- scores << level_score
38
+ scores << severity_score
30
39
  weights << 0.3
31
40
  end
32
41
 
@@ -37,10 +46,10 @@ class Lumberjack::CaptureDevice::EntryScore
37
46
  weights << 0.2
38
47
  end
39
48
 
40
- # Check tags match
41
- if tags_filter.is_a?(Hash) && !tags_filter.empty?
42
- tags_score = calculate_tags_score(entry.tags, tags_filter)
43
- scores << tags_score
49
+ # Check attributes match
50
+ if attributes_filter.is_a?(Hash) && !attributes_filter.empty?
51
+ attributes_score = calculate_attributes_score(entry.attributes, attributes_filter)
52
+ scores << attributes_score
44
53
  weights << 0.3
45
54
  end
46
55
 
@@ -63,8 +72,12 @@ class Lumberjack::CaptureDevice::EntryScore
63
72
  base_score
64
73
  end
65
74
 
66
- # Calculate score for any field value against a filter
67
- # Returns a score between 0.0 and 1.0 based on how well the value matches the filter
75
+ # Calculate score for any field value against a filter.
76
+ # Returns a score between 0.0 and 1.0 based on how well the value matches the filter.
77
+ #
78
+ # @param value [Object] The value to match against the filter.
79
+ # @param filter [String, Regexp, Object] The filter to match the value against.
80
+ # @return [Float] A score between 0.0 and 1.0 indicating match quality.
68
81
  def calculate_field_score(value, filter)
69
82
  return 0.0 unless value && filter
70
83
 
@@ -92,10 +105,15 @@ class Lumberjack::CaptureDevice::EntryScore
92
105
  end
93
106
  end
94
107
 
95
- # Calculate proximity score based on log level distance
96
- def level_proximity_score(entry_level, filter_level)
97
- level_diff = (entry_level - filter_level).abs
98
- case level_diff
108
+ # Calculate proximity score based on log severity distance.
109
+ # Provides partial scoring for severities that are close to the target.
110
+ #
111
+ # @param entry_severity [Integer] The severity level of the log entry.
112
+ # @param filter_severity [Integer] The target severity level to match.
113
+ # @return [Float] A score between 0.0 and 1.0 based on severity proximity.
114
+ def severity_proximity_score(entry_severity, filter_severity)
115
+ severity_diff = (entry_severity - filter_severity).abs
116
+ case severity_diff
99
117
  when 0 then 1.0
100
118
  when 1 then 0.7
101
119
  when 2 then 0.4
@@ -103,24 +121,34 @@ class Lumberjack::CaptureDevice::EntryScore
103
121
  end
104
122
  end
105
123
 
106
- # Calculate score for tag matching
107
- def calculate_tags_score(entry_tags, tags_filter)
108
- return 0.0 unless entry_tags && tags_filter.is_a?(Hash)
124
+ # Calculate score for attribute matching.
125
+ # Compares entry attributes against filter attributes and returns a score
126
+ # based on how many attributes match.
127
+ #
128
+ # @param entry_attributes [Hash] The attributes from the log entry.
129
+ # @param attributes_filter [Hash] The attributes filter to match against.
130
+ # @return [Float] A score between 0.0 and 1.0 based on attribute matches.
131
+ def calculate_attributes_score(entry_attributes, attributes_filter)
132
+ return 0.0 unless entry_attributes && attributes_filter.is_a?(Hash)
109
133
 
110
- tags_filter = deep_stringify_keys(Lumberjack::Utils.expand_tags(tags_filter))
111
- tags = deep_stringify_keys(Lumberjack::Utils.expand_tags(entry_tags))
134
+ attributes_filter = deep_stringify_keys(Lumberjack::Utils.expand_attributes(attributes_filter))
135
+ attributes = deep_stringify_keys(Lumberjack::Utils.expand_attributes(entry_attributes))
112
136
 
113
- total_tag_filters = count_tag_filters(tags_filter)
114
- return 0.0 if total_tag_filters == 0
137
+ total_attribute_filters = count_attribute_filters(attributes_filter)
138
+ return 0.0 if total_attribute_filters == 0
115
139
 
116
- matched_tags = count_matched_tags(tags, tags_filter)
117
- matched_tags.to_f / total_tag_filters
140
+ matched_attributes = count_matched_attributes(attributes, attributes_filter)
141
+ matched_attributes.to_f / total_attribute_filters
118
142
  end
119
143
 
120
144
  private
121
145
 
122
- # Calculate string similarity using a simple Levenshtein distance-based approach
123
- # Returns a score between 0.0 and 1.0 where 1.0 is an exact match
146
+ # Calculate string similarity using a simple Levenshtein distance-based approach.
147
+ # Returns a score between 0.0 and 1.0 where 1.0 is an exact match.
148
+ #
149
+ # @param str1 [String] The first string to compare.
150
+ # @param str2 [String] The second string to compare.
151
+ # @return [Float] A similarity score between 0.0 and 1.0.
124
152
  def string_similarity(str1, str2)
125
153
  return 1.0 if str1 == str2
126
154
  return 0.0 if str1.nil? || str2.nil? || str1.empty? || str2.empty?
@@ -142,10 +170,17 @@ class Lumberjack::CaptureDevice::EntryScore
142
170
 
143
171
  # Convert distance to similarity score
144
172
  return 0.0 if max_length == 0
173
+
145
174
  1.0 - (distance.to_f / max_length)
146
175
  end
147
176
 
148
- # Simple Levenshtein distance implementation
177
+ # Simple Levenshtein distance implementation.
178
+ # Calculates the minimum number of single-character edits needed
179
+ # to change one string into another.
180
+ #
181
+ # @param str1 [String] The first string.
182
+ # @param str2 [String] The second string.
183
+ # @return [Integer] The Levenshtein distance between the strings.
149
184
  def levenshtein_distance(str1, str2)
150
185
  return str2.length if str1.empty?
151
186
  return str1.length if str2.empty?
@@ -171,10 +206,15 @@ class Lumberjack::CaptureDevice::EntryScore
171
206
  matrix[str1.length][str2.length]
172
207
  end
173
208
 
174
- def count_tag_filters(tags_filter, count = 0)
175
- tags_filter.each do |_name, value_filter|
209
+ # Count the total number of attribute filters in a nested hash structure.
210
+ #
211
+ # @param attributes_filter [Hash] The attributes filter hash to count.
212
+ # @param count [Integer] The current count (used for recursion).
213
+ # @return [Integer] The total number of filters.
214
+ def count_attribute_filters(attributes_filter, count = 0)
215
+ attributes_filter.each do |_name, value_filter|
176
216
  if value_filter.is_a?(Hash)
177
- count = count_tag_filters(value_filter, count)
217
+ count = count_attribute_filters(value_filter, count)
178
218
  else
179
219
  count += 1
180
220
  end
@@ -182,27 +222,43 @@ class Lumberjack::CaptureDevice::EntryScore
182
222
  count
183
223
  end
184
224
 
185
- def count_matched_tags(tags, tags_filter, count = 0)
186
- return count unless tags && tags_filter
225
+ # Count the number of matched attributes in a nested structure.
226
+ #
227
+ # @param attributes [Hash] The log entry attributes to check.
228
+ # @param attributes_filter [Hash] The filter attributes to match against.
229
+ # @param count [Integer] The current count (used for recursion).
230
+ # @return [Integer] The number of matched attributes.
231
+ def count_matched_attributes(attributes, attributes_filter, count = 0)
232
+ return count unless attributes && attributes_filter
187
233
 
188
- tags_filter.each do |name, value_filter|
234
+ attributes_filter.each do |name, value_filter|
189
235
  name = name.to_s
190
- tag_values = tags[name]
236
+ attribute_values = attributes[name]
191
237
 
192
- if value_filter.is_a?(Hash) && tag_values.is_a?(Hash)
193
- count = count_matched_tags(tag_values, value_filter, count)
194
- elsif tags.include?(name) && exact_match?(tag_values, value_filter)
238
+ if value_filter.is_a?(Hash) && attribute_values.is_a?(Hash)
239
+ count = count_matched_attributes(attribute_values, value_filter, count)
240
+ elsif attributes.include?(name) && exact_match?(attribute_values, value_filter)
195
241
  count += 1
196
242
  end
197
243
  end
198
244
  count
199
245
  end
200
246
 
247
+ # Check if a value exactly matches the filter using the === operator.
248
+ #
249
+ # @param value [Object] The value to match.
250
+ # @param filter [Object] The filter to match against.
251
+ # @return [Boolean] True if the value matches the filter.
201
252
  def exact_match?(value, filter)
202
253
  return true unless filter
254
+
203
255
  filter === value
204
256
  end
205
257
 
258
+ # Recursively convert all keys in a hash structure to strings.
259
+ #
260
+ # @param hash [Hash, Object] The hash to stringify or other object to return as-is.
261
+ # @return [Hash, Object] The hash with string keys or the original object.
206
262
  def deep_stringify_keys(hash)
207
263
  if hash.is_a?(Hash)
208
264
  hash.each_with_object({}) do |(key, value), result|
@@ -2,79 +2,151 @@
2
2
 
3
3
  # RSpec matcher for checking captured logs for specific entries.
4
4
  class Lumberjack::CaptureDevice::IncludeLogEntryMatcher
5
+ # Initialize the matcher with expected log entry attributes.
6
+ #
7
+ # @param expected_hash [Hash] Expected log entry attributes to match against.
5
8
  def initialize(expected_hash)
6
9
  @expected_hash = expected_hash.transform_keys(&:to_sym)
7
- @captured_logger = nil
10
+ @logger = nil
8
11
  end
9
12
 
13
+ # Check if the logger contains a log entry matching the expected attributes.
14
+ #
15
+ # @param actual [Lumberjack::Logger, Lumberjack::ForkedLogger] The logger to check. The logger must be using
16
+ # a Lumberjack::Device::Test device.
17
+ # @return [Boolean] True if a matching log entry is found.
10
18
  def matches?(actual)
11
- @captured_logger = actual
12
- return false unless valid_captured_logger?
19
+ @logger = actual
20
+ return false unless valid_logger?
13
21
 
14
- @captured_logger.include?(@expected_hash)
22
+ device = @logger.is_a?(Lumberjack::Device::Test) ? @logger : @logger.device
23
+ device.include?(@expected_hash)
15
24
  end
16
25
 
26
+ # Generate a failure message when the matcher fails.
27
+ #
28
+ # @return [String] A formatted failure message.
17
29
  def failure_message
18
- if valid_captured_logger?
19
- formatted_failure_message(@captured_logger, @expected_hash, negated: false)
30
+ if valid_logger?
31
+ formatted_failure_message(@logger, @expected_hash)
20
32
  else
21
- wrong_object_type_message(@captured_logger)
33
+ wrong_object_type_message(@logger)
22
34
  end
23
35
  end
24
36
 
37
+ # Generate a failure message when the negated matcher fails.
38
+ #
39
+ # @return [String] A formatted failure message for negated expectations.
25
40
  def failure_message_when_negated
26
- if valid_captured_logger?
27
- formatted_failure_message(@captured_logger, @expected_hash, negated: true)
41
+ if valid_logger?
42
+ formatted_negated_failure_message(@logger, @expected_hash)
28
43
  else
29
- wrong_object_type_message(@captured_logger)
44
+ wrong_object_type_message(@logger)
30
45
  end
31
46
  end
32
47
 
48
+ # Provide a description of what this matcher checks.
49
+ #
50
+ # @return [String] A human-readable description of the matcher.
33
51
  def description
34
52
  "have logged entry with #{expectation_description(@expected_hash)}"
35
53
  end
36
54
 
37
55
  private
38
56
 
39
- def valid_captured_logger?
40
- @captured_logger.is_a?(Lumberjack::CaptureDevice)
57
+ # Check if the logger is using a valid Lumberjack::Device::Test device.
58
+ #
59
+ # @return [Boolean] True if the logger is a Lumberjack::Device::Test.
60
+ def valid_logger?
61
+ return true if @logger.is_a?(Lumberjack::Device::Test)
62
+ return false unless @logger.respond_to?(:device)
63
+
64
+ @logger.device.is_a?(Lumberjack::Device::Test)
41
65
  end
42
66
 
43
- def wrong_object_type_message(captured_logger)
44
- "Expected a Lumberjack::CaptureDevice object, but received a #{captured_logger.class}."
67
+ # Generate an error message for wrong object type.
68
+ #
69
+ # @param logger [Object] The object that was passed instead of a Lumberjack::Device::Test.
70
+ # @return [String] An error message describing the type mismatch.
71
+ def wrong_object_type_message(logger)
72
+ unless logger.respond_to?(:device)
73
+ return "Expected a Lumberjack::Logger object, but received a #{logger.class}."
74
+ end
75
+
76
+ device = logger.device
77
+ "Expected logger device to be a Lumberjack::Device::Test, but it is a #{device.class}."
45
78
  end
46
79
 
47
- def formatted_failure_message(captured_logger, expected_hash, negated:)
48
- closest_match = captured_logger.closest_match(**expected_hash)
49
- message = "expected logs did not include expected entry.\n\n" \
50
- "Expected entry:\n-----------\n#{Lumberjack::CaptureDevice.formatted_expectation(expected_hash)}\n\n" \
51
- "Captured logs:\n-----------\n#{captured_logger.inspect}"
80
+ # Generate a detailed failure message showing expected vs actual logs.
81
+ #
82
+ # @param logger_or_device [Lumberjack::Device::Test] The logger device.
83
+ # @param expected_hash [Hash] The expected log entry attributes.
84
+ # @return [String] A formatted failure message with context.
85
+ def formatted_failure_message(logger_or_device, expected_hash)
86
+ device = logger_or_device.respond_to?(:device) ? logger_or_device.device : logger_or_device
87
+
88
+ # Handle deprecated keys
89
+ if expected_hash.include?(:level) && !expected_hash.include?(:severity)
90
+ expected_hash = expected_hash.merge(severity: expected_hash[:level])
91
+ end
92
+ if expected_hash.include?(:tags) && !expected_hash.include?(:attributes)
93
+ expected_hash = expected_hash.merge(attributes: expected_hash[:tags])
94
+ end
52
95
 
96
+ message = +"expected logs to include entry:\n" \
97
+ "#{Lumberjack::Device::Test.formatted_expectation(expected_hash, indent: 2)}"
98
+
99
+ closest_match = device.closest_match(**expected_hash)
53
100
  if closest_match
54
- # Convert the entry to a hash format for formatted_expectation
55
- closest_match_hash = {
56
- level: closest_match.severity_label,
57
- message: closest_match.message,
58
- progname: closest_match.progname,
59
- tags: closest_match.tags
60
- }.compact
61
-
62
- message = "#{message}\n\nClosest match found:\n-----------\n" \
63
- "#{Lumberjack::CaptureDevice.formatted_expectation(closest_match_hash)}"
101
+ message << "\n\nClosest match found:" \
102
+ "#{Lumberjack::Device::Test.formatted_expectation(closest_match, indent: 2)}"
103
+ end
104
+
105
+ entries = device.entries
106
+ message << "\n\nLogged #{entries.length} #{(entries.length == 1) ? "entry" : "entries"}"
107
+ if entries.length > 0
108
+ message << "\n----------------------\n"
109
+ template = Lumberjack::LocalLogTemplate.new
110
+ entries.each do |entry|
111
+ message << "#{template.call(entry)}\n"
112
+ end
113
+ end
114
+
115
+ message
116
+ end
117
+
118
+ # Generate a failure message for negated expectations.
119
+ #
120
+ # @param logger_or_device [Lumberjack::Device::Test] The logger to check.
121
+ # @param expected_hash [Hash] The expected log entry attributes that should not be present.
122
+ # @return [String] A formatted failure message for negated expectations.
123
+ def formatted_negated_failure_message(logger_or_device, expected_hash)
124
+ device = logger_or_device.respond_to?(:device) ? logger_or_device.device : logger_or_device
125
+ message = "expected logs not to include entry:\n" \
126
+ "#{Lumberjack::Device::Test.formatted_expectation(expected_hash, indent: 2)}"
127
+
128
+ match = device.match(**expected_hash)
129
+ if match
130
+ message = "#{message}\n\nFound entry:\n" \
131
+ "#{Lumberjack::Device::Test.formatted_expectation(match, indent: 2)}"
64
132
  end
65
133
 
66
134
  message
67
135
  end
68
136
 
137
+ # Create a human-readable description of the expected log entry attributes.
138
+ #
139
+ # @param expected_hash [Hash] The expected log entry attributes.
140
+ # @return [String] A formatted description of the expected attributes.
69
141
  def expectation_description(expected_hash)
70
142
  info = []
71
- info << "level: #{expected_hash[:level].inspect}" unless expected_hash[:level].nil?
143
+ info << "severity: #{expected_hash[:severity].inspect}" unless expected_hash[:severity].nil?
72
144
  info << "message: #{expected_hash[:message].inspect}" unless expected_hash[:message].nil?
73
145
  info << "progname: #{expected_hash[:progname].inspect}" unless expected_hash[:progname].nil?
74
- if expected_hash[:tags].is_a?(Hash) && !expected_hash[:tags].empty?
75
- tags = Lumberjack::Utils.flatten_tags(expected_hash[:tags])
76
- tags_info = tags.collect { |name, value| "#{name}=#{value.inspect}" }.join(", ")
77
- info << "tags: #{tags_info}"
146
+ if expected_hash[:attributes].is_a?(Hash) && !expected_hash[:attributes].empty?
147
+ attributes = Lumberjack::Utils.flatten_attributes(expected_hash[:attributes])
148
+ attributes_info = attributes.collect { |name, value| "#{name}=#{value.inspect}" }.join(", ")
149
+ info << "attributes: #{attributes_info}"
78
150
  end
79
151
  info.join(", ")
80
152
  end
@@ -3,13 +3,70 @@
3
3
  require_relative "../capture_device"
4
4
  require "rspec"
5
5
 
6
+ # RSpec helper methods for working with CaptureDevice.
6
7
  module Lumberjack::CaptureDevice::RSpec
8
+ # Create a matcher for checking if a log entry is included in the captured logs.
9
+ # This matcher provides better error messages than using the include? method directly.
10
+ #
11
+ # @param expected_hash [Hash] The expected log entry attributes to match.
12
+ # @option expected_hash [String, Symbol, Integer] :level The expected log level.
13
+ # @option expected_hash [String, Symbol, Integer] :severity Alias for :level.
14
+ # @option expected_hash [String, Regexp] :message The expected message content.
15
+ # @option expected_hash [Hash] :attributes Expected log entry attributes.
16
+ # @option expected_hash [Hash] :tags Alias for :attributes.
17
+ # @option expected_hash [String] :progname Expected program name.
18
+ # @return [Lumberjack::CaptureDevice::IncludeLogEntryMatcher] A matcher for the expected log entry.
19
+ # @example
20
+ # expect(logs).to include_log_entry(level: :info, message: "User logged in")
21
+ # @example
22
+ # expect(logs).to include_log_entry(message: /error/i, attributes: {user_id: 123})
7
23
  def include_log_entry(expected_hash)
8
24
  Lumberjack::CaptureDevice::IncludeLogEntryMatcher.new(expected_hash)
9
25
  end
10
26
 
11
- def capture_logger(logger, &block)
12
- Lumberjack::CaptureDevice.capture(logger, &block)
27
+ # Capture log entries from a logger within a block. This method temporarily
28
+ # replaces the logger's device with a CaptureDevice, sets the log level to debug,
29
+ # and removes formatters to capture raw log entries for testing.
30
+ #
31
+ # @param logger [Lumberjack::Logger] The logger to capture entries from.
32
+ # @yield [device] The block to execute while capturing log entries.
33
+ # @yieldparam device [Lumberjack::CaptureDevice] The device that will capture the log entries.
34
+ # @return [Lumberjack::CaptureDevice] The device that captured the log entries.
35
+ # @example
36
+ # logs = capture_logger(Rails.logger) do
37
+ # Rails.logger.info("Test message")
38
+ # end
39
+ # expect(logs).to include_log_entry(level: :info, message: "Test message")
40
+ def capture_logger(logger, write_to_original: true, &block)
41
+ Lumberjack::CaptureDevice.capture(logger, write_to_original: write_to_original, &block)
42
+ end
43
+
44
+ # RSpec around hook to automatically capture logs for each example. The captured logs are only
45
+ # written to the original logger if the example fails. This helps keep the logs more usable for
46
+ # debugging test failures since it removes all the noise from passing tests.
47
+ #
48
+ # This is designed for CI environments where you can save the logs as artifacts of the test run.
49
+ #
50
+ # @param logger [Lumberjack::Logger] The logger to capture entries for.
51
+ # @param example [RSpec::Core::Example] The current RSpec example.
52
+ #
53
+ # @example Capture logs for a Rails application
54
+ # # In your spec_helper.rb or rails_helper.rb
55
+ # RSpec.configure do |config|
56
+ # config.around do |example|
57
+ # capture_logger_around_example(Rails.logger, example)
58
+ # end
59
+ # end
60
+ def capture_logger_around_example(logger, example)
61
+ capture_logger(logger, write_to_original: false) do |captured_device|
62
+ example.run
63
+
64
+ if example.exception
65
+ logger.tag(rspec: {source_location: example.source_location, description: example.metadata[:description]}) do
66
+ captured_device.write_to_underlying_device
67
+ end
68
+ end
69
+ end
13
70
  end
14
71
  end
15
72
 
@@ -5,10 +5,12 @@ require "lumberjack"
5
5
  module Lumberjack
6
6
  # Lumberjack device for capturing log entries into memory to allow them to be inspected
7
7
  # for testing purposes.
8
- class CaptureDevice < Lumberjack::Device
9
- VERSION = File.read(File.join(__dir__, "..", "..", "VERSION"))
8
+ class CaptureDevice < Lumberjack::Device::Test
9
+ VERSION = ::File.read(::File.join(__dir__, "..", "..", "VERSION")).strip.freeze
10
10
 
11
- attr_reader :buffer
11
+ require_relative "capture_device/include_log_entry_matcher"
12
+
13
+ include Enumerable
12
14
 
13
15
  class << self
14
16
  # Capture the entries written by the logger within a block. Within the block all log
@@ -18,125 +20,153 @@ module Lumberjack
18
20
  # by the method call.
19
21
  #
20
22
  # @param logger [Lumberjack::Logger] The logger to capture entries from.
23
+ # @param write_to_original [Boolean] If true (the default) the captured entries will be written
24
+ # back to the original device when the block completes. If false, the captured entries
25
+ # will not be written back.
21
26
  # @yield [device] The block to execute while capturing log entries.
22
27
  # @return [Lumberjack::CaptureDevice] The device that captured the log entries.
23
28
  # @yieldparam device [Lumberjack::CaptureDevice] The device that will capture the log entries.
24
29
  # @example
25
30
  # Lumberjack::CaptureDevice.capture(logger) do |logs|
26
31
  # logger.info("This will be captured")
27
- # expect(logs).to include(level: :info, message: "This will be captured")
32
+ # expect(logs).to include(severity: :info, message: "This will be captured")
28
33
  # end
29
34
  #
30
35
  # @example
31
36
  # logs = Lumberjack::CaptureDevice.capture(logger) { logger.info("This will be captured") }
32
- # expect(logs).to include(level: :info, message: "This will be captured")
33
- def capture(logger)
34
- device = new
37
+ # expect(logs).to include(severity: :info, message: "This will be captured")
38
+ def capture(logger, write_to_original: true)
35
39
  save_device = logger.device
36
40
  save_level = logger.level
37
- save_formatter = logger.formatter
41
+ device = new(underlying_device: save_device)
42
+
38
43
  begin
39
44
  logger.device = device
40
45
  logger.level = :debug
41
- logger.formatter = Lumberjack::Formatter.empty
42
46
  yield device
43
47
  ensure
44
48
  logger.device = save_device
45
49
  logger.level = save_level
46
- logger.formatter = save_formatter
50
+ device.write_to_underlying_device if write_to_original
47
51
  end
48
- device
49
- end
50
52
 
51
- def formatted_expectation(expectation)
52
- expectation = expectation.transform_keys(&:to_s).compact
53
- message = []
54
- message << "level: #{expectation["level"].inspect}" if expectation.include?("level")
55
- message << "message: #{expectation["message"].inspect}" if expectation.include?("message")
56
- message << "progname: #{expectation["progname"].inspect}" if expectation.include?("progname")
57
- if expectation["tags"].is_a?(Hash) && !expectation["tags"].empty?
58
- tags = Lumberjack::Utils.flatten_tags(expectation["tags"])
59
- prefix = "tags: "
60
- tags.each do |name, value|
61
- message << "#{prefix} #{name}: #{value.inspect}"
62
- prefix = " "
63
- end
64
- end
65
- message.join("\n")
53
+ device
66
54
  end
67
55
  end
68
56
 
69
- def initialize
70
- @buffer = []
71
- end
57
+ # The original device from the logger before capture started.
58
+ #
59
+ # @return [Lumberjack::Device, nil] The original device, or nil if none was set.
60
+ attr_reader :underlying_device
72
61
 
73
- def write(entry)
74
- @buffer << entry
62
+ # Initialize a new CaptureDevice.
63
+ #
64
+ # @param options [Hash] Options to pass to the parent Test device.
65
+ def initialize(options = {})
66
+ @underlying_device = options[:underlying_device]
67
+ super(options.merge(max_entries: 1_000_000))
75
68
  end
76
69
 
77
- # Clear all entries that have been written to the buffer.
78
- def clear
79
- @buffer.clear
70
+ # Return all the captured entries that match the specified filters. These filters are
71
+ # the same as described in the `include?` method.
72
+ #
73
+ # @param message [String, Regexp, nil] The message to match against the log entries.
74
+ # @param severity [String, Symbol, Integer, nil] The severity to match against the log entries.
75
+ # @param attributes [Hash, nil] A hash of attribute names to values to match against the log entries. The attributes
76
+ # will match nested attributes using dot notation (e.g. `foo.bar` will match an attribute with the structure
77
+ # +{foo: {bar: "value"}}+).
78
+ # @param progname [String, nil] The program name to match against the log entries.
79
+ # @param limit [Integer, nil] The maximum number of entries to return. If nil, all matching entries
80
+ # will be returned.
81
+ # @param level [String, Symbol, Integer, nil] Alias for the `severity` parameter.
82
+ # @param tags [Hash, nil] Alias for the `attributes` parameter.
83
+ # @return [Array<Lumberjack::LogEntry>] An array of log entries that match the specified filters.
84
+ def extract(message: nil, severity: nil, attributes: nil, progname: nil, limit: nil, level: nil, tags: nil)
85
+ matched = []
86
+ if severity.nil? && !level.nil?
87
+ Lumberjack::Utils.deprecated("Lumberjack::CaptureDevice#extract(level)", "Lumberjack::CaptureDevice#extract level parameter has been renamed to severity; it will be removed in version 2.1.")
88
+ severity = level
89
+ end
90
+ if attributes.nil? && !tags.nil?
91
+ Lumberjack::Utils.deprecated("Lumberjack::CaptureDevice#extract(tags)", "Lumberjack::CaptureDevice#extract tags parameter has been renamed to attributes; it will be removed in version 2.1.")
92
+ attributes = tags
93
+ end
94
+
95
+ matcher = LogEntryMatcher.new(message: message, severity: severity, attributes: attributes, progname: progname)
96
+
97
+ entries.each do |entry|
98
+ matched << entry if matcher.match?(entry)
99
+ break if limit && matched.size >= limit
100
+ end
101
+
102
+ matched
80
103
  end
81
104
 
82
- # Return true if the captured log entries match the specified level, message, and tags.
105
+ # Return true if the captured log entries match the specified level, message, and attributes.
83
106
  #
84
- # For level, you can specified either a numeric constant (i.e. `Logger::WARN`) or a symbol
107
+ # For level, you can specify either a numeric constant (i.e. `Logger::WARN`) or a symbol
85
108
  # (i.e. `:warn`).
86
109
  #
87
110
  # For message you can specify a string to perform an exact match or a regular expression
88
111
  # to perform a partial or pattern match. You can also supply any matcher value available
89
112
  # in your test library (i.e. in rspec you could use `anything` or `instance_of(Error)`, etc.).
90
113
  #
91
- # For tags, you can specify a hash of tag names to values to match. You can use
92
- # regular expression or matchers as the values here as well. Tags can also be nested to match
93
- # nested tags.
94
- #
95
- # Example:
114
+ # For attributes, you can specify a hash of attribute names to values to match. You can use
115
+ # regular expression or matchers as the values here as well. attributes can also be nested to match
116
+ # nested attributes.
96
117
  #
97
- # ```
98
- # logs.include(level: :warn, message: /something happened/, tags: {duration: instance_of(Float)})
99
- # ```
118
+ # @example
119
+ # logs.include?(level: :warn, message: /something happened/, attributes: {user: "john"})
100
120
  #
101
- # @param args [Hash] The filters to apply to the captured entries.
102
- # @option args [String, Regexp] :message The message to match against the log entries.
103
- # @option args [String, Symbol, Integer] :level The log level to match against the log entries.
104
- # @option args [Hash] :tags A hash of tag names to values to match against the log entries. The tags
105
- # will match nested tags using dot notation (e.g. `foo.bar` will match a tag with the structure
106
- # `{foo: {bar: "value"}}`).
107
- # @option args [String] :progname The program name to match against the log entries.
121
+ # @param filters [Hash] The filters to apply to the captured entries.
122
+ # @option filters [String, Regexp] :message The message to match against the log entries.
123
+ # @option filters [String, Symbol, Integer] :severity The log level to match against the log entries.
124
+ # @option filters [Hash] :attributes A hash of attribute names to values to match against the log entries. The attributes
125
+ # will match nested attributes using dot notation (e.g. `foo.bar` will match an attribute with the structure
126
+ # +{foo: {bar: "value"}}+).
127
+ # @option filters [String] :progname The program name to match against the log entries.
128
+ # @option filters [String, Symbol, Integer, nil] :level Alias for the `severity` option. This option is deprecated.
129
+ # @option filters [Hash, nil] :tags Alias for the `attributes` option. This option is deprecated.
108
130
  # @return [Boolean] True if any entries match the specified filters, false otherwise.
109
- def include?(args)
110
- !extract(**args.merge(limit: 1)).empty?
131
+ def include?(filters)
132
+ if filters.include?(:level)
133
+ Lumberjack::Utils.deprecated("Lumberjack::CaptureDevice#include?(level)", "Lumberjack::CaptureDevice#include? level option has been renamed to severity; it will be removed in version 2.1.")
134
+ end
135
+
136
+ if filters.include?(:tags)
137
+ Lumberjack::Utils.deprecated("Lumberjack::CaptureDevice#include?(tags)", "Lumberjack::CaptureDevice#include? tags option has been renamed to attributes; it will be removed in version 2.1.")
138
+ end
139
+
140
+ munged_filters = {
141
+ message: filters[:message],
142
+ severity: filters[:severity] || filters[:level],
143
+ attributes: filters[:attributes] || filters[:tags],
144
+ progname: filters[:progname]
145
+ }.compact
146
+
147
+ !!match(**munged_filters)
111
148
  end
112
149
 
113
- # Return all the captured entries that match the specified filters. These filters are
114
- # the same as described in the `include?` method.
150
+ # Return the first captured entry that matches the filters.
115
151
  #
116
152
  # @param message [String, Regexp, nil] The message to match against the log entries.
117
- # @param level [String, Symbol, Integer, nil] The log level to match against the log entries.
118
- # @param tags [Hash, nil] A hash of tag names to values to match against the log entries. The tags
119
- # will match nested tags using dot notation (e.g. `foo.bar` will match a tag with the structure
120
- # `{foo: {bar: "value"}}`).
121
- # @param limit [Integer, nil] The maximum number of entries to return. If nil, all matching entries
122
- # will be returned.
123
- # @return [Array<Lumberjack::Entry>] An array of log entries that match the specified filters.
124
- def extract(message: nil, level: nil, tags: nil, limit: nil, progname: nil)
125
- matches = []
126
-
127
- if level
128
- # Normalize the level filter to numeric values.
129
- level = (level.is_a?(Integer) ? level : Lumberjack::Severity.label_to_level(level))
153
+ # @param severity [String, Symbol, Integer, nil] The log level to match against the log entries.
154
+ # @param attributes [Hash, nil] A hash of attribute names to values to match against the log entries. The attributes
155
+ # will match nested attributes using dot notation (e.g. `foo.bar` will match an attribute with the structure
156
+ # +{foo: {bar: "value"}}+).
157
+ # @param progname [String, nil] The program name to match against the log entries.
158
+ # @param level [String, Symbol, Integer, nil] Alias for the `severity` parameter. This parameter is deprecated.
159
+ # @param tags [Hash, nil] Alias for the `attributes` parameter. This parameter is deprecated.
160
+ # @return [Lumberjack::LogEntry, nil] The first matching log entry, or nil if no match is found.
161
+ def match(message: nil, severity: nil, attributes: nil, progname: nil, level: nil, tags: nil)
162
+ unless level.nil?
163
+ Lumberjack::Utils.deprecated("Lumberjack::CaptureDevice#match(level)", "Lumberjack::CaptureDevice#match level parameter has been renamed to severity; it will be removed in version 2.1.")
130
164
  end
131
-
132
- @buffer.each do |entry|
133
- if matched?(entry, message, level, tags, progname)
134
- matches << entry
135
- break if limit && matches.size >= limit
136
- end
165
+ unless tags.nil?
166
+ Lumberjack::Utils.deprecated("Lumberjack::CaptureDevice#match(tags)", "Lumberjack::CaptureDevice#match tags parameter has been renamed to attributes; it will be removed in version 2.1.")
137
167
  end
138
168
 
139
- matches
169
+ super(message: message, severity: severity || level, attributes: attributes || tags, progname: progname)
140
170
  end
141
171
 
142
172
  # Return the log entry that most closely matches the specified filters. This method
@@ -145,118 +175,75 @@ module Lumberjack
145
175
  # they match. Returns nil if no entry meets the minimum matching criteria.
146
176
  #
147
177
  # @param message [String, Regexp, nil] The message to match against the log entries.
148
- # @param level [String, Symbol, Integer, nil] The log level to match against the log entries.
149
- # @param tags [Hash, nil] A hash of tag names to values to match against the log entries.
178
+ # @param severity [String, Symbol, Integer, nil] The severity to match against the log entries.
179
+ # @param attributes [Hash, nil] A hash of attribute names to values to match against the log entries.
150
180
  # @param progname [String, nil] The program name to match against the log entries.
151
- # @return [Lumberjack::Entry, nil] The log entry that most closely matches the filters, or nil if no entry meets minimum criteria.
152
- def closest_match(message: nil, level: nil, tags: nil, progname: nil)
153
- return nil if @buffer.empty?
154
-
155
- # Normalize level filter
156
- if level
157
- level = (level.is_a?(Integer) ? level : Lumberjack::Severity.label_to_level(level))
181
+ # @param level [String, Symbol, Integer, nil] Alias for the `severity` parameter.
182
+ # @param tags [Hash, nil] Alias for the `attributes` parameter.
183
+ # @return [Lumberjack::LogEntry, nil] The log entry that most closely matches the filters, or nil if no entry meets minimum criteria.
184
+ def closest_match(message: nil, severity: nil, attributes: nil, progname: nil, level: nil, tags: nil)
185
+ return nil if length == 0
186
+
187
+ unless level.nil?
188
+ Lumberjack::Utils.deprecated("Lumberjack::CaptureDevice#closest_match(level)", "Lumberjack::CaptureDevice#closest_match level parameter has been renamed to severity; it will be removed in version 2.1.")
158
189
  end
159
-
160
- best_entry = nil
161
- best_score = 0
162
-
163
- @buffer.each do |entry|
164
- score = Lumberjack::CaptureDevice::EntryScore.calculate_match_score(entry, message, level, tags, progname)
165
- if score > best_score && score >= Lumberjack::CaptureDevice::EntryScore::MIN_SCORE_THRESHOLD
166
- best_score = score
167
- best_entry = entry
168
- end
190
+ unless tags.nil?
191
+ Lumberjack::Utils.deprecated("Lumberjack::CaptureDevice#closest_match(tags)", "Lumberjack::CaptureDevice#closest_match tags parameter has been renamed to attributes; it will be removed in version 2.1.")
169
192
  end
170
193
 
171
- best_entry
194
+ super(
195
+ message: message,
196
+ severity: severity || level,
197
+ attributes: attributes || tags,
198
+ progname: progname
199
+ )
172
200
  end
173
201
 
202
+ # Write the captured log entries to the underlying device.
203
+ #
204
+ # @return [void]
205
+ def write_to_underlying_device
206
+ write_to(@underlying_device) if @underlying_device
207
+ end
208
+
209
+ # Provide a detailed string representation showing all captured entries.
210
+ #
211
+ # @return [String] A formatted string showing all captured log entries.
174
212
  def inspect
175
- message = +"<##{self.class.name} #{@buffer.size} #{(@buffer.size == 1) ? "entry" : "entries"} captured:"
176
- @buffer.each do |entry|
177
- message << "\n #{formatted_entry(entry)}"
213
+ message = +"<##{self.class.name} #{length} #{(length == 1) ? "entry" : "entries"} captured:\n"
214
+ template = Lumberjack::LocalLogTemplate.new
215
+ entries.each do |entry|
216
+ formatted = template.call(entry).split("\n").collect { |line| " #{line}" }.join("\n")
217
+ message << formatted
218
+ message << "\n"
178
219
  end
179
- message << "\n>"
220
+ message << ">"
180
221
  message
181
222
  end
182
223
 
224
+ # Provide a simple string representation showing the count of captured entries.
225
+ #
226
+ # @return [String] A brief description of the captured entries count.
183
227
  def to_s
184
- "<##{self.class.name} #{@buffer.size} #{(@buffer.size == 1) ? "entry" : "entries"} captured>"
228
+ "<##{self.class.name} #{length} #{(length == 1) ? "entry" : "entries"} captured>"
185
229
  end
186
230
 
187
- private
188
-
189
- def matched?(entry, message_filter, level_filter, tags_filter, progname_filter)
190
- return false unless match?(entry.message, message_filter)
191
- return false unless match?(entry.severity, level_filter)
192
- return false unless match?(entry.progname, progname_filter)
193
-
194
- if tags_filter.is_a?(Hash)
195
- tags_filter = deep_stringify_keys(Lumberjack::Utils.expand_tags(tags_filter))
196
- end
197
- tags = deep_stringify_keys(Lumberjack::Utils.expand_tags(entry.tags))
198
-
199
- return false unless match_tags?(tags, tags_filter)
200
-
201
- true
202
- end
203
-
204
- def match?(value, filter)
205
- return true unless filter
206
-
207
- filter === value
208
- end
209
-
210
- def match_tags?(tags, filter)
211
- return true unless filter
212
- return false unless tags
213
-
214
- filter.all? do |name, value_filter|
215
- name = name.to_s
216
- tag_values = tags[name]
217
- if tag_values.is_a?(Hash)
218
- if value_filter.is_a?(Hash)
219
- match_tags?(tag_values, value_filter)
220
- else
221
- false
222
- end
223
- elsif value_filter.nil? || (value_filter.is_a?(Enumerable) && value_filter.empty?)
224
- tag_values.nil? || (tag_values.is_a?(Array) && tag_values.empty?)
225
- elsif tags.include?(name)
226
- match?(tag_values, value_filter)
227
- else
228
- false
229
- end
230
- end
231
+ # Return the number of captured log entries.
232
+ #
233
+ # @return [Integer] The number of captured entries.
234
+ def length
235
+ @buffer.length
231
236
  end
232
237
 
233
- def deep_stringify_keys(hash)
234
- if hash.is_a?(Hash)
235
- hash.each_with_object({}) do |(key, value), result|
236
- new_key = key.to_s
237
- new_value = deep_stringify_keys(value)
238
- result[new_key] = new_value
239
- end
240
- elsif hash.is_a?(Enumerable)
241
- hash.collect { |item| deep_stringify_keys(item) }
242
- else
243
- hash
244
- end
245
- end
238
+ alias_method :size, :length
246
239
 
247
- def formatted_entry(entry)
248
- timestamp = entry.time.strftime("%Y-%m-%d %H:%M:%S")
249
- formatted = +"#{timestamp} #{entry.severity_label}: #{entry.message}"
250
- formatted << "\n progname: #{entry.progname}" if entry.progname.to_s != ""
251
- if entry.tags && !entry.tags.empty?
252
- Lumberjack::Utils.flatten_tags(entry.tags).to_a.sort_by(&:first).each do |name, value|
253
- formatted << "\n #{name}: #{value}"
254
- end
255
- end
256
- formatted
240
+ # Iterate over each captured log entry.
241
+ #
242
+ # @yield [entry] Block to execute for each captured entry.
243
+ # @yieldparam entry [Lumberjack::LogEntry] A captured log entry.
244
+ # @return [Array<Lumberjack::LogEntry>] The captured entries (when no block given).
245
+ def each(&block)
246
+ @buffer.each(&block)
257
247
  end
258
248
  end
259
249
  end
260
-
261
- require_relative "capture_device/entry_score"
262
- require_relative "capture_device/include_log_entry_matcher"
@@ -31,8 +31,8 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.require_paths = ["lib"]
33
33
 
34
- spec.required_ruby_version = ">= 2.5"
34
+ spec.required_ruby_version = ">= 2.7"
35
35
 
36
- spec.add_dependency "lumberjack", ">=1.3.3"
36
+ spec.add_dependency "lumberjack", ">=2.0"
37
37
  spec.add_development_dependency "bundler"
38
38
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lumberjack_capture_device
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-07-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: lumberjack
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 1.3.3
18
+ version: '2.0'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: 1.3.3
25
+ version: '2.0'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: bundler
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +37,6 @@ dependencies:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
- description:
42
40
  email:
43
41
  - bbdurand@gmail.com
44
42
  executables: []
@@ -62,7 +60,6 @@ metadata:
62
60
  homepage_uri: https://github.com/bdurand/lumberjack_capture_device
63
61
  source_code_uri: https://github.com/bdurand/lumberjack_capture_device
64
62
  changelog_uri: https://github.com/bdurand/lumberjack_capture_device/blob/main/CHANGELOG.md
65
- post_install_message:
66
63
  rdoc_options: []
67
64
  require_paths:
68
65
  - lib
@@ -70,15 +67,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
67
  requirements:
71
68
  - - ">="
72
69
  - !ruby/object:Gem::Version
73
- version: '2.5'
70
+ version: '2.7'
74
71
  required_rubygems_version: !ruby/object:Gem::Requirement
75
72
  requirements:
76
73
  - - ">="
77
74
  - !ruby/object:Gem::Version
78
75
  version: '0'
79
76
  requirements: []
80
- rubygems_version: 3.4.10
81
- signing_key:
77
+ rubygems_version: 3.6.9
82
78
  specification_version: 4
83
79
  summary: Testing device for the lumberjack gem that can be used for asserting messages
84
80
  have been logged in a test suite.