====== log ======
===== Flow =====
Application:
* every call to ''NRF_LOG_sth'' puts a log entry into ringbuffer.
* NOT DEFERRED: ''NRF_LOG_FLUSH()'' is enforced: calls ''nrf_log_frontend_dequeue()'' in loop until ringbuffer empty.
Frontend:
* DEFERRED: Application calls ''nrf_log_frontend_dequeue()'' and:
* ''nrf_log_frontend_dequeue()'':
* one entry is pop-ed from ringbuffer
* new memobj is allocated from ''log_mempool'' to fit log entry.
* all registered backends are iterated:
* check if backend is enabled ''nrf_log_backend_is_enabled(p_backend)''
* filters: check if module log level and log entry severity level matches
* if log entry should be send to backend - call ''nrf_log_backend_put''
* **BACKEND:** process log entry and do really printf-like processing and sending / TX-ing / storing for background processing (**NRF_CLI_LOG_BACKEND**)
* ''nrf_log_frontend_dequeue()'' return ''false'' if ringbuffer is empty
participant Application as APP
queue "Circular Buffer\n**NRF_LOG_BUFSIZE**" as CB
queue "//log_mempool//\ndynamic memory pool" as MEM
control "//nrf_log_frontend_dequeue()//" as DE
note over MEM
**NRF_LOG_MSGPOOL_ELEMENT_SIZE**
**x**
**NRF_LOG_MSGPOOL_ELEMENT_COUNT**
endnote
control "backend RTT" as B1
control "backend UART" as B2
control "backend x" as B3
APP -> CB: NRF_LOG_INFO()
activate CB
APP -> CB: NRF_LOG_DEBUG()
activate CB
APP -> CB: NRF_LOG_HEXDUMP()
activate CB
...
APP -> DE: NRF_LOG_PROCESS()
activate DE
DE <-- MEM: nrf_memobj_alloc()
activate MEM
activate DE
CB --> DE: pop one entry
deactivate CB
DE --> DE: copy entry to allocated\nmempool object
DE <-- DE: backend1:\ncheck filters and status
DE --> B1: mempool object
DE <-- DE: backend2:\ncheck filters and status
DE --> B2: mempool object
DE <-- DE: backend3:\ncheck filters and status
DE --> B3: mempool object
MEM <-- DE: nrf_memobj_put()
deactivate MEM
DE --> APP: return "circular buffer is empty"
deactivate DE
deactivate DE
===== buffer sizes =====
* Circular buffer of **NRF_LOG_BUFSIZE (1024)**:
* Its aim is to store log entry from app as fast as possible without further processing.
* DEFERRED: should be big enough to store all log entries until apps calls log to be processed.
* Dynamic memory pool:
* size is ''NRF_LOG_MSGPOOL_ELEMENT_SIZE (20) x NRF_LOG_MSGPOOL_ELEMENT_COUNT (8)''
* provides dynamic memory access, even to fragmented memory.
* provides storage for log entry qualified to be passed to backends.
* provides ''get/put'' counting semaphore locking mechanism, so one log entry can be passed to multiple log backends at time.
* It looks like to be designed for background backends. Counting semaphore will keep object locked until all backends finish.
* Background backends:
* **NRF_CLI_LOG_BACKEND**
* Backends are using temporary string buffers - this is the real place where printf-like function writes generated strings.
* ''NRF_LOG_BACKEND_UART_TEMP_BUFFER_SIZE 64''
===== Backends api =====
typedef struct
{
void (*put)(nrf_log_backend_t const * p_backend, nrf_log_entry_t * p_entry);
void (*panic_set)(nrf_log_backend_t const * p_backend);
void (*flush)(nrf_log_backend_t const * p_backend);
} nrf_log_backend_api_t;
* ''put''
* UART/RTT:
* printf-like processing
* memobj with log entry is released (''put()'' - counting semaphore decremented)
* CLI:
* memobj is not released. ''get()'' is called to lock it. And entry is pushed to own **queue**.
* own **queue** size is defined by ''NRF_CLI_DEF''
* ''nrf_cli_process()'' --> ''cli_log_entry_process()'' gets entries from **queue**. It makes memobj free by ''put()'' after processing.
* ''panic_set'':
* RTT: empty
* UART: uninit uart driver
* ''flush'' - empty function in most backends
* CLI: ''(void)cli_log_entry_process(p_cli, true);''
* ''nrf_cli_process()''
* ''cli_log_entry_process()''
* ''do { } while'' loop until no log entries in CLI internal **queue**. So all entries are processed at once.
* result of processing is passed to printf-like writer ''p_cli->p_fprintf_ctx'' == ''nrf_cli_print_stream''
* ''cli_write'' which maps to ''nrf_cli_uart_transport_api.write'' == ''cli_uart_write''
* cli_uart_write puts data to UART TX ringbuffer defined by macro NRF_CLI_UART_DEF
===== using CLI and deferred log =====
SUMMARY:
* app logs data and metadata to ringbuffer: ''NRF_LOG_BUFSIZE'' - so must be big enough to handle all entries until log process
* app idle calls: ''NRF_LOG_PROCESS()''
* if dynamic log levels enabled it filter what to log
* it copies entries from ringbuffer to memobj (dynamic memory of size ''NRF_LOG_MSGPOOL_ELEMENT_SIZE (20) x NRF_LOG_MSGPOOL_ELEMENT_COUNT (8)'')
* each one memobj log entry is passed to all active backends (multiple reference locking possible by counting semaphore)
* UART backend: it process log entry (printf-like formatting) and put it in UART TX buffer. Memobj log entry is released.
* RTT backend: it process log entry (printf-like formatting) and put it in RTT buffer. Memobj log entry is released.
* CLI backend: it puts log entry into own queue. Memobj log entry is **NOT released**.
* **conclusion**: dynamic memory pool size ''NRF_LOG_MSGPOOL_ELEMENT_SIZE (20) x NRF_LOG_MSGPOOL_ELEMENT_COUNT (8)'' should be the same as ''NRF_LOG_BUFSIZE''.
* **conclusion**: CLI LOG queue sized defined by macro ''NRF_CLI_DEF'' should be big enough to track all memobj log entries.
* app idle calls: ''nrf_cli_process()''
* whole queue is processed in loop
* each memobj log entry is processed (printf-like formatting) and put into UART TX ring buffer (size defined by ''NRF_CLI_UART_DEF'').