lumberjack_capture_device 1.2.1 → 1.2.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: ee398d1d45c445f0afb2469285cfbbfc6896313251078684619ca5a12bbcc0e3
4
- data.tar.gz: c81bc5da733d88a2213e7b6459d8b0be81a9501484862043dea0d3761c364b57
3
+ metadata.gz: 9147e66264c0eeebb01f7c04df1b6e18ab64bc4db3337e968d6cd1d2201fef7f
4
+ data.tar.gz: f01bf2e332024e9ef26ef703b374956e5f17c6bb4f351cb4518b1591b62dd992
5
5
  SHA512:
6
- metadata.gz: 790bc14321e6e30426098616d96ee5425d63b58b9041e251001856dc0b1abb8549a220f892570df4c1c85ca398002a3e94de37f395f45fb84b406c5331ccf655
7
- data.tar.gz: 2512ec2c40dbefe1242abe07d3c7b0a391e9a425c18abc54247d9a76195f73a548f6a080d51d8463caa92512c34b96d9bff81e06179b60fc0569d98c4b1fc50f
6
+ metadata.gz: 5e5ae9f94313a18f793b299998c9760aaa8d7bf5d8f462f48b4519f83c8844893ff894e5b570e31b15d55dc1ad9d25ee723529dc72e51db83d5060a4bcf1597c
7
+ data.tar.gz: 06a11ff5e4c5f3afed7cda12fa2f5f9bf94369f6c5b382d7118622136b7f1507d67c1e68ca472c6c38d1bf38cd3aa6db66f4859a145f78b9b69a8f9f4024a634
data/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ 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
+ ## 1.2.2
8
+
9
+ ### Changed
10
+
11
+ - Improved failure message on RSpec matcher.
12
+
13
+ ### Added
14
+
15
+ - `Lumberjack::CaptureDevice` now acts as an enumerable object.
16
+ - Exposed helper methods for formatting log entries.
17
+
7
18
  ## 1.2.1
8
19
 
9
20
  ### Changed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.1
1
+ 1.2.2
@@ -16,7 +16,7 @@ class Lumberjack::CaptureDevice::IncludeLogEntryMatcher
16
16
 
17
17
  def failure_message
18
18
  if valid_captured_logger?
19
- formatted_failure_message(@captured_logger, @expected_hash, negated: false)
19
+ formatted_failure_message(@captured_logger, @expected_hash)
20
20
  else
21
21
  wrong_object_type_message(@captured_logger)
22
22
  end
@@ -24,7 +24,7 @@ class Lumberjack::CaptureDevice::IncludeLogEntryMatcher
24
24
 
25
25
  def failure_message_when_negated
26
26
  if valid_captured_logger?
27
- formatted_failure_message(@captured_logger, @expected_hash, negated: true)
27
+ formatted_negated_failure_message(@captured_logger, @expected_hash)
28
28
  else
29
29
  wrong_object_type_message(@captured_logger)
30
30
  end
@@ -44,23 +44,35 @@ class Lumberjack::CaptureDevice::IncludeLogEntryMatcher
44
44
  "Expected a Lumberjack::CaptureDevice object, but received a #{captured_logger.class}."
45
45
  end
46
46
 
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}"
47
+ def formatted_failure_message(captured_logger, expected_hash)
48
+ message = +"expected logs to include entry:\n" \
49
+ "#{Lumberjack::CaptureDevice.formatted_expectation(expected_hash, indent: 2)}\n\n" \
50
+ "Captured #{captured_logger.length} log #{(captured_logger.length == 1) ? "entry" : "entries"}"
51
+
52
+ if captured_logger.length > 0
53
+ message << "\n----------------------\n"
54
+ captured_logger.each do |entry|
55
+ message << "#{Lumberjack::CaptureDevice.formatted_entry(entry)}\n"
56
+ end
57
+ end
52
58
 
59
+ closest_match = captured_logger.closest_match(**expected_hash)
53
60
  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)}"
61
+ message = "#{message}\n\nClosest match found:" \
62
+ "#{Lumberjack::CaptureDevice.formatted_expectation(closest_match, indent: 2)}"
63
+ end
64
+
65
+ message
66
+ end
67
+
68
+ def formatted_negated_failure_message(captured_logger, expected_hash)
69
+ message = "expected logs not to include entry:\n" \
70
+ "#{Lumberjack::CaptureDevice.formatted_expectation(expected_hash, indent: 2)}"
71
+
72
+ match = captured_logger.match(**expected_hash)
73
+ if match
74
+ message = "#{message}\n\nFound entry:\n" \
75
+ "#{Lumberjack::CaptureDevice.formatted_expectation(match, indent: 2)}"
64
76
  end
65
77
 
66
78
  message
@@ -8,8 +8,9 @@ module Lumberjack
8
8
  class CaptureDevice < Lumberjack::Device
9
9
  VERSION = File.read(File.join(__dir__, "..", "..", "VERSION"))
10
10
 
11
- attr_reader :buffer
11
+ include Enumerable
12
12
 
13
+ attr_reader :buffer
13
14
  class << self
14
15
  # Capture the entries written by the logger within a block. Within the block all log
15
16
  # entries will be written to a CaptureDevice rather than to the normal output for
@@ -48,18 +49,51 @@ module Lumberjack
48
49
  device
49
50
  end
50
51
 
51
- def formatted_expectation(expectation)
52
+ # Helper method to format a log entry for display.
53
+ #
54
+ # @param entry [Lumberjack::LogEntry] The log entry to format.
55
+ # @param indent [Integer] The indentation to prefix on every line.
56
+ # @return [String] The formatted log entry.
57
+ def formatted_entry(entry, indent: 0)
58
+ indent_str = " " * indent
59
+ timestamp = entry.time.strftime("%Y-%m-%d %H:%M:%S")
60
+ formatted = +"#{indent_str}#{timestamp} #{entry.severity_label}: #{entry.message}"
61
+ formatted << "\n#{indent_str} progname: #{entry.progname}" if entry.progname.to_s != ""
62
+ if entry.tags && !entry.tags.empty?
63
+ Lumberjack::Utils.flatten_tags(entry.tags).to_a.sort_by(&:first).each do |name, value|
64
+ formatted << "\n#{indent_str} #{name}: #{value}"
65
+ end
66
+ end
67
+ formatted
68
+ end
69
+
70
+ # Format a log entry or expectation hash into a more human readable format.
71
+ #
72
+ # @param expectation [Hash, Lumberjack::LogEntry] The expectation or log entry to format.
73
+ # @return [String] A formatted string representation of the expectation or log entry.
74
+ def formatted_expectation(expectation, indent: 0)
75
+ if expectation.is_a?(Lumberjack::LogEntry)
76
+ expectation = {
77
+ "level" => expectation.severity_label,
78
+ "message" => expectation.message,
79
+ "progname" => expectation.progname,
80
+ "tags" => expectation.tags
81
+ }
82
+ end
83
+
52
84
  expectation = expectation.transform_keys(&:to_s).compact
85
+
53
86
  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")
87
+ indent_str = " " * indent
88
+ message << "#{indent_str}level: #{expectation["level"].inspect}" if expectation.include?("level")
89
+ message << "#{indent_str}message: #{expectation["message"].inspect}" if expectation.include?("message")
90
+ message << "#{indent_str}progname: #{expectation["progname"].inspect}" if expectation.include?("progname")
57
91
  if expectation["tags"].is_a?(Hash) && !expectation["tags"].empty?
58
92
  tags = Lumberjack::Utils.flatten_tags(expectation["tags"])
59
93
  prefix = "tags: "
60
- tags.each do |name, value|
94
+ tags.sort_by(&:first).each do |name, value|
61
95
  message << "#{prefix} #{name}: #{value.inspect}"
62
- prefix = " "
96
+ prefix = "#{indent_str} "
63
97
  end
64
98
  end
65
99
  message.join("\n")
@@ -107,7 +141,7 @@ module Lumberjack
107
141
  # @option args [String] :progname The program name to match against the log entries.
108
142
  # @return [Boolean] True if any entries match the specified filters, false otherwise.
109
143
  def include?(args)
110
- !extract(**args.merge(limit: 1)).empty?
144
+ !!match(**args)
111
145
  end
112
146
 
113
147
  # Return all the captured entries that match the specified filters. These filters are
@@ -120,8 +154,8 @@ module Lumberjack
120
154
  # `{foo: {bar: "value"}}`).
121
155
  # @param limit [Integer, nil] The maximum number of entries to return. If nil, all matching entries
122
156
  # 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)
157
+ # @return [Array<Lumberjack::LogEntry>] An array of log entries that match the specified filters.
158
+ def extract(message: nil, level: nil, tags: nil, progname: nil, limit: nil)
125
159
  matches = []
126
160
 
127
161
  if level
@@ -139,6 +173,17 @@ module Lumberjack
139
173
  matches
140
174
  end
141
175
 
176
+ # Return the first entry that matches the specified filters.
177
+ #
178
+ # @param message [String, Regexp, nil] The message to match against the log entries.
179
+ # @param level [String, Symbol, Integer, nil] The log level to match against the log entries.
180
+ # @param tags [Hash, nil] A hash of tag names to values to match against the log entries.
181
+ # @param progname [String, nil] The program name to match against the log entries.
182
+ # @return [Lumberjack::LogEntry, nil] The log entry that most closely matches the filters, or nil if no entry meets minimum criteria.
183
+ def match(message: nil, level: nil, tags: nil, progname: nil)
184
+ extract(message: message, level: level, tags: tags, progname: progname, limit: 1).first
185
+ end
186
+
142
187
  # Return the log entry that most closely matches the specified filters. This method
143
188
  # uses fuzzy matching logic to find the best match when no exact match exists.
144
189
  # The matching score is calculated based on how many criteria are met and how closely
@@ -148,10 +193,13 @@ module Lumberjack
148
193
  # @param level [String, Symbol, Integer, nil] The log level to match against the log entries.
149
194
  # @param tags [Hash, nil] A hash of tag names to values to match against the log entries.
150
195
  # @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.
196
+ # @return [Lumberjack::LogEntry, nil] The log entry that most closely matches the filters, or nil if no entry meets minimum criteria.
152
197
  def closest_match(message: nil, level: nil, tags: nil, progname: nil)
153
198
  return nil if @buffer.empty?
154
199
 
200
+ exact_match = match(message: message, level: level, tags: tags, progname: progname)
201
+ return exact_match if exact_match
202
+
155
203
  # Normalize level filter
156
204
  if level
157
205
  level = (level.is_a?(Integer) ? level : Lumberjack::Severity.label_to_level(level))
@@ -174,7 +222,7 @@ module Lumberjack
174
222
  def inspect
175
223
  message = +"<##{self.class.name} #{@buffer.size} #{(@buffer.size == 1) ? "entry" : "entries"} captured:"
176
224
  @buffer.each do |entry|
177
- message << "\n #{formatted_entry(entry)}"
225
+ message << "\n #{Lumberjack::CaptureDevice.formatted_entry(entry)}"
178
226
  end
179
227
  message << "\n>"
180
228
  message
@@ -184,6 +232,16 @@ module Lumberjack
184
232
  "<##{self.class.name} #{@buffer.size} #{(@buffer.size == 1) ? "entry" : "entries"} captured>"
185
233
  end
186
234
 
235
+ def length
236
+ @buffer.length
237
+ end
238
+
239
+ alias_method :size, :length
240
+
241
+ def each(&block)
242
+ @buffer.each(&block)
243
+ end
244
+
187
245
  private
188
246
 
189
247
  def matched?(entry, message_filter, level_filter, tags_filter, progname_filter)
@@ -243,18 +301,6 @@ module Lumberjack
243
301
  hash
244
302
  end
245
303
  end
246
-
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
257
- end
258
304
  end
259
305
  end
260
306
 
metadata CHANGED
@@ -1,14 +1,14 @@
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: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-27 00:00:00.000000000 Z
11
+ date: 2025-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lumberjack