LoggerHandlerKit.Arrange (Logger Handler Kit v0.1.0)
View SourceFunctions that help set up logger handler tests.
Summary
Functions
Attaches a logger handler with individual translation and ownership filters.
Give a particular process access to a logger handler attached by add_handler/4
.
Replace global logger translator with per-handler ones, so that each test can configure it independently.
Ownership filter drops all events that do not have access to the logger handler attached by add_handler/4
. This is one of the main things that makes async tests possible.
Functions
@spec add_handler( :logger_handler.id(), module(), term(), :logger_handler.config() | map() ) :: {%{handler_id: :logger_handler.id(), handler_ref: reference()}, (-> term())}
Attaches a logger handler with individual translation and ownership filters.
Arguments
handler_id
is an id that will be used for handler. A good idea is to use a test name or a test module.handler_module
a handler module that we want to attach. For example,:logger_std_h
orSentry.LoggerHandler
.config
this is a handler config. The one thathandler_module
defines. A small one, of:term()
type.big_config_override
is a map that will be merged into handler config. But a different one, the one of:logger_handler.config()
type. This is also the place to puthandle_otp_reports
andhandle_sasl_reports
options, they will be passed to the translator.
Return
The function returns a two element tuple. The first element is a map that contains
handler_id
and handler_ref
. It can be merged into the test context. The second
element is an anonymous function that detaches the handler. Drop it into
ExUnit.Callbacks.on_exit/1
callback.
The handler_module
is not attached as-is. Instead, it is wrapped in LoggerHandlerKit.HandlerWrapper
.
Every time a handlers' :logger_handler.log/2
callback is invoked, it sends a message to the
test process which can be received with LoggerHandlerKit.Assert.assert_logged/1
function.
Example
setup %{test: test} = context do
{context, on_exit} =
LoggerHandlerKit.Arrange.add_handler(
test,
:logger_std_h,
%{},
%{formatter: Logger.default_formatter(), level: :debug}
)
on_exit(on_exit)
context
end
Give a particular process access to a logger handler attached by add_handler/4
.
It shouldn't be necessary when using LoggerHandlerKit.Act
functions, but for custom cases you might need it.
Remember!
In a lot of cases, caller tracking is enough to automatically propagate ownership information. Use it!
Replace global logger translator with per-handler ones, so that each test can configure it independently.
Normally, handle_otp_reports
and handle_sasl_reports
are global configuration
options, and changing them in tests is sufficient to make the tests sync.
However, there is a workaround! In reality, both options are read at startup
and passed to the logger_translator
filter, which Elixir Logger attaches as a
primary filter. We can detach this primary filter and reattach it
to each logger handler independently. This way, each handler can take advantage of
different translator configurations.
This function is designed to be run as a test setup, and it does exactly that. It detaches the global translator filter and attaches it to each existing handler.
defmodule LoggerHandlerKit.DefaultLoggerTest do
use ExUnit.Case, async: true
setup_all {LoggerHandlerKit.Arrange, :ensure_per_handler_translation}
...
end
Exception
Not all handlers will get a personal translator. Attaching logger translator to a logger handler can sometimes lead to
surprising results because of the way logger filters work. If a handler has filter_default: :stop
in configuration, it
effectively drops all events by default and expects filters to explicitly tell which events they want through. Logger
Translator follows a different paradigm, it blocks things that it wants to be blocked and happily allows everything else.
When paired, a conservative handler and a underzelous filter will result in handler receiving events it didn't expect. To
mitigate this, we only attach translator to handlers with filter_default: :log
Ownership filter drops all events that do not have access to the logger handler attached by add_handler/4
. This is one of the main things that makes async tests possible.
Ownership filter is built with NimbleOwnership
mechanism, the same that powers Mox
,
Req
, etc. For the most part, caller tracking is enough to correctly propagate
ownership information across processes. However, in some cases the logging process is
completely detached from the originating process. In this case, ownership filter will
check pid
key in metadata, and if that pid has access to the handler, it will
allow it and also ask Mox
to allow it, if present.