Logger
Detox Logger API allows you to save your custom messages and events alongside the built-in ones.
In addition to being formatted and printed to the console, they can be preserved as log artifacts if you enable them via --record-logs
CLI option or artifacts.plugins.log.enabled
in the config:
<artifacts-location>/detox.log
– plain text log;<artifacts-location>/detox.trace.json
– Trace Event format file, viewable via Perfetto orchrome://tracing
.
Below we’ll be listing all the public properties and methods of the logger.
Properties
log.level
[enum]
Read-only. Returns the current log level, one of values:
fatal
, error
, warn
, info
, debug
, trace
.
Methods
log.*([event,] ...msg)
Logs an instant message with an optional event metadata. There are six methods for producing log messages varying by log level:
log.fatal([event,] ...msg)
log.error([event,] ...msg)
log.warn([event,] ...msg)
log.info([event,] ...msg)
log.debug([event,] ...msg)
log.trace([event,] ...msg)
Example:
const { log } = require('detox');
log.info('Some message');
// detox[2020] i Some message
log.error({ err: new Error('Test') }, 'An error message');
// detox[2020] i An error message
// err: Test
log.*.begin([event,] msg)
Logs a beginning of a duration event with an optional event metadata. Duration events are displayed as continuous colorful segments on the timeline and can be stacked if you call the method multiple times, e.g.:
log.info.begin({ data: { email } }, 'Login Flow');
// detox[2020] B Login Flow
// data: { email: 'tester@example.com' }
log.info.begin('Nested sub-flow');
// detox[2020] B Nested sub-flow
Make sure you always end your events so that they don't get marked as unfinished, like shown on the screenshot below:
log.*.end([event, msg])
Logs an end of a duration event with an optional event metadata and a message. If the message is omitted, the logger prints the corresponding message from the duration begin event:
log.info.end();
// detox[2020] E Nested sub-flow
log.info.end({ success: true }, 'Login Flow (custom end message)');
// detox[2020] E Login Flow (custom end message)
log.*.complete([event,] msg, functionOrPromise)
A convenience method to wrap functions and promises with log.*.begin
and log.*.end
, e.g.:
await log.info.complete('Login Flow', async () => {
// …
});
// detox[2020] B Login Flow
// detox[2020] E Login Flow
As a bonus, this wrapper also adds { success: true }
or { success: false, error }
metadata to
the end event.
Effectively, begin
and end
can even be called in two complete different places - such as beforeEach
and afterEach
, but that is discouraged.
In fact, log.*.complete()
is the recommended way of tracing things, e.g.:
Event metadata
All the log methods accept an optional first argument which can contain some custom metadata: numbers, strings and booleans:
detox.log.info({ /* metadata */ }, message);
detox.log.trace.begin({ /* metadata */ }, message);
detox.log.trace.end({ /* metadata */ });
Aside from custom user properties, there are a few meaningful properties that affect the timeline representation.
id
[string | number]
Use arbitrary IDs when you have a risk of overlapping concurrent events, e.g.:
await Promise.all([
await detox.log.complete({ id: 1 }, 'Do this', async () => { /* … */ }),
await detox.log.complete({ id: 2 }, 'Do that', async () => { /* … */ }),
]);
Using IDs will prevent situations like this, where the nested event outlasts its parent:
B E
|-- event 1 ----|
B E
|-- event 2 -----|
In the example above, the actual sequence of calls will be:
log.info.begin('event 1');
log.info.begin('event 2');
log.info.end(); // from event 1
log.info.end(); // from event 2
Therefore, it will be interpreted erroneously on the timeline, as if the second event has ended before the first one:
B E
|-- event 1 --------|
B E
|-- event 2 -|
When you begin an event with a specific id
while there's already some other duration event, the logger allocates another "lane" for that event by assigning a distinct tid
(thread ID) to it:
cat
[string | string[]]
Event category. Helpful for filtering specific events.
Pass either a string of comma-separated values or a string array, e.g.:
log.info({ cat: 'login,login-email' }, 'Starting e-mail login flow...');
// is identical to:
log.info({ cat: ['login', 'login-email'] }, 'Starting e-mail login flow...');
cname
[string]
Custom event color. See the available color names here.
*
[string | number | boolean]
Your custom properties, e.g.:
detox.log.info({ login: 'test@example.com' }, 'Entering credentials...');
Custom properties are not printed to the terminal logs, but there are a few reserved names which have
an extra formatting due to our default logger.options.stringifiers
— these are: args
, data
, error
, stack
, origin
.
Also, there are a few reserved properties which cannot be logged:
pid
— process ID,tid
— thread ID,ts
— timestamp,ph
— phase: begin (B), end (E), instant (i) event.
Artifacts
The logger subsystem produces two artifacts when --record-logs
CLI option is enabled or artifacts.plugins.log.enabled
config is set to true.
detox.log
This file contains all the log messages you could see in the terminal window, except that there are messages of all the log levels, from fatal
to trace
.
detox.trace.json
JSON file, which, if loaded into Perfetto or chrome://tracing
(internal address in Google Chrome browser), would look something like this:
The tracing view provides a visual, hierarchical representation of the various processes that took place during the execution of the testing session, over the execution’s time-line. These processes appear as hierarchical sections – sometimes visually ordered in a parent-child way, depending on their formation time and context.