Skip to main content
Version: Next

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 or chrome://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:

Timeline artifact example

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.