lumberjack_capture_device 1.2.2 → 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 +4 -4
 - data/CHANGELOG.md +15 -0
 - data/README.md +26 -6
 - data/VERSION +1 -1
 - data/lib/lumberjack/capture_device/entry_score.rb +98 -42
 - data/lib/lumberjack/capture_device/include_log_entry_matcher.rb +95 -35
 - data/lib/lumberjack/capture_device/rspec.rb +59 -2
 - data/lib/lumberjack/capture_device.rb +146 -205
 - data/lumberjack_capture_device.gemspec +2 -2
 - metadata +6 -10
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: be90765c9b26b0ca9c501504e83c2a58cf1f2ce74edffe0b5e3d8e2515194277
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: '0825d9fa6621df4c5cdea31de794e1c8f263249d3f42f0e17945806ccb7f5f69'
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 5423feff017c2b44974d7e75268c39388353165128632d0b7533619b5271ff17aeb1e9250d7ceda017de56f272baf0bda9b6e2a180a98397b13520b128f5449b
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 3bbdb6b6ca43683a3ec20ead94fa93e557b33194c4ee58d5f36c478e50b01e7ff5abed29a3001fce885f47f304f62d3bcbeec5cddd00708401f377db7d775bf1
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -4,6 +4,21 @@ 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 
     | 
    
         
            +
             
     | 
| 
       7 
22 
     | 
    
         
             
            ## 1.2.2
         
     | 
| 
       8 
23 
     | 
    
         | 
| 
       9 
24 
     | 
    
         
             
            ### 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  
     | 
| 
      
 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  
     | 
| 
      
 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,  
     | 
| 
       56 
     | 
    
         
            -
            expect(logs).to include( 
     | 
| 
       57 
     | 
    
         
            -
            expect(logs).to include( 
     | 
| 
      
 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.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 
     | 
    
         
            -
                 
     | 
| 
      
 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  
     | 
| 
       23 
     | 
    
         
            -
                  if  
     | 
| 
       24 
     | 
    
         
            -
                     
     | 
| 
       25 
     | 
    
         
            -
                      1.0  # Exact  
     | 
| 
      
 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 
     | 
    
         
            -
                       
     | 
| 
      
 36 
     | 
    
         
            +
                      severity_proximity_score(entry.severity, severity_filter)  # Partial severity match
         
     | 
| 
       28 
37 
     | 
    
         
             
                    end
         
     | 
| 
       29 
     | 
    
         
            -
                    scores <<  
     | 
| 
      
 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  
     | 
| 
       41 
     | 
    
         
            -
                  if  
     | 
| 
       42 
     | 
    
         
            -
                     
     | 
| 
       43 
     | 
    
         
            -
                    scores <<  
     | 
| 
      
 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  
     | 
| 
       96 
     | 
    
         
            -
                 
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
      
 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  
     | 
| 
       107 
     | 
    
         
            -
                 
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
       111 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
       114 
     | 
    
         
            -
                  return 0.0 if  
     | 
| 
      
 137 
     | 
    
         
            +
                  total_attribute_filters = count_attribute_filters(attributes_filter)
         
     | 
| 
      
 138 
     | 
    
         
            +
                  return 0.0 if total_attribute_filters == 0
         
     | 
| 
       115 
139 
     | 
    
         | 
| 
       116 
     | 
    
         
            -
                   
     | 
| 
       117 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
                 
     | 
| 
       175 
     | 
    
         
            -
             
     | 
| 
      
 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 =  
     | 
| 
      
 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 
     | 
    
         
            -
                 
     | 
| 
       186 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
      
 234 
     | 
    
         
            +
                  attributes_filter.each do |name, value_filter|
         
     | 
| 
       189 
235 
     | 
    
         
             
                    name = name.to_s
         
     | 
| 
       190 
     | 
    
         
            -
                     
     | 
| 
      
 236 
     | 
    
         
            +
                    attribute_values = attributes[name]
         
     | 
| 
       191 
237 
     | 
    
         | 
| 
       192 
     | 
    
         
            -
                    if value_filter.is_a?(Hash) &&  
     | 
| 
       193 
     | 
    
         
            -
                      count =  
     | 
| 
       194 
     | 
    
         
            -
                    elsif  
     | 
| 
      
 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,91 +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 
     | 
    
         
            -
                @ 
     | 
| 
      
 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 
     | 
    
         
            -
                @ 
     | 
| 
       12 
     | 
    
         
            -
                return false unless  
     | 
| 
      
 19 
     | 
    
         
            +
                @logger = actual
         
     | 
| 
      
 20 
     | 
    
         
            +
                return false unless valid_logger?
         
     | 
| 
       13 
21 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                @ 
     | 
| 
      
 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  
     | 
| 
       19 
     | 
    
         
            -
                  formatted_failure_message(@ 
     | 
| 
      
 30 
     | 
    
         
            +
                if valid_logger?
         
     | 
| 
      
 31 
     | 
    
         
            +
                  formatted_failure_message(@logger, @expected_hash)
         
     | 
| 
       20 
32 
     | 
    
         
             
                else
         
     | 
| 
       21 
     | 
    
         
            -
                  wrong_object_type_message(@ 
     | 
| 
      
 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  
     | 
| 
       27 
     | 
    
         
            -
                  formatted_negated_failure_message(@ 
     | 
| 
      
 41 
     | 
    
         
            +
                if valid_logger?
         
     | 
| 
      
 42 
     | 
    
         
            +
                  formatted_negated_failure_message(@logger, @expected_hash)
         
     | 
| 
       28 
43 
     | 
    
         
             
                else
         
     | 
| 
       29 
     | 
    
         
            -
                  wrong_object_type_message(@ 
     | 
| 
      
 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 
     | 
    
         
            -
               
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
               
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
               
     | 
| 
      
 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
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
       48 
96 
     | 
    
         
             
                message = +"expected logs to include entry:\n" \
         
     | 
| 
       49 
     | 
    
         
            -
                  "#{Lumberjack:: 
     | 
| 
       50 
     | 
    
         
            -
                  "Captured #{captured_logger.length} log #{(captured_logger.length == 1) ? "entry" : "entries"}"
         
     | 
| 
      
 97 
     | 
    
         
            +
                  "#{Lumberjack::Device::Test.formatted_expectation(expected_hash, indent: 2)}"
         
     | 
| 
       51 
98 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
                 
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
                   
     | 
| 
       55 
     | 
    
         
            -
                     
     | 
| 
       56 
     | 
    
         
            -
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                closest_match = device.closest_match(**expected_hash)
         
     | 
| 
      
 100 
     | 
    
         
            +
                if closest_match
         
     | 
| 
      
 101 
     | 
    
         
            +
                  message << "\n\nClosest match found:" \
         
     | 
| 
      
 102 
     | 
    
         
            +
                    "#{Lumberjack::Device::Test.formatted_expectation(closest_match, indent: 2)}"
         
     | 
| 
       57 
103 
     | 
    
         
             
                end
         
     | 
| 
       58 
104 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                 
     | 
| 
       60 
     | 
    
         
            -
                 
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
      
 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
         
     | 
| 
       63 
113 
     | 
    
         
             
                end
         
     | 
| 
       64 
114 
     | 
    
         | 
| 
       65 
115 
     | 
    
         
             
                message
         
     | 
| 
       66 
116 
     | 
    
         
             
              end
         
     | 
| 
       67 
117 
     | 
    
         | 
| 
       68 
     | 
    
         
            -
               
     | 
| 
      
 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
         
     | 
| 
       69 
125 
     | 
    
         
             
                message = "expected logs not to include entry:\n" \
         
     | 
| 
       70 
     | 
    
         
            -
                  "#{Lumberjack:: 
     | 
| 
      
 126 
     | 
    
         
            +
                  "#{Lumberjack::Device::Test.formatted_expectation(expected_hash, indent: 2)}"
         
     | 
| 
       71 
127 
     | 
    
         | 
| 
       72 
     | 
    
         
            -
                match =  
     | 
| 
      
 128 
     | 
    
         
            +
                match = device.match(**expected_hash)
         
     | 
| 
       73 
129 
     | 
    
         
             
                if match
         
     | 
| 
       74 
130 
     | 
    
         
             
                  message = "#{message}\n\nFound entry:\n" \
         
     | 
| 
       75 
     | 
    
         
            -
                    "#{Lumberjack:: 
     | 
| 
      
 131 
     | 
    
         
            +
                    "#{Lumberjack::Device::Test.formatted_expectation(match, indent: 2)}"
         
     | 
| 
       76 
132 
     | 
    
         
             
                end
         
     | 
| 
       77 
133 
     | 
    
         | 
| 
       78 
134 
     | 
    
         
             
                message
         
     | 
| 
       79 
135 
     | 
    
         
             
              end
         
     | 
| 
       80 
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.
         
     | 
| 
       81 
141 
     | 
    
         
             
              def expectation_description(expected_hash)
         
     | 
| 
       82 
142 
     | 
    
         
             
                info = []
         
     | 
| 
       83 
     | 
    
         
            -
                info << " 
     | 
| 
      
 143 
     | 
    
         
            +
                info << "severity: #{expected_hash[:severity].inspect}" unless expected_hash[:severity].nil?
         
     | 
| 
       84 
144 
     | 
    
         
             
                info << "message: #{expected_hash[:message].inspect}" unless expected_hash[:message].nil?
         
     | 
| 
       85 
145 
     | 
    
         
             
                info << "progname: #{expected_hash[:progname].inspect}" unless expected_hash[:progname].nil?
         
     | 
| 
       86 
     | 
    
         
            -
                if expected_hash[: 
     | 
| 
       87 
     | 
    
         
            -
                   
     | 
| 
       88 
     | 
    
         
            -
                   
     | 
| 
       89 
     | 
    
         
            -
                  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}"
         
     | 
| 
       90 
150 
     | 
    
         
             
                end
         
     | 
| 
       91 
151 
     | 
    
         
             
                info.join(", ")
         
     | 
| 
       92 
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 
     | 
    
         
            -
               
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
      
 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,12 +5,13 @@ 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 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                require_relative "capture_device/include_log_entry_matcher"
         
     | 
| 
       10 
12 
     | 
    
         | 
| 
       11 
13 
     | 
    
         
             
                include Enumerable
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                attr_reader :buffer
         
     | 
| 
       14 
15 
     | 
    
         
             
                class << self
         
     | 
| 
       15 
16 
     | 
    
         
             
                  # Capture the entries written by the logger within a block. Within the block all log
         
     | 
| 
       16 
17 
     | 
    
         
             
                  # entries will be written to a CaptureDevice rather than to the normal output for
         
     | 
| 
         @@ -19,169 +20,153 @@ module Lumberjack 
     | 
|
| 
       19 
20 
     | 
    
         
             
                  # by the method call.
         
     | 
| 
       20 
21 
     | 
    
         
             
                  #
         
     | 
| 
       21 
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.
         
     | 
| 
       22 
26 
     | 
    
         
             
                  # @yield [device] The block to execute while capturing log entries.
         
     | 
| 
       23 
27 
     | 
    
         
             
                  # @return [Lumberjack::CaptureDevice] The device that captured the log entries.
         
     | 
| 
       24 
28 
     | 
    
         
             
                  # @yieldparam device [Lumberjack::CaptureDevice] The device that will capture the log entries.
         
     | 
| 
       25 
29 
     | 
    
         
             
                  # @example
         
     | 
| 
       26 
30 
     | 
    
         
             
                  #   Lumberjack::CaptureDevice.capture(logger) do |logs|
         
     | 
| 
       27 
31 
     | 
    
         
             
                  #     logger.info("This will be captured")
         
     | 
| 
       28 
     | 
    
         
            -
                  #     expect(logs).to include( 
     | 
| 
      
 32 
     | 
    
         
            +
                  #     expect(logs).to include(severity: :info, message: "This will be captured")
         
     | 
| 
       29 
33 
     | 
    
         
             
                  #   end
         
     | 
| 
       30 
34 
     | 
    
         
             
                  #
         
     | 
| 
       31 
35 
     | 
    
         
             
                  # @example
         
     | 
| 
       32 
36 
     | 
    
         
             
                  #   logs = Lumberjack::CaptureDevice.capture(logger) { logger.info("This will be captured") }
         
     | 
| 
       33 
     | 
    
         
            -
                  #   expect(logs).to include( 
     | 
| 
       34 
     | 
    
         
            -
                  def capture(logger)
         
     | 
| 
       35 
     | 
    
         
            -
                    device = new
         
     | 
| 
      
 37 
     | 
    
         
            +
                  #   expect(logs).to include(severity: :info, message: "This will be captured")
         
     | 
| 
      
 38 
     | 
    
         
            +
                  def capture(logger, write_to_original: true)
         
     | 
| 
       36 
39 
     | 
    
         
             
                    save_device = logger.device
         
     | 
| 
       37 
40 
     | 
    
         
             
                    save_level = logger.level
         
     | 
| 
       38 
     | 
    
         
            -
                     
     | 
| 
      
 41 
     | 
    
         
            +
                    device = new(underlying_device: save_device)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
       39 
43 
     | 
    
         
             
                    begin
         
     | 
| 
       40 
44 
     | 
    
         
             
                      logger.device = device
         
     | 
| 
       41 
45 
     | 
    
         
             
                      logger.level = :debug
         
     | 
| 
       42 
     | 
    
         
            -
                      logger.formatter = Lumberjack::Formatter.empty
         
     | 
| 
       43 
46 
     | 
    
         
             
                      yield device
         
     | 
| 
       44 
47 
     | 
    
         
             
                    ensure
         
     | 
| 
       45 
48 
     | 
    
         
             
                      logger.device = save_device
         
     | 
| 
       46 
49 
     | 
    
         
             
                      logger.level = save_level
         
     | 
| 
       47 
     | 
    
         
            -
                       
     | 
| 
      
 50 
     | 
    
         
            +
                      device.write_to_underlying_device if write_to_original
         
     | 
| 
       48 
51 
     | 
    
         
             
                    end
         
     | 
| 
       49 
     | 
    
         
            -
                    device
         
     | 
| 
       50 
     | 
    
         
            -
                  end
         
     | 
| 
       51 
52 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       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
         
     | 
| 
      
 53 
     | 
    
         
            +
                    device
         
     | 
| 
       68 
54 
     | 
    
         
             
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
       69 
56 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
       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
         
     | 
| 
      
 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
         
     | 
| 
       83 
61 
     | 
    
         | 
| 
       84 
     | 
    
         
            -
             
     | 
| 
      
 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))
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
       85 
69 
     | 
    
         | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
             
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
      
 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
         
     | 
| 
       100 
93 
     | 
    
         
             
                  end
         
     | 
| 
       101 
     | 
    
         
            -
                end
         
     | 
| 
       102 
94 
     | 
    
         | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
                  @buffer = []
         
     | 
| 
       105 
     | 
    
         
            -
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
                  matcher = LogEntryMatcher.new(message: message, severity: severity, attributes: attributes, progname: progname)
         
     | 
| 
       106 
96 
     | 
    
         | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
             
     | 
| 
      
 97 
     | 
    
         
            +
                  entries.each do |entry|
         
     | 
| 
      
 98 
     | 
    
         
            +
                    matched << entry if matcher.match?(entry)
         
     | 
| 
      
 99 
     | 
    
         
            +
                    break if limit && matched.size >= limit
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
       110 
101 
     | 
    
         | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
       112 
     | 
    
         
            -
                def clear
         
     | 
| 
       113 
     | 
    
         
            -
                  @buffer.clear
         
     | 
| 
      
 102 
     | 
    
         
            +
                  matched
         
     | 
| 
       114 
103 
     | 
    
         
             
                end
         
     | 
| 
       115 
104 
     | 
    
         | 
| 
       116 
     | 
    
         
            -
                # Return true if the captured log entries match the specified level, message, and  
     | 
| 
      
 105 
     | 
    
         
            +
                # Return true if the captured log entries match the specified level, message, and attributes.
         
     | 
| 
       117 
106 
     | 
    
         
             
                #
         
     | 
| 
       118 
     | 
    
         
            -
                # For level, you can  
     | 
| 
      
 107 
     | 
    
         
            +
                # For level, you can specify either a numeric constant (i.e. `Logger::WARN`) or a symbol
         
     | 
| 
       119 
108 
     | 
    
         
             
                # (i.e. `:warn`).
         
     | 
| 
       120 
109 
     | 
    
         
             
                #
         
     | 
| 
       121 
110 
     | 
    
         
             
                # For message you can specify a string to perform an exact match or a regular expression
         
     | 
| 
       122 
111 
     | 
    
         
             
                # to perform a partial or pattern match. You can also supply any matcher value available
         
     | 
| 
       123 
112 
     | 
    
         
             
                # in your test library (i.e. in rspec you could use `anything` or `instance_of(Error)`, etc.).
         
     | 
| 
       124 
113 
     | 
    
         
             
                #
         
     | 
| 
       125 
     | 
    
         
            -
                # For  
     | 
| 
       126 
     | 
    
         
            -
                # regular expression or matchers as the values here as well.  
     | 
| 
       127 
     | 
    
         
            -
                # nested  
     | 
| 
      
 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.
         
     | 
| 
       128 
117 
     | 
    
         
             
                #
         
     | 
| 
       129 
     | 
    
         
            -
                #  
     | 
| 
      
 118 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 119 
     | 
    
         
            +
                #   logs.include?(level: :warn, message: /something happened/, attributes: {user: "john"})
         
     | 
| 
       130 
120 
     | 
    
         
             
                #
         
     | 
| 
       131 
     | 
    
         
            -
                #  
     | 
| 
       132 
     | 
    
         
            -
                #  
     | 
| 
       133 
     | 
    
         
            -
                #  
     | 
| 
       134 
     | 
    
         
            -
                #
         
     | 
| 
       135 
     | 
    
         
            -
                #  
     | 
| 
       136 
     | 
    
         
            -
                #  
     | 
| 
       137 
     | 
    
         
            -
                # @option  
     | 
| 
       138 
     | 
    
         
            -
                # @option  
     | 
| 
       139 
     | 
    
         
            -
                # 
     | 
| 
       140 
     | 
    
         
            -
                #   `{foo: {bar: "value"}}`).
         
     | 
| 
       141 
     | 
    
         
            -
                # @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.
         
     | 
| 
       142 
130 
     | 
    
         
             
                # @return [Boolean] True if any entries match the specified filters, false otherwise.
         
     | 
| 
       143 
     | 
    
         
            -
                def include?( 
     | 
| 
       144 
     | 
    
         
            -
                   
     | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
       146 
     | 
    
         
            -
             
     | 
| 
       147 
     | 
    
         
            -
                # Return all the captured entries that match the specified filters. These filters are
         
     | 
| 
       148 
     | 
    
         
            -
                # the same as described in the `include?` method.
         
     | 
| 
       149 
     | 
    
         
            -
                #
         
     | 
| 
       150 
     | 
    
         
            -
                # @param message [String, Regexp, nil] The message to match against the log entries.
         
     | 
| 
       151 
     | 
    
         
            -
                # @param level [String, Symbol, Integer, nil] The log level to match against the log entries.
         
     | 
| 
       152 
     | 
    
         
            -
                # @param tags [Hash, nil] A hash of tag names to values to match against the log entries. The tags
         
     | 
| 
       153 
     | 
    
         
            -
                #   will match nested tags using dot notation (e.g. `foo.bar` will match a tag with the structure
         
     | 
| 
       154 
     | 
    
         
            -
                #   `{foo: {bar: "value"}}`).
         
     | 
| 
       155 
     | 
    
         
            -
                # @param limit [Integer, nil] The maximum number of entries to return. If nil, all matching entries
         
     | 
| 
       156 
     | 
    
         
            -
                #   will be returned.
         
     | 
| 
       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)
         
     | 
| 
       159 
     | 
    
         
            -
                  matches = []
         
     | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
       161 
     | 
    
         
            -
                  if level
         
     | 
| 
       162 
     | 
    
         
            -
                    # Normalize the level filter to numeric values.
         
     | 
| 
       163 
     | 
    
         
            -
                    level = (level.is_a?(Integer) ? level : Lumberjack::Severity.label_to_level(level))
         
     | 
| 
      
 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.")
         
     | 
| 
       164 
134 
     | 
    
         
             
                  end
         
     | 
| 
       165 
135 
     | 
    
         | 
| 
       166 
     | 
    
         
            -
                   
     | 
| 
       167 
     | 
    
         
            -
                     
     | 
| 
       168 
     | 
    
         
            -
                      matches << entry
         
     | 
| 
       169 
     | 
    
         
            -
                      break if limit && matches.size >= limit
         
     | 
| 
       170 
     | 
    
         
            -
                    end
         
     | 
| 
      
 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.")
         
     | 
| 
       171 
138 
     | 
    
         
             
                  end
         
     | 
| 
       172 
139 
     | 
    
         | 
| 
       173 
     | 
    
         
            -
                   
     | 
| 
      
 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)
         
     | 
| 
       174 
148 
     | 
    
         
             
                end
         
     | 
| 
       175 
149 
     | 
    
         | 
| 
       176 
     | 
    
         
            -
                # Return the first entry that matches the  
     | 
| 
      
 150 
     | 
    
         
            +
                # Return the first captured entry that matches the filters.
         
     | 
| 
       177 
151 
     | 
    
         
             
                #
         
     | 
| 
       178 
152 
     | 
    
         
             
                # @param message [String, Regexp, nil] The message to match against the log entries.
         
     | 
| 
       179 
     | 
    
         
            -
                # @param  
     | 
| 
       180 
     | 
    
         
            -
                # @param  
     | 
| 
      
 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"}}+).
         
     | 
| 
       181 
157 
     | 
    
         
             
                # @param progname [String, nil] The program name to match against the log entries.
         
     | 
| 
       182 
     | 
    
         
            -
                # @ 
     | 
| 
       183 
     | 
    
         
            -
                 
     | 
| 
       184 
     | 
    
         
            -
             
     | 
| 
      
 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.")
         
     | 
| 
      
 164 
     | 
    
         
            +
                  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.")
         
     | 
| 
      
 167 
     | 
    
         
            +
                  end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                  super(message: message, severity: severity || level, attributes: attributes || tags, progname: progname)
         
     | 
| 
       185 
170 
     | 
    
         
             
                end
         
     | 
| 
       186 
171 
     | 
    
         | 
| 
       187 
172 
     | 
    
         
             
                # Return the log entry that most closely matches the specified filters. This method
         
     | 
| 
         @@ -190,119 +175,75 @@ module Lumberjack 
     | 
|
| 
       190 
175 
     | 
    
         
             
                # they match. Returns nil if no entry meets the minimum matching criteria.
         
     | 
| 
       191 
176 
     | 
    
         
             
                #
         
     | 
| 
       192 
177 
     | 
    
         
             
                # @param message [String, Regexp, nil] The message to match against the log entries.
         
     | 
| 
       193 
     | 
    
         
            -
                # @param  
     | 
| 
       194 
     | 
    
         
            -
                # @param  
     | 
| 
      
 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.
         
     | 
| 
       195 
180 
     | 
    
         
             
                # @param progname [String, nil] The program name to match against the log entries.
         
     | 
| 
      
 181 
     | 
    
         
            +
                # @param level [String, Symbol, Integer, nil] Alias for the `severity` parameter.
         
     | 
| 
      
 182 
     | 
    
         
            +
                # @param tags [Hash, nil] Alias for the `attributes` parameter.
         
     | 
| 
       196 
183 
     | 
    
         
             
                # @return [Lumberjack::LogEntry, nil] The log entry that most closely matches the filters, or nil if no entry meets minimum criteria.
         
     | 
| 
       197 
     | 
    
         
            -
                def closest_match(message: nil,  
     | 
| 
       198 
     | 
    
         
            -
                  return nil if  
     | 
| 
       199 
     | 
    
         
            -
             
     | 
| 
       200 
     | 
    
         
            -
                  exact_match = match(message: message, level: level, tags: tags, progname: progname)
         
     | 
| 
       201 
     | 
    
         
            -
                  return exact_match if exact_match
         
     | 
| 
      
 184 
     | 
    
         
            +
                def closest_match(message: nil, severity: nil, attributes: nil, progname: nil, level: nil, tags: nil)
         
     | 
| 
      
 185 
     | 
    
         
            +
                  return nil if length == 0
         
     | 
| 
       202 
186 
     | 
    
         | 
| 
       203 
     | 
    
         
            -
                   
     | 
| 
       204 
     | 
    
         
            -
             
     | 
| 
       205 
     | 
    
         
            -
                    level = (level.is_a?(Integer) ? level : Lumberjack::Severity.label_to_level(level))
         
     | 
| 
      
 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.")
         
     | 
| 
       206 
189 
     | 
    
         
             
                  end
         
     | 
| 
       207 
     | 
    
         
            -
             
     | 
| 
       208 
     | 
    
         
            -
             
     | 
| 
       209 
     | 
    
         
            -
                  best_score = 0
         
     | 
| 
       210 
     | 
    
         
            -
             
     | 
| 
       211 
     | 
    
         
            -
                  @buffer.each do |entry|
         
     | 
| 
       212 
     | 
    
         
            -
                    score = Lumberjack::CaptureDevice::EntryScore.calculate_match_score(entry, message, level, tags, progname)
         
     | 
| 
       213 
     | 
    
         
            -
                    if score > best_score && score >= Lumberjack::CaptureDevice::EntryScore::MIN_SCORE_THRESHOLD
         
     | 
| 
       214 
     | 
    
         
            -
                      best_score = score
         
     | 
| 
       215 
     | 
    
         
            -
                      best_entry = entry
         
     | 
| 
       216 
     | 
    
         
            -
                    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.")
         
     | 
| 
       217 
192 
     | 
    
         
             
                  end
         
     | 
| 
       218 
193 
     | 
    
         | 
| 
       219 
     | 
    
         
            -
                   
     | 
| 
      
 194 
     | 
    
         
            +
                  super(
         
     | 
| 
      
 195 
     | 
    
         
            +
                    message: message,
         
     | 
| 
      
 196 
     | 
    
         
            +
                    severity: severity || level,
         
     | 
| 
      
 197 
     | 
    
         
            +
                    attributes: attributes || tags,
         
     | 
| 
      
 198 
     | 
    
         
            +
                    progname: progname
         
     | 
| 
      
 199 
     | 
    
         
            +
                  )
         
     | 
| 
       220 
200 
     | 
    
         
             
                end
         
     | 
| 
       221 
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.
         
     | 
| 
       222 
212 
     | 
    
         
             
                def inspect
         
     | 
| 
       223 
     | 
    
         
            -
                  message = +"<##{self.class.name} #{ 
     | 
| 
       224 
     | 
    
         
            -
                   
     | 
| 
       225 
     | 
    
         
            -
             
     | 
| 
      
 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"
         
     | 
| 
       226 
219 
     | 
    
         
             
                  end
         
     | 
| 
       227 
     | 
    
         
            -
                  message << " 
     | 
| 
      
 220 
     | 
    
         
            +
                  message << ">"
         
     | 
| 
       228 
221 
     | 
    
         
             
                  message
         
     | 
| 
       229 
222 
     | 
    
         
             
                end
         
     | 
| 
       230 
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.
         
     | 
| 
       231 
227 
     | 
    
         
             
                def to_s
         
     | 
| 
       232 
     | 
    
         
            -
                  "<##{self.class.name} #{ 
     | 
| 
      
 228 
     | 
    
         
            +
                  "<##{self.class.name} #{length} #{(length == 1) ? "entry" : "entries"} captured>"
         
     | 
| 
       233 
229 
     | 
    
         
             
                end
         
     | 
| 
       234 
230 
     | 
    
         | 
| 
      
 231 
     | 
    
         
            +
                # Return the number of captured log entries.
         
     | 
| 
      
 232 
     | 
    
         
            +
                #
         
     | 
| 
      
 233 
     | 
    
         
            +
                # @return [Integer] The number of captured entries.
         
     | 
| 
       235 
234 
     | 
    
         
             
                def length
         
     | 
| 
       236 
235 
     | 
    
         
             
                  @buffer.length
         
     | 
| 
       237 
236 
     | 
    
         
             
                end
         
     | 
| 
       238 
237 
     | 
    
         | 
| 
       239 
238 
     | 
    
         
             
                alias_method :size, :length
         
     | 
| 
       240 
239 
     | 
    
         | 
| 
      
 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).
         
     | 
| 
       241 
245 
     | 
    
         
             
                def each(&block)
         
     | 
| 
       242 
246 
     | 
    
         
             
                  @buffer.each(&block)
         
     | 
| 
       243 
247 
     | 
    
         
             
                end
         
     | 
| 
       244 
     | 
    
         
            -
             
     | 
| 
       245 
     | 
    
         
            -
                private
         
     | 
| 
       246 
     | 
    
         
            -
             
     | 
| 
       247 
     | 
    
         
            -
                def matched?(entry, message_filter, level_filter, tags_filter, progname_filter)
         
     | 
| 
       248 
     | 
    
         
            -
                  return false unless match?(entry.message, message_filter)
         
     | 
| 
       249 
     | 
    
         
            -
                  return false unless match?(entry.severity, level_filter)
         
     | 
| 
       250 
     | 
    
         
            -
                  return false unless match?(entry.progname, progname_filter)
         
     | 
| 
       251 
     | 
    
         
            -
             
     | 
| 
       252 
     | 
    
         
            -
                  if tags_filter.is_a?(Hash)
         
     | 
| 
       253 
     | 
    
         
            -
                    tags_filter = deep_stringify_keys(Lumberjack::Utils.expand_tags(tags_filter))
         
     | 
| 
       254 
     | 
    
         
            -
                  end
         
     | 
| 
       255 
     | 
    
         
            -
                  tags = deep_stringify_keys(Lumberjack::Utils.expand_tags(entry.tags))
         
     | 
| 
       256 
     | 
    
         
            -
             
     | 
| 
       257 
     | 
    
         
            -
                  return false unless match_tags?(tags, tags_filter)
         
     | 
| 
       258 
     | 
    
         
            -
             
     | 
| 
       259 
     | 
    
         
            -
                  true
         
     | 
| 
       260 
     | 
    
         
            -
                end
         
     | 
| 
       261 
     | 
    
         
            -
             
     | 
| 
       262 
     | 
    
         
            -
                def match?(value, filter)
         
     | 
| 
       263 
     | 
    
         
            -
                  return true unless filter
         
     | 
| 
       264 
     | 
    
         
            -
             
     | 
| 
       265 
     | 
    
         
            -
                  filter === value
         
     | 
| 
       266 
     | 
    
         
            -
                end
         
     | 
| 
       267 
     | 
    
         
            -
             
     | 
| 
       268 
     | 
    
         
            -
                def match_tags?(tags, filter)
         
     | 
| 
       269 
     | 
    
         
            -
                  return true unless filter
         
     | 
| 
       270 
     | 
    
         
            -
                  return false unless tags
         
     | 
| 
       271 
     | 
    
         
            -
             
     | 
| 
       272 
     | 
    
         
            -
                  filter.all? do |name, value_filter|
         
     | 
| 
       273 
     | 
    
         
            -
                    name = name.to_s
         
     | 
| 
       274 
     | 
    
         
            -
                    tag_values = tags[name]
         
     | 
| 
       275 
     | 
    
         
            -
                    if tag_values.is_a?(Hash)
         
     | 
| 
       276 
     | 
    
         
            -
                      if value_filter.is_a?(Hash)
         
     | 
| 
       277 
     | 
    
         
            -
                        match_tags?(tag_values, value_filter)
         
     | 
| 
       278 
     | 
    
         
            -
                      else
         
     | 
| 
       279 
     | 
    
         
            -
                        false
         
     | 
| 
       280 
     | 
    
         
            -
                      end
         
     | 
| 
       281 
     | 
    
         
            -
                    elsif value_filter.nil? || (value_filter.is_a?(Enumerable) && value_filter.empty?)
         
     | 
| 
       282 
     | 
    
         
            -
                      tag_values.nil? || (tag_values.is_a?(Array) && tag_values.empty?)
         
     | 
| 
       283 
     | 
    
         
            -
                    elsif tags.include?(name)
         
     | 
| 
       284 
     | 
    
         
            -
                      match?(tag_values, value_filter)
         
     | 
| 
       285 
     | 
    
         
            -
                    else
         
     | 
| 
       286 
     | 
    
         
            -
                      false
         
     | 
| 
       287 
     | 
    
         
            -
                    end
         
     | 
| 
       288 
     | 
    
         
            -
                  end
         
     | 
| 
       289 
     | 
    
         
            -
                end
         
     | 
| 
       290 
     | 
    
         
            -
             
     | 
| 
       291 
     | 
    
         
            -
                def deep_stringify_keys(hash)
         
     | 
| 
       292 
     | 
    
         
            -
                  if hash.is_a?(Hash)
         
     | 
| 
       293 
     | 
    
         
            -
                    hash.each_with_object({}) do |(key, value), result|
         
     | 
| 
       294 
     | 
    
         
            -
                      new_key = key.to_s
         
     | 
| 
       295 
     | 
    
         
            -
                      new_value = deep_stringify_keys(value)
         
     | 
| 
       296 
     | 
    
         
            -
                      result[new_key] = new_value
         
     | 
| 
       297 
     | 
    
         
            -
                    end
         
     | 
| 
       298 
     | 
    
         
            -
                  elsif hash.is_a?(Enumerable)
         
     | 
| 
       299 
     | 
    
         
            -
                    hash.collect { |item| deep_stringify_keys(item) }
         
     | 
| 
       300 
     | 
    
         
            -
                  else
         
     | 
| 
       301 
     | 
    
         
            -
                    hash
         
     | 
| 
       302 
     | 
    
         
            -
                  end
         
     | 
| 
       303 
     | 
    
         
            -
                end
         
     | 
| 
       304 
248 
     | 
    
         
             
              end
         
     | 
| 
       305 
249 
     | 
    
         
             
            end
         
     | 
| 
       306 
     | 
    
         
            -
             
     | 
| 
       307 
     | 
    
         
            -
            require_relative "capture_device/entry_score"
         
     | 
| 
       308 
     | 
    
         
            -
            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. 
     | 
| 
      
 34 
     | 
    
         
            +
              spec.required_ruby_version = ">= 2.7"
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
              spec.add_dependency "lumberjack", ">= 
     | 
| 
      
 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:  
     | 
| 
      
 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:  
     | 
| 
      
 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:  
     | 
| 
      
 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:  
     | 
| 
      
 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. 
     | 
| 
      
 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. 
     | 
| 
       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.
         
     |