Testing
New with Semantic Logger v4.13.
When writing tests we want to verify that the correct log messages, metrics, etc. are being created by the application. Since Semantic Logger uses a global logging mechanism we can’t use the regular logging to verify logging events.
Minitest helpers
To add the Minitest helper methods, add the following line to test_helper.rb:
Minitest::Test.include SemanticLogger::Test::Minitest Example test file:
require_relative "test_helper" class UserTest < ActiveSupport::TestCase describe User do it "logs message" do messages = semantic_logger_events do # Captures all log events during this block User.new.enable! end # Confirm the number of expected messages assert_equal 2, messages.count, messages # Confirm that the first log message includes the following elements assert_semantic_logger_event( messages[0], level: :info, message: "User enabled" ) # Confirm that the second log message includes the following elements assert_semantic_logger_event( messages[1], level: :debug, message: "Completed" ) end end end For more examples on testing log messages, see Rails Semantic Logger tests.
Capturing Events
The method semantic_logger_events returns all captured log events that occurred during the supplied block.
messages = semantic_logger_events do # ... Captures all log events during this block end By default semantic_logger_events captures all log events. To narrow the events to only those captured by a specific class, supply the class name. For Example:
messages = semantic_logger_events(ApiClient) do # ... Only captures ApiClient log events created during this block. end Verifying a log event
Use assert_semantic_logger_event to verify that a single log event has all the required attributes.
assert_semantic_logger_event( messages[0], level: :info, message: "User enabled", ) All arguments other than event are optional:
event:SemanticLogger::Logthe event captured.message: Text message to be logged.message_includes: A partial match of the message.level: Log level of the supplied log call::trace, :debug, :info, :warn, :error, :fatal.payload: Exact match of hash of payload items.payload_includes: Partial match of payload items.tags: Any tags active on the thread when the log call was made.named_tags: Any tags active on the thread when the log call was made.name: Class name supplied to the logging instance.exception: Ruby Exception object that was logged.metric: The metric text supplied.metric_amount: Used for numeric or counter metrics.dimensions: Any dimensions captured.context: Hash of named contexts that were captured when the log entry was created.time: The time at which the log entry was created.duration: The time taken to complete a measure call in milli-seconds.backtrace: The backtrace captured if active.thread_name: Name of the thread in which the logging call was called.
Instead of asserting an exact match on the message, a partial match can be supplied using message_includes. For example:
assert_semantic_logger_event( messages[0], level: :info, message_includes: "enabled" ) To verify a partial payload, and ignore any extra keys in the payload, use payload_includes to specify the partial payload.
Example, asserts the entire payload is an exact match:
assert_semantic_logger_event( messages[0], level: :info, message: "User enabled", payload: { first_name: "Jack", last_name: "Jones" } ) Example, asserts a partial payload matches:
assert_semantic_logger_event( messages[0], level: :info, message: "User enabled", payload_includes: { first_name: "Jack" } ) RSpec
For RSpec users, this sample supplied by @jgascoignetaylor-godaddy will be useful:
context 'when it blows up' do let(:capture_logger) { SemanticLogger::Test::CaptureLogEvents.new } it 'should should log the error' do allow_any_instance_of(MyThing).to receive(:logger).and_return(capture_logger) MyThing.new('asdf').do_something! expect(capture_logger.events.last.message).to include('Here is a message') expect(capture_logger.events.last.level).to eq(:error) end end Open to pull requests to implement the RSpec equivalent of the Minitest helpers: SemanticLogger::Test::Minitest.
Other testing frameworks
If you use another testing framework and would like to contribute helper methods similar to the ones supplied in Semantic Logger for Minitest, we would welcome a pull request.
The approach is to stub out the Semantic Logger and replace it with an instance of SemanticLogger::Test::CaptureLogEvents. It looks and feels like a regular logging class, except that it retains the log events in memory. The raw events are captured so that tests are not affected by configured appenders or their formats.
Define a special test logger to capture log events:
let(:logger) { SemanticLogger::Test::CaptureLogEvents.new } To capture just the log events from a specific class, stub the logger on that class:
User.stub(:logger, logger) do # Capture all logging calls to the User logger. end Or, to capture all log events from all classes, stub the global logger:
SemanticLogger::Logger.stub(:processor, logger) do # Capture all log events during the block. end The log events are now available in logger.events.