blob: 120333dd1c84b520e1c9acc43c7f1ebf2f46558b [file] [log] [blame]
.. _chapter-pw-log:
.. default-domain:: cpp
.. highlight:: cpp
------
pw_log
------
Pigweed's logging module provides facilities for applications to log
information about the execution of their application. The module is split into
two components:
1. The facade (this module) which is only a macro interface layer
2. The backend, provided elsewhere, that implements the low level logging
Usage examples
--------------
Here is a typical usage example, showing setting the module name, and using the
long-form names.
.. code-block:: cpp
#define PW_LOG_MODULE_NAME "BLE"
#include "pw_log/log.h"
int main() {
PW_LOG_INFO("Booting...");
PW_LOG_DEBUG("CPU temp: %.2f", cpu_temperature);
if (BootFailed()) {
PW_LOG_CRITICAL("Had trouble booting due to error %d", GetErrorCode());
ReportErrorsAndHalt();
}
PW_LOG_INFO("Successfully booted");
}
In ``.cc`` files, it is possible to dispense with the ``PW_`` part of the log
names and go for shorter log macros.
.. code-block:: cpp
#define PW_LOG_MODULE_NAME "BLE"
#define PW_LOG_USE_ULTRA_SHORT_NAMES 1
#include "pw_log/log.h"
int main() {
INF("Booting...");
DBG("CPU temp: %.2f", cpu_temperature);
if (BootFailed()) {
CRT("Had trouble booting due to error %d", GetErrorCode());
ReportErrorsAndHalt();
}
INF("Successfully booted");
}
Layer diagram example: ``stm32f429i-disc1``
-------------------------------------------
Below is an example diagram showing how the modules connect together for the
``stm32f429i-disc1`` target, where the ``pw_log`` backend is ``pw_log_basic``.
``pw_log_basic`` uses the ``pw_sys_io`` module to log in plaintext, which in
turn outputs to the STM32F429 bare metal backend for ``pw_sys_io``, which is
``pw_sys_io_baremetal_stm32f429i``.
.. blockdiag::
blockdiag {
default_fontsize = 14;
orientation = portrait;
group {
color = "#AAAAAA";
label = "Microcontroller"
app [label = "App code"];
facade [label = "pw_log"];
backend [label = "pw_log_basic"];
sys_io [label = "pw_sys_io"];
sys_io_bm [label = "pw_sys_io_\nstm32f429"];
uart [label = "UART pins"];
}
ftdi [label = "FTDI cable"];
computer [label = "Minicom"];
app -> facade -> backend -> sys_io -> sys_io_bm -> uart -> ftdi -> computer;
//app -> facade [folded];
//backend -> sys_io [folded];
//uart -> ftdi [folded];
}
Logging macros
--------------
These are the primary macros for logging information about the functioning of a
system, intended to be used directly.
.. cpp:function:: PW_LOG(level, flags, fmt, ...)
This is the primary mechanism for logging.
*level* - An integer level as defined by ``pw_log/levels.h``.
*flags* - Arbitrary flags the backend can leverage. The semantics of these
flags are not defined in the facade, but are instead meant as a general
mechanism for communication bits of information to the logging backend.
Here are some ideas for what a backend might use flags for:
- Example: ``HAS_PII`` - A log has personally-identifying data
- Example: ``HAS_DII`` - A log has device-identifying data
- Example: ``RELIABLE_DELIVERY`` - Ask the backend to ensure the log is
delivered; this may entail blocking other logs.
- Example: ``BEST_EFFORT`` - Don't deliver this log if it would mean blocking
or dropping important-flagged logs
*fmt* - The message to log, which may contain format specifiers like ``%d``
or ``%0.2f``.
Example:
.. code-block:: cpp
PW_LOG(PW_LOG_DEFAULT_FLAGS, PW_LOG_LEVEL_INFO, "Temp is %d degrees", temp);
PW_LOG(UNRELIABLE_DELIVERY, PW_LOG_LEVEL_ERROR, "It didn't work!");
.. note::
``PW_LOG()`` should not be used frequently; typically only when adding
flags to a particular message to mark PII or to indicate delivery
guarantees. For most cases, prefer to use the direct ``PW_LOG_INFO`` or
``PW_LOG_DEBUG`` style macros, which are often implemented more efficiently
in the backend.
.. cpp:function:: PW_LOG_DEBUG(fmt, ...)
.. cpp:function:: PW_LOG_INFO(fmt, ...)
.. cpp:function:: PW_LOG_WARN(fmt, ...)
.. cpp:function:: PW_LOG_ERROR(fmt, ...)
.. cpp:function:: PW_LOG_CRITICAL(fmt, ...)
Shorthand for `PW_LOG(PW_LOG_DEFAULT_FLAGS, <level>, fmt, ...)`.
Filtering logs
--------------
``pw_log`` supports compile time filtering of logs through two mechanisms.
1. Filter by level. Source files that define ``PW_LOG_LEVEL`` will display all
logs at or above the chosen level.
Example:
.. code-block:: cpp
#include "pw_log/log.h"
#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO
void DoSomething() {
PW_LOG_DEBUG("This won't be logged at all");
PW_LOG_INFO("This is INFO level, and will display");
PW_LOG_WARN("This is above INFO level, and will display");
}
2. Filter by arbitrary expression based on ``level`` and ``flags``. Source
files that define ``PW_LOG_ENABLE_IF(level, flags)`` will display if the
given expression returns true.
Example:
.. code-block:: cpp
#include "pw_log/log.h"
// This define might be supplied by the build system.
#define MY_PRODUCT_LOG_PII_ENABLED false
// This is the PII mask bit selected by the application.
#define MY_PRODUCT_PII_MASK (1 << 5)
// Pigweed's log facade will call this macro to decide to log or not. In
// this case, it will drop logs with the PII flag set if display of PII is
// not enabled for the application.
#define PW_LOG_ENABLE_IF(level, flags) \
(level >= PW_LOG_INFO && \
!((flags & MY_PRODUCT_PII_MASK) && MY_PRODUCT_LOG_PII_ENABLED)
void DoSomethingWithSensitiveInfo() {
PW_LOG_DEBUG("This won't be logged at all");
PW_LOG_INFO("This is INFO level, and will display");
// In this example, this will not be logged since logging with PII
// is disabled by the above macros.
PW_LOG(PW_LOG_LEVEL_INFO,
MY_PRODUCT_PII_MASK,
"Sensitive: %d",
sensitive_info);
}
.. attention::
At this time, only compile time filtering is supported. In the future, we
plan to add support for runtime filtering.
Logging attributes
------------------
The logging facade in Pigweed is designed to facilitate the capture of at least
the following attributes:
- *Level* - The log level; for example, INFO, DEBUG, ERROR, etc. Typically an
integer
- *Flags* - Bitset for e.g. RELIABLE_DELIVERY, or HAS_PII, or BEST_EFFORT
- *File* - The file where the log was triggered
- *Line* - The line number in the file where the log line occured
- *Function* - What function the log comes from. This is expensive in binary
size to use!
- *Module* - The user-defined module name for the log statement; e.g. “BLE” or
“BAT”
- *Message* - The message itself; with % format arguments
- *Arguments* - The format arguments to message
- *Thread* - For devices running with an RTOS, capturing the thread is very
useful
- *Others* - Processor security level? Maybe Thread is a good proxy for this
Each backend may decide to capture different attributes to balance the tradeoff
between call site code size, call site run time, wire format size, logging
complexity, and more.
Design discussion
-----------------
Why not use C++ style stream logging operators like Google Log?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are multiple reasons to avoid the C++ stream logging style in embedded,
but the biggest reason is that C++ stream logging defeats log tokenization. By
having the string literals broken up between ``<<`` operators, tokenization
becomes impossible with current language features.
Consider this example use of Google Log:
.. code-block:: cpp
LOG(INFO) << "My temperature is " << temperature << ". State: " << state;
This log statement has two string literals. It might seem like one could convert
move to tokenization:
.. code-block:: cpp
LOG(INFO) << TOKEN("My temperature is ") << temperature << TOKEN(". State: ") << state;
However, this doesn't work. The key problem is that the tokenization system
needs to allocate the string in a linker section that is excluded from the
final binary, but is in the final ELF executable (and so can be extracted).
Since there is no way to declare a string or array in a different section in
the middle of an experession in C++, it is not possible to tokenize an
expression like the above.
In contrast, the ``printf``-style version is a single statement with a single
string constant, which can be expanded by the preprocessor (as part of
``pw_tokenizer``) into a constant array in a special section.
.. code-block:: cpp
// Note: LOG_INFO can be tokenized behind the macro; transparent to users.
PW_LOG_INFO("My temperature is %d. State: %s", temperature, state);
Additionally, while Pigweed is mostly C++, it a practical reality that at times
projects using Pigweed will need to log from third-party libraries written in
C. Thus, we also wanted to retain C compatibility.
In summary, printf-style logging is better for Pigweed's target audience
because it:
- works with tokenization
- is C compatibile
- has smaller call sites
The Pigweed authors additionally maintain a C++ stream-style embedded logging
library for compatibility with non-embedded code. While it is effective for
porting server code to microcontrollers quickly, we do not advise embedded
projects use that approach unless absolutely necessary.
- See also :ref:`chapter-pw-log-tokenized` for details on leveraging Pigweed's
tokenizer module for logging.
- See also :ref:`chapter-pw-tokenizer` for details on Pigweed's tokenizer,
which is useful for more than just logging.
Why does the facade use header redirection instead of C functions?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Without header redirection, it is not possible to do sophisticated macro
transforms in the backkend. For example, to apply tokenization to log strings,
the backend must define the handling macros. Additionally, compile-time
filtering by log level or flags is not possible without header redirection.
While it may be possible to do the filtering in the facade, that would imply
having the same filtering implementation for all log handling, which is a
restriction we want to avoid.
Why is the module name done as a preprocessor define rather than an argument?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
APIs are a balance between power and ease of use. In the practical cases we
have seen over the years, most translation units only need to log to one
module, like ``"BLE"``, ``"PWR"``, ``"BAT"`` and so on. Thus, adding the
argument to each macro call seemed like too much. On the other hand, flags are
something that are typically added on a per-log-statement basis, and is why the
flags are added on a per-call basis (though hidden through the high-level
macros).