# Logging
[< Dynamic Facades](dynamic.md) | [Up: README](../README.md) | [Process Sharing >](process-sharing.md)
## Dispatch logging
Record every call that crosses a contract boundary, then assert on
the sequence:
```elixir
setup do
MyApp.Todos
|> DoubleDown.Double.stub(:get_todo, fn [id] -> {:ok, %Todo{id: id}} end)
DoubleDown.Testing.enable_log(MyApp.Todos)
:ok
end
test "logs dispatch calls" do
MyApp.Todos.get_todo("42")
assert [{MyApp.Todos, :get_todo, ["42"], {:ok, %Todo{id: "42"}}}] =
DoubleDown.Testing.get_log(MyApp.Todos)
end
```
The log captures `{contract, operation, args, result}` tuples in
dispatch order. Enable logging before making calls; `get_log/1`
returns the full sequence.
## Log matcher (structured log assertions)
`DoubleDown.Log` provides structured expectations against the dispatch
log. Unlike `get_log/1` + manual assertions, it supports ordered
matching, counting, reject expectations, and strict mode.
This is particularly valuable with fakes like `Repo.InMemory` that do
real computation (changeset validation, PK autogeneration, timestamps)
— matching on results in the log is a meaningful assertion, not a
tautology.
### Basic usage
```elixir
DoubleDown.Testing.enable_log(MyApp.Todos)
# ... set up double and dispatch ...
DoubleDown.Log.match(:create_todo, fn
{_, _, [params], {:ok, %Todo{id: id}}} when is_binary(id) -> true
end)
|> DoubleDown.Log.reject(:delete_todo)
|> DoubleDown.Log.verify!(MyApp.Todos)
```
Matcher functions only need positive clauses — `FunctionClauseError`
is caught and treated as "didn't match". No `_ -> false` catch-all
needed, though returning `false` explicitly can be useful for
excluding specific values that are hard to exclude with pattern
matching alone.
### Counting occurrences
```elixir
DoubleDown.Log.match(:insert, fn
{_, _, [%Changeset{data: %Discrepancy{}}], {:ok, _}} -> true
end, times: 3)
|> DoubleDown.Log.verify!(DoubleDown.Repo)
```
### Strict mode
By default, extra log entries between matchers are ignored (loose
mode). Strict mode requires every log entry to be matched:
```elixir
DoubleDown.Log.match(:insert, fn _ -> true end)
|> DoubleDown.Log.match(:update, fn _ -> true end)
|> DoubleDown.Log.verify!(MyContract, strict: true)
```
### Using with DoubleDown.Double
Double and Log serve complementary roles — Double for fail-fast
validation and producing return values, Log for after-the-fact
result inspection:
```elixir
# Set up double
DoubleDown.Double.expect(MyContract, :create, fn [p] -> {:ok, struct!(Thing, p)} end)
DoubleDown.Testing.enable_log(MyContract)
# Run code under test
MyModule.do_work(params)
# Verify expectations consumed
DoubleDown.Double.verify!()
# Verify log entries match expected patterns
DoubleDown.Log.match(:create, fn
{_, _, _, {:ok, %Thing{}}} -> true
end)
|> DoubleDown.Log.verify!(MyContract)
```
---
[< Dynamic Facades](dynamic.md) | [Up: README](../README.md) | [Process Sharing >](process-sharing.md)