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.



181
182
183
184
185
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 181

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.



177
178
179
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 177

def event_block
  @event_block
end

Instance Method Details

#failure_messageObject



223
224
225
226
227
228
229
230
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 223

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



232
233
234
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 232

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)


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 187

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)


203
204
205
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 203

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)


211
212
213
214
215
216
217
218
219
220
221
# File 'lib/rails_semantic_logging/rspec/matchers.rb', line 211

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