# Wox Logger Design

This document explains how Wox configures and manages its logging system.

It follows the [good practices][logger-doc] to configure logging for a library.

**Key idea**: Wox dynamically adapts its logger configuration depending on whether it's run directly, under pytest, or through a subprocess.

## Why using a filter rather than an adapter to add contextual information ?

The adapter is a wrapper around the logger, providing easily contextual information.

In the case of wox, whose sessions are intricately nested within several loops, the design used the module approach instead of a classic OOP approach. The adapter doesn't suit well with the module approach.

To solve this issue, I use a filter added at the beginning of the main.py. The filter
is actualize once within the session. Since the logger is instantiated in logger_setup.py, it becomes immediately usable on import. Moreover, any import of the
logger will inherit the state from the main.py.

I'm leveraging the knowledge of import mechanism to create a lightweight, easy to set
and powerful logger for wox.

## Logger and pytest interaction.

Wox's logger must handle 3 situations : logs wox session, logs pytest session and
logs pytest session within wox session.

The wox's logger provides to the user logs from the wox sessions, but also
from the tests done with pytest. To ensure the logger behaves accordingly, it
requires some tweak to work correctly with pytest.

The main concern lies in the log file handler, which creates its log file upon import-time initialization.

And wox must log the file in .logs/wox-session during a wox session and in
.logs/tests during tests launched with pytest.

To achieve this requirement, the log file handler has a dynamic filename depending
of whether the command is wox related or pytest related:

```python
wox/logger/handlers/log_file_handler.py:
----------------------------------------

if 'pytest' in sys.modules:
    log_path = DOT_WOX / '.logs' / 'tests'
    log_path.mkdir(parents = True, exist_ok = True)
    filename = log_path / f'output-{datetime.now():%Y-%m-%d_%H-%M-%S}.log'
else:
    log_path = DOT_WOX / '.logs' / 'wox-session'
    log_path.mkdir(parents = True, exist_ok = True)
    filename = log_path/ f'output-{datetime.now():%Y-%m-%d_%H-%M-%S}.log'
```

Finally, since wox_logger is correctly configured to be independant from the root logger, using the default option propagate as True will duplicate the logs - once logged by the wox_logger and once logged by the root logger. So it is a good practice to define propagate = False when the configuration of the logger is independant from the root logger.
Since pytest adds automatically handler to the root logger, if wox_logger
does not propagate its LogRecords to the root logger, the wox_logger won't have access
to the logs emanating from pytest.

To fix this, I dynamically define propagate value depending on the presence of pytest:

```python
wox/logger/logger_setup.py:
---------------------------

if 'pytest' in sys.modules:
    wox_logger.propagate = True
else:
    wox_logger.propagate = False
```

> **But what happens if wox use pytest as a command ?**
Wox launch pytest as a subprocess, which initializes its own log file handler.
As a result, wox writes correctly logs but pytest running in the subprocess has its own wox_logger initializing its own log file handler and creating a log file
in .wox/.logs/tests.
To handle this case, wox creates an environment variable, WOX_SUBPROCESSOR_NO_LOG.
This environment variable prevents the initialization of a log file handler and
replaces it with a NullHandler.

```python

wox/subprocessor.py - in run_command():
---------------------------------------

if any(part.endswith('pytest') or part == 'pytest' for part in command):
    env['WOX_SUBPROCESSOR_NO_LOG'] = '1'

wox/logger/handlers/log_file_handler.py:
----------------------------------------

if os.getenv('WOX_SUBPROCESSOR_NO_LOG') == '1':
    return None

wox/logger/logger_setup.py:
---------------------------

console_handler = initialize_console_handler()
log_file_handler = initialize_log_file_handler()

wox_logger = logging.getLogger('wox')
wox_logger.setLevel(logging.DEBUG)
wox_logger.addHandler(console_handler)
if log_file_handler:
    wox_logger.addHandler(log_file_handler)
else:
    wox_logger.addHandler(logging.NullHandler())
```

## Wox logger initializes .wox

The log file handler creates its log file during the import-time initialization. It is very difficult to handle it otherwise. Instead of fighting it, I chose to leverage it by letting the log file handler set up the .wox directory.

[logger-doc]: https://docs.python.org/3/howto/logging.html#library-config
