Class: RailsSemanticLogging::RSpec::HaveLoggedMessageMatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_semantic_logging/rspec/matchers.rb

Overview

Block matcher that finds a specific log event by message and exposes both the raw SemanticLogger::Log and an optionally formatted version to a user block, so the test can make arbitrary assertions on the event.

Complementary to ‘log_semantic`:

  • ‘log_semantic` is declarative (“was anything matching these criteria logged?”) and returns a boolean.

  • ‘have_logged_message` is imperative — it finds the event, hands it over to the test, and lets the test assert whatever it needs.

Usage:

expect { logger.info("hello", key: "val") }.to have_logged_message("hello")
expect { do_work }.to have_logged_message(/import/, :warn)

expect { service.call }
  .to have_logged_message("Completed").with_formatted_event { |event, formatted|
    expect(event.payload).to include(order_id: order.id)
    expect(formatted.dig("http", "status_code")).to eq(200)
  }

The optional ‘with_formatted_event` block receives `(event, formatted)` where `formatted` is the JSON output of the chosen formatter parsed back with indifferent access. By default the gem’s Datadog formatter is used; pass any ‘SemanticLogger::Formatters::Base` subclass to override:

.with_formatted_event(SemanticLogger::Formatters::Json) { |event, formatted| ... }

Constant Summary collapse

DEFAULT_FORMATTER =
::RailsSemanticLogging::Formatters::Datadog

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(expected, expected_level = nil) ⇒ HaveLoggedMessageMatcher

Returns a new instance of HaveLoggedMessageMatcher.



147
148
149
150
151
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 147

def initialize(expected, expected_level = nil)
  @expected_message = expected
  @expected_level = expected_level
  @formatter_class = DEFAULT_FORMATTER
end

Instance Attribute Details

#event_blockObject (readonly)

Returns the value of attribute event_block.



143
144
145
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 143

def event_block
  @event_block
end

Instance Method Details

#failure_messageObject



189
190
191
192
193
194
195
196
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 189

def failure_message
  return @error.message if @error
  if @log_event && @expected_level && @log_event.level != @expected_level
    return "expected log level #{@expected_level.inspect}, got #{@log_event.level.inspect}"
  end

  "expected block to log a message matching #{@expected_message.inspect}"
end

#failure_message_when_negatedObject



198
199
200
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 198

def failure_message_when_negated
  "expected block NOT to log a message matching #{@expected_message.inspect}, but a matching event was emitted"
end

#matches?(block) ⇒ Boolean

Returns:

  • (Boolean)


153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 153

def matches?(block)
  # Capture the live appender BEFORE we attach our test appender. The
  # Datadog formatter reads `logger.host` on the appender it receives, so
  # the formatter call later on needs a real one (any subscriber will do).
  @live_appender = ::SemanticLogger.appenders.first
  @log_event = capture_first_matching_event(block)
  return false unless @log_event
  return false if @expected_level && @log_event.level != @expected_level

  @event_block&.call(@log_event, formatted)
  true
rescue ::RSpec::Expectations::ExpectationNotMetError => e
  @error = e
  false
end

#supports_block_expectations?Boolean

Returns:

  • (Boolean)


169
170
171
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 169

def supports_block_expectations?
  true
end

#with_formatted_event(formatter_class = nil, &block) ⇒ Object

Attach a block that receives the matched ‘(log_event, formatted)` pair. `formatted` is the JSON output of the configured formatter, parsed back with `HashWithIndifferentAccess` so callers can navigate the structure with either string or symbol keys.

Raises:

  • (ArgumentError)


177
178
179
180
181
182
183
184
185
186
187
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 177

def with_formatted_event(formatter_class = nil, &block)
  raise ArgumentError, 'block is required' unless block
  if formatter_class && !(formatter_class < ::SemanticLogger::Formatters::Base)
    raise ArgumentError,
          "formatter must inherit from SemanticLogger::Formatters::Base, got #{formatter_class.inspect}"
  end

  @formatter_class = formatter_class if formatter_class
  @event_block = block
  self
end