LoggerHandlerKit
View Source📚 Logger Handler Kit is an educational Hex package! The goal is to help people understand how to write and test logger handlers. This is a great package if you want to:
- learn more about logging
- write your own logger handler
- bootstrap tests
- improve your test coverage
- learn how to make your tests async
You can use this package in a number of ways:
- Read through the docs to learn about concepts
- Read through the code to learn how to make things work
- Add as a test dependency for your app and actually use test helpers
AAA
Since the package's primary goal is to aid with tests, its most important parts are organized into three modules that embody what is called an AAA test pattern. The AAA stands for three main stages of a test: Arrange, Act and Assert:
LoggerHandlerKit.Arrange
helps with setting up handlers for testsLoggerHandlerKit.Act
suggests a menu of interesting test casesLoggerHandlerKit.Assert
provides a single assert function with a very important job
Example Test Suite
Logger Handler Kit comes with a fully asynchronous test suite for the default
Elixir logger handler, inspired by tests in Sentry
, DiscoLog
and Elixir
itself. It demonstrates how a handler can be tested with a fully async suite by
employing pretty much every trick in LoggerHandlerKit's repertoire:
- It creates a dedicated handler for each test.
- It guards the test handler against irrelevant log events with an ownership filter.
- It configures the
logger_std_h
handler to write to a fake IO device instead of stdout. The device relays all received writes back to the test process. - It uses
LoggerHandlerKit.Arrange.ensure_per_handler_translation/1
to enable switchinghandle_otp_reports
andhandle_sasl_reports
without impacting the rest of the application.
Here's a visualization of how test setup compares to the regular application setup. Note how test process logs use a separate "path" thanks to a dedicated logger handler guarded by the ownership filter:
---
config:
look: handDrawn
theme: neutral
---
flowchart TD
subgraph before[Normal Setup]
test_pid[Test Process] --> test_log
other_tests[Other Tests] --> other_log
test_log@{shape: rounded, label: [log event]} --> translator
other_log@{shape: rounded, label: [log event]} --> translator
subgraph primary[Primary Filters]
translator[:logger_translator]
end
subgraph handlers
default_handler
end
translator --> default_handler[:default handler]
default_handler --> stdout[standard output]
end
subgraph after[Async Setup]
test_pid2[Test Process] --> test_log2
other_tests2[Other Tests] --> other_log2
test_log2@{shape: rounded, label: [log event]} --> no_filters
other_log2@{shape: rounded, label: [log event]} --> no_filters
subgraph primary2[Primary Filters]
no_filters@{shape: braces, label: [No Filters]}
end
no_filters --> default_translator
no_filters --> ownership_filter
subgraph handlers2[Handlers]
default_translator[:logger_translator] --> default_handler2[:default handler]
ownership_filter[ownership filter] --> test_translator[:logger_translator]
test_translator[:logger_translator] --> test_handler[test handler]
end
default_handler2 --> stdout2[standard output]
test_handler[test handler] --> fakeio[FakeIODevice]
end
Getting Started Good
- Add the package to your project as a test dependency
def deps do
[
# The package only makes sense for tests!
{:logger_handler_kit, only: :test, "~> 0.1.0"}
]
end
- Replace
MyHandler
with your handler name and make this test green:
defmodule MyHandlerTest do
use ExUnit.Case, async: true
setup_all {LoggerHandlerKit.Arrange, :ensure_per_handler_translation}
setup %{test: test} = context do
{context, on_exit} =
LoggerHandlerKit.Arrange.add_handler(
test,
MyHandler,
%{}
)
on_exit(on_exit)
context
end
test "string message", %{handler_ref: ref} do
LoggerHandlerKit.Act.string_message()
LoggerHandlerKit.Assert.assert_logged(ref)
end
end
- Add assertions that make sense for your handler to the test and make it green again.
- Write a passing test for each function in the
LoggerHandlerKit.Act
module.
🎉 Congratulations, your logger handler is now pretty good and you have a decent asynchronous test suite on your hands.
TODO
- [ ] cover metadata
- [ ] cover overload protection
- [ ] cover encoding/serialization
- [ ] cover logging packages overview