lumberjack_capture_device 1.0.1 → 1.1.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: 35d8ad5610c0d02fbc90b22a141d58aac09de8f98dcbfa85ed71c69d90e3aae2
4
- data.tar.gz: 96657c5cb4a6dfc2431b8fe1689469950f5f144db64f29413df79baa1bf18aeb
3
+ metadata.gz: f40306e5addb7cd7a23f739e937f26c6f42ab6a7dc2836cf0e9c128fa0835541
4
+ data.tar.gz: 282b90a7dfe1de511806b0dbf1a8597c6ab7a00a9a031e64f642242fe686b3f1
5
5
  SHA512:
6
- metadata.gz: f22f7e086c58df3f4fc180b8d84df5bb49c7fc3966f578ae644b8a9c95ca7a143ad71f82f277622af4607377e9ea7219af10f07af6d38bbd07d674a4e3746a9c
7
- data.tar.gz: 706cf53df406ca2702b314d89b9f38e9d55c764e667a852a7c2e08fc30e808315a5199e7f603acab6af8748fb15b078da69b9985a10d60c16806b6569e43f48b
6
+ metadata.gz: ddfc01f72e76421232add226ed4d6d8158119883aea02483469d86c8fad428654c576c0df91441acfa3b2332ea3e696419651adfa61549dfd499bda43f7062df
7
+ data.tar.gz: 8f8b915dfebdb33f58d0f06a143f9a61a919fd503319d2eb74157f221afaba19e30c66884915b682ae772ba6c2fbbea66fabfd3a073c42cde3b57f9398d89589
data/CHANGELOG.md CHANGED
@@ -4,10 +4,30 @@ 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.1.1
8
+
9
+ ### Fixed
10
+
11
+ - Handle tag array comparison when an array contains hashes to consistently convert the hash keys to strings. Otherwise array hashes were being treated differently that other tag structures.
12
+
13
+ ### Changed
14
+
15
+ - Improved the `inspect` method to provide a clearer representation of captured log entries to make debugging tests easier.
16
+
17
+ ## 1.1.0
18
+
19
+ ### Added
20
+
21
+ - Support for matching tag structures in log entries regardless of if they are specified with dot notation or nested tags. So "foo.bar" will match a tag with the structure `{foo: {bar: "value"}}` or `{"foo.bar" => value}`.
22
+
7
23
  ## 1.0.1
24
+
8
25
  ### Added
26
+
9
27
  - Ruby 3 compatibility
10
28
 
11
29
  ## 1.0.0
30
+
12
31
  ### Added
32
+
13
33
  - Initial release.
data/README.md CHANGED
@@ -1,26 +1,27 @@
1
+ # Lumberjack Capture Device
2
+
1
3
  [![Continuous Integration](https://github.com/bdurand/lumberjack_capture_device/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/lumberjack_capture_device/actions/workflows/continuous_integration.yml)
2
4
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
+ [![Gem Version](https://badge.fury.io/rb/lumberjack_capture_device.svg)](https://badge.fury.io/rb/lumberjack_capture_device)
3
6
 
4
- # Lumberjack Capture Device
5
-
6
- This is a plugin device for the [lumberjack gem](https://github.com/bdurand/lumberjack) that enables capturing log messages in a test suite so that assertions can be made against them. It provides and easy and stable method of testing that specific log messages are being sent to a logger.
7
+ This is a plugin device for the [lumberjack gem](https://github.com/bdurand/lumberjack) that enables capturing log messages in a test suite so that assertions can be made against them. It provides an easy and stable method of testing that specific log messages are being sent to a logger.
7
8
 
8
- Using mocks and stubs on a logger to test that it receives messages can make for a brittle test suite since there can a wide variety of code writing messages to logs and your test suite may have a higher log level turned on causing it skip messages at a lower level.
9
+ Using mocks and stubs on a logger to test that it receives messages can make for a brittle test suite since there can be a wide variety of code writing messages to logs and your test suite may have a higher log level turned on causing it to skip messages at a lower level.
9
10
 
10
- For instance, this rspec code can break if any of the code called by the `do_something` writes a different info log message:
11
+ For instance, this RSpec code can break if any of the code called by `do_something` writes a different info log message:
11
12
 
12
13
  ```ruby
13
14
  do_something
14
- expect(Rail.logger).to receive(:info).with("Something happened")
15
+ expect(Rails.logger).to receive(:info).with("Something happened")
15
16
  ```
16
17
 
17
18
  It will also break if the test suite logger has the log level set to `warn` or higher since it will then skip all info and debug messages.
18
19
 
19
20
  ## Usage
20
21
 
21
- You can call the `Lumberjack::CaptureDevice.capture` method to override a logger so that it will capture log entries within a block to an in memory buffer. This method will yield the capturing log device as well as return it as the result of the method. The log level will also be temporarily set to debug within the block, so you can capture all log messages without having to change the log level for the entire test suite.
22
+ You can call the `Lumberjack::CaptureDevice.capture` method to temporarily override a logger so that it will capture log entries within a block to an in-memory buffer. This method will yield the capturing log device and also return it as the result of the method. The log level will also be temporarily set to debug within the block, so you can capture all log messages without having to change the log level for the entire test suite.
22
23
 
23
- You can use the `include?` method on the log device to determine if specific log entries were made. This would be the equivalent code to the above rspec test, but without the brittleness of mocking method calls:
24
+ You can use the `include?` method on the log device to determine if specific log entries were made. This would be the equivalent code to the above RSpec test, but without the brittleness of mocking method calls:
24
25
 
25
26
  ```ruby
26
27
  Lumberjack::CaptureDevice.capture(Rails.logger) do |logs|
@@ -44,12 +45,47 @@ assert(logs.include?(level: :info, message: "Something happened"))
44
45
  ```
45
46
 
46
47
 
47
- You can filter the logs on level, message, and tags. The level option can take either a label (i.e. `:warn`) or a constant (i.e. `Logger::WARN`). The message filter can be either an exact string, or a regular expression or any matcher supported by your test library. The tags argument can match tags with a Hash mapping tag names to the matcher.
48
+ You can filter the logs on level, message, and tags.
49
+
50
+ - The level option can take either a label (i.e. `:warn`) or a constant (i.e. `Logger::WARN`).
51
+ - The message filter can be either an exact string or a regular expression, or any matcher supported by your test library.
52
+ - 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.
48
53
 
49
54
  ```ruby
50
55
  expect(logs).to include(level: :info, message: /something/i)
51
56
  expect(logs).to include(level: Logger::INFO, tags: {foo: "bar"})
52
57
  expect(logs).to include(tags: {foo: anything, count: {one: 1}})
58
+ expect(logs).to include(tags: {foo: anything, "count.one" => 1})
59
+ ```
60
+
61
+ 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`.
62
+
63
+ ## Installation
64
+
65
+ Add this line to your application's Gemfile:
66
+
67
+ ```ruby
68
+ gem 'lumberjack_capture_device'
53
69
  ```
54
70
 
55
- You can also use the `Lumberjack::CaptureDevice#extract` method with the same arguments as used by `include?` to grab all lines that match the filters. And finally, you can access all the log entries with `Lumberjack::CaptureDevice#buffer`.
71
+ And then execute:
72
+
73
+ ```bash
74
+ $ bundle install
75
+ ```
76
+
77
+ Or install it yourself as:
78
+
79
+ ```bash
80
+ $ gem install lumberjack_capture_device
81
+ ```
82
+
83
+ ## Contributing
84
+
85
+ Open a pull request on GitHub.
86
+
87
+ Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
88
+
89
+ ## License
90
+
91
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.1.1
@@ -14,6 +14,20 @@ module Lumberjack
14
14
  # the logger. In addition, all formatters will be removed and the log level will be set
15
15
  # to debug. The device being written to be both yielded to the block as well as returned
16
16
  # by the method call.
17
+ #
18
+ # @param logger [Lumberjack::Logger] The logger to capture entries from.
19
+ # @yield [device] The block to execute while capturing log entries.
20
+ # @return [Lumberjack::CaptureDevice] The device that captured the log entries.
21
+ # @yieldparam device [Lumberjack::CaptureDevice] The device that will capture the log entries.
22
+ # @example
23
+ # Lumberjack::CaptureDevice.capture(logger) do |logs|
24
+ # logger.info("This will be captured")
25
+ # expect(logs).to include(level: :info, message: "This will be captured")
26
+ # end
27
+ #
28
+ # @example
29
+ # logs = Lumberjack::CaptureDevice.capture(logger) { logger.info("This will be captured") }
30
+ # expect(logs).to include(level: :info, message: "This will be captured")
17
31
  def capture(logger)
18
32
  device = new
19
33
  save_device = logger.device
@@ -64,14 +78,32 @@ module Lumberjack
64
78
  # ```
65
79
  # logs.include(level: :warn, message: /something happened/, tags: {duration: instance_of(Float)})
66
80
  # ```
81
+ #
82
+ # @param args [Hash] The filters to apply to the captured entries.
83
+ # @option args [String, Regexp] :message The message to match against the log entries.
84
+ # @option args [String, Symbol, Integer] :level The log level to match against the log entries.
85
+ # @option args [Hash] :tags A hash of tag names to values to match against the log entries. The tags
86
+ # will match nested tags using dot notation (e.g. `foo.bar` will match a tag with the structure
87
+ # `{foo: {bar: "value"}}`).
88
+ # @return [Boolean] True if any entries match the specified filters, false otherwise.
67
89
  def include?(args)
68
90
  !extract(**args.merge(limit: 1)).empty?
69
91
  end
70
92
 
71
93
  # Return all the captured entries that match the specified filters. These filters are
72
94
  # the same as described in the `include?` method.
95
+ #
96
+ # @param message [String, Regexp, nil] The message to match against the log entries.
97
+ # @param level [String, Symbol, Integer, nil] The log level to match against the log entries.
98
+ # @param tags [Hash, nil] A hash of tag names to values to match against the log entries. The tags
99
+ # will match nested tags using dot notation (e.g. `foo.bar` will match a tag with the structure
100
+ # `{foo: {bar: "value"}}`).
101
+ # @param limit [Integer, nil] The maximum number of entries to return. If nil, all matching entries
102
+ # will be returned.
103
+ # @return [Array<Lumberjack::Entry>] An array of log entries that match the specified filters.
73
104
  def extract(message: nil, level: nil, tags: nil, limit: nil)
74
105
  matches = []
106
+
75
107
  if level
76
108
  # Normalize the level filter to numeric values.
77
109
  level = (level.is_a?(Integer) ? level : Lumberjack::Severity.label_to_level(level))
@@ -82,13 +114,33 @@ module Lumberjack
82
114
  break if limit && matches.size >= limit
83
115
  end
84
116
  end
117
+
85
118
  matches
86
119
  end
87
120
 
121
+ def inspect
122
+ message = +"<##{self.class.name} #{@buffer.size} #{(@buffer.size == 1) ? "entry" : "entries"} captured:"
123
+ @buffer.each do |entry|
124
+ message << "\n #{formatted_entry(entry)}"
125
+ end
126
+ message << "\n>"
127
+ message
128
+ end
129
+
88
130
  private
89
131
 
90
132
  def matched?(entry, message_filter, level_filter, tags_filter)
91
- match?(entry.message, message_filter) && match?(entry.severity, level_filter) && match_tags?(entry.tags, tags_filter)
133
+ return false unless match?(entry.message, message_filter)
134
+ return false unless match?(entry.severity, level_filter)
135
+
136
+ if tags_filter.is_a?(Hash)
137
+ tags_filter = deep_stringify_keys(Lumberjack::Utils.expand_tags(tags_filter))
138
+ end
139
+ tags = deep_stringify_keys(Lumberjack::Utils.expand_tags(entry.tags))
140
+
141
+ return false unless match_tags?(tags, tags_filter)
142
+
143
+ true
92
144
  end
93
145
 
94
146
  def match?(value, filter)
@@ -99,21 +151,51 @@ module Lumberjack
99
151
  def match_tags?(tags, filter)
100
152
  return true unless filter
101
153
  return false unless tags
154
+
102
155
  filter.all? do |name, value_filter|
103
156
  name = name.to_s
104
157
  tag_values = tags[name]
105
158
  if tag_values.is_a?(Hash)
106
159
  if value_filter.is_a?(Hash)
107
- match_tags?(Lumberjack::Tags.stringify_keys(tag_values), value_filter)
160
+ match_tags?(tag_values, value_filter)
108
161
  else
109
162
  false
110
163
  end
111
- elsif tag_values || tags.include?(name)
164
+ elsif value_filter.nil? || (value_filter.is_a?(Enumerable) && value_filter.empty?)
165
+ tag_values.nil? || (tag_values.is_a?(Array) && tag_values.empty?)
166
+ elsif tags.include?(name)
112
167
  match?(tag_values, value_filter)
113
168
  else
114
169
  false
115
170
  end
116
171
  end
117
172
  end
173
+
174
+ def deep_stringify_keys(hash)
175
+ if hash.is_a?(Hash)
176
+ hash.each_with_object({}) do |(key, value), result|
177
+ new_key = key.to_s
178
+ new_value = deep_stringify_keys(value)
179
+ result[new_key] = new_value
180
+ end
181
+ elsif hash.is_a?(Enumerable)
182
+ hash.collect { |item| deep_stringify_keys(item) }
183
+ else
184
+ hash
185
+ end
186
+ end
187
+
188
+ def formatted_entry(entry)
189
+ timestamp = entry.time.strftime("%Y-%m-%d %H:%M:%S")
190
+ formatted = +"#{timestamp} #{entry.severity_label}: #{entry.message}"
191
+ formatted << "\n progname: #{entry.progname}" if entry.progname.to_s != ""
192
+ formatted << "\n pid: #{entry.pid}" if entry.pid
193
+ if entry.tags && !entry.tags.empty?
194
+ Lumberjack::Utils.flatten_tags(entry.tags).to_a.sort_by(&:first).each do |name, value|
195
+ formatted << "\n #{name}: #{value}"
196
+ end
197
+ end
198
+ formatted
199
+ end
118
200
  end
119
201
  end
@@ -8,6 +8,12 @@ Gem::Specification.new do |spec|
8
8
  spec.homepage = "https://github.com/bdurand/lumberjack_capture_device"
9
9
  spec.license = "MIT"
10
10
 
11
+ spec.metadata = {
12
+ "homepage_uri" => spec.homepage,
13
+ "source_code_uri" => spec.homepage,
14
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
15
+ }
16
+
11
17
  # Specify which files should be added to the gem when it is released.
12
18
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
13
19
  ignore_files = %w[
@@ -25,8 +31,8 @@ Gem::Specification.new do |spec|
25
31
 
26
32
  spec.require_paths = ["lib"]
27
33
 
28
- spec.required_ruby_version = ">= 2.4.0"
34
+ spec.required_ruby_version = ">= 2.5"
29
35
 
30
- spec.add_dependency "lumberjack", ">=1.1.0"
36
+ spec.add_dependency "lumberjack", ">=1.3.3"
31
37
  spec.add_development_dependency "bundler"
32
38
  end
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.0.1
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-25 00:00:00.000000000 Z
11
+ date: 2025-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lumberjack
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.1.0
19
+ version: 1.3.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.1.0
26
+ version: 1.3.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- description:
41
+ description:
42
42
  email:
43
43
  - bbdurand@gmail.com
44
44
  executables: []
@@ -54,8 +54,11 @@ files:
54
54
  homepage: https://github.com/bdurand/lumberjack_capture_device
55
55
  licenses:
56
56
  - MIT
57
- metadata: {}
58
- post_install_message:
57
+ metadata:
58
+ homepage_uri: https://github.com/bdurand/lumberjack_capture_device
59
+ source_code_uri: https://github.com/bdurand/lumberjack_capture_device
60
+ changelog_uri: https://github.com/bdurand/lumberjack_capture_device/blob/main/CHANGELOG.md
61
+ post_install_message:
59
62
  rdoc_options: []
60
63
  require_paths:
61
64
  - lib
@@ -63,15 +66,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
63
66
  requirements:
64
67
  - - ">="
65
68
  - !ruby/object:Gem::Version
66
- version: 2.4.0
69
+ version: '2.5'
67
70
  required_rubygems_version: !ruby/object:Gem::Requirement
68
71
  requirements:
69
72
  - - ">="
70
73
  - !ruby/object:Gem::Version
71
74
  version: '0'
72
75
  requirements: []
73
- rubygems_version: 3.0.3
74
- signing_key:
76
+ rubygems_version: 3.4.10
77
+ signing_key:
75
78
  specification_version: 4
76
79
  summary: Testing device for the lumberjack gem that can be used for asserting messages
77
80
  have been logged in a test suite.