blob: 01a0581773b65eaa1345fb2ceffc5ef9edc8bc17 [file] [log] [blame]
.. _module-pw_log:
======
pw_log
======
.. pigweed-module::
:name: 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
``pw_log`` also defines a logging protobuf, helper utilities, and an RPC
service for efficiently storing and transmitting log messages. See
:ref:`module-pw_log-protobuf` for details.
--------------
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. Include ``pw_log/short.h`` or
``pw_log/shorter.h`` for shorter versions of the macros.
.. warning::
The shorter log macros collide with `Abseil's logging API
<https://abseil.io/docs/cpp/guides/logging>`_. Do not use them in upstream
Pigweed modules, or any code that may depend on Abseil.
.. code-block:: cpp
#define PW_LOG_MODULE_NAME "BLE"
#include "pw_log/shorter.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");
}
The ``pw_log`` facade also exposes a handful of macros that only apply
specifically to tokenized logging. See :ref:`module-pw_log-tokenized-args` for
details.
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``.
.. image:: example_layer_diagram.svg
.. _module-pw_log-macros:
Logging macros
==============
These are the primary macros for logging information about the functioning of a
system, intended to be used directly.
.. c:macro:: PW_LOG(level, module, flags, fmt, ...)
This is the primary mechanism for logging.
*level* - An integer level as defined by ``pw_log/levels.h``.
*module* - A string literal for the module name. Defaults to
:c:macro:`PW_LOG_MODULE_NAME`.
*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.
``pw_log`` reserves 2 flag bits by default, but log backends may provide for
more or fewer flag bits.
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_LEVEL_INFO, PW_LOG_MODULE_NAME, PW_LOG_FLAGS, "Temp is %d degrees", temp);
PW_LOG(PW_LOG_LEVEL_ERROR, PW_LOG_MODULE_NAME, UNRELIABLE_DELIVERY, "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.
.. c:macro:: PW_LOG_DEBUG(fmt, ...)
.. c:macro:: PW_LOG_INFO(fmt, ...)
.. c:macro:: PW_LOG_WARN(fmt, ...)
.. c:macro:: PW_LOG_ERROR(fmt, ...)
.. c:macro:: PW_LOG_CRITICAL(fmt, ...)
Shorthand for ``PW_LOG(<level>, PW_LOG_MODULE_NAME, PW_LOG_FLAGS, fmt, ...)``.
.. c:macro:: PW_LOG_EVERY_N(level, rate, ...)
A simple rate limit logger which will simply log one out of every N logs.
*level* - An integer level as defined by ``pw_log/levels.h``.
*rate* - Rate to reduce logs to, every ``rate``-th log will complete, others
will be suppressed.
.. c:macro:: PW_LOG_EVERY_N_DURATION(level, min_interval_between_logs, msg, ...)
This is a rate-limited form of logging, especially useful for progressive
or chatty logs that should be logged periodically, but not on each instance
of the logger being called.
*level* - An integer level as defined by ``pw_log/levels.h``.
*min_interval_between_logs* - A ``std::chrono::duration`` of the minimum time
between logs. Logs attempted before this time duration will be completely
dropped.
Dropped logs will be counted to add a drop count and calculated rate of the
logs.
*msg* - Formattable log message, as you would pass to the above ``PW_LOG``
macro.
.. note::
``PW_LOG_EVERY_N`` is simpler, if you simply need to reduce uniformly
periodic logs by a fixed or variable factor not based explicitly on a
duration. Each call to the macro will incur a static ``uint32_t`` within
the calling context.
``PW_LOG_EVERY_N_DURATION`` is able to suppress all logs based on a time
interval, suppressing logs logging faster than the desired time interval.
Each call to the duration macro will incur a static 16 byte object to
track the time interval within the calling context.
Example:
.. code-block:: cpp
// Ensure at least 500ms between transfer parameter logs.
chrono::SystemClock::duration rate_limit_ =
chrono::SystemClock::for_at_least(std::chrono::milliseconds(500));
PW_LOG_EVERY_N_DURATION(PW_LOG_LEVEL_INFO,
rate_limit_,
"Transfer %u sending transfer parameters!"
static_cast<unsigned>(session_id_));
--------------------
Module configuration
--------------------
This module has configuration options that globally affect the behavior of
pw_log via compile-time configuration of this module, see the
:ref:`module documentation <module-structure-compile-time-configuration>` for
more details.
.. c:macro:: PW_LOG_LEVEL_DEFAULT
Controls the default value of ``PW_LOG_LEVEL``. Setting
``PW_LOG_LEVEL_DEFAULT`` will change the behavior of all source files that
have not explicitly set ``PW_LOG_LEVEL``. Defaults to ``PW_LOG_LEVEL_DEBUG``.
.. c:macro:: PW_LOG_FLAGS_DEFAULT
Controls the default value of ``PW_LOG_FLAGS``. Setting
``PW_LOG_FLAGS_DEFAULT`` will change the behavior of all source files that
have not explicitly set ``PW_LOG_FLAGS``. Defaults to ``0``.
.. c:macro:: PW_LOG_ENABLE_IF_DEFAULT
Controls the default value of ``PW_LOG_ENABLE_IF``. Setting
``PW_LOG_ENABLE_IF_DEFAULT`` will change the behavior of all source files that
have not explicitly set ``PW_LOG_ENABLE_IF``. Defaults to
``((level) >= PW_LOG_LEVEL)``.
Per-source file configuration
=============================
This module defines macros that can be overridden to independently control the
behavior of ``pw_log`` statements for each C or C++ source file. To override
these macros, add ``#define`` statements for them before including headers.
The option macro definitions must be visible to ``pw_log/log.h`` the first time
it is included. To handle potential transitive includes, place these
``#defines`` before all ``#include`` statements. This should only be done in
source files, not headers. For example:
.. code-block:: cpp
// Set the pw_log option macros here, before ALL of the #includes.
#define PW_LOG_MODULE_NAME "Calibration"
#define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
#include <array>
#include <random>
#include "devices/hal9000.h"
#include "pw_log/log.h"
#include "pw_rpc/server.h"
int MyFunction() {
PW_LOG_INFO("hello???");
}
.. c:macro:: PW_LOG_MODULE_NAME
A string literal module name to use in logs. Log backends may attach this
name to log messages or use it for runtime filtering. Defaults to ``""``. The
``PW_LOG_MODULE_NAME_DEFINED`` macro is set to ``1`` or ``0`` to indicate
whether ``PW_LOG_MODULE_NAME`` was overridden.
.. c:macro:: PW_LOG_FLAGS
Log flags to use for the ``PW_LOG_<level>`` macros. Different flags may be
applied when using the ``PW_LOG`` macro directly.
Log backends use flags to change how they handle individual log messages.
Potential uses include assigning logs priority or marking them as containing
personal information. Defaults to ``PW_LOG_FLAGS_DEFAULT``.
.. c:macro:: PW_LOG_LEVEL
Filters logs by level. Source files that define ``PW_LOG_LEVEL`` will display
only logs at or above the chosen level. Log statements below this level will
be compiled out of optimized builds. Defaults to ``PW_LOG_LEVEL_DEFAULT``.
Example:
.. code-block:: cpp
#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO
#include "pw_log/log.h"
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");
}
.. c:macro:: PW_LOG_ENABLE_IF(level, flags)
Filters logs by an arbitrary expression based on ``level`` and ``flags``.
Source files that define ``PW_LOG_ENABLE_IF(level, flags)`` will display if
the given expression evaluates true. Defaults to
``PW_LOG_ENABLE_IF_DEFAULT``.
Example:
.. code-block:: cpp
// 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_LEVEL_INFO && \
!((flags & MY_PRODUCT_PII_MASK) && MY_PRODUCT_LOG_PII_ENABLED)
#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)
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.
.. _module-pw_log-circular-deps:
----------------------------------------------
Avoiding circular dependencies with ``PW_LOG``
----------------------------------------------
Because logs are so widely used, including in low-level libraries, it is
common for the ``pw_log`` backend to cause circular dependencies. Because of
this, log backends may avoid declaring explicit dependencies, instead relying
on include paths to access header files.
GN
==
In GN, the ``pw_log`` backend's full implementation with true dependencies is
made available through the ``$dir_pw_log:impl`` group. When ``pw_log_BACKEND``
is set, ``$dir_pw_log:impl`` must be listed in the ``pw_build_LINK_DEPS``
variable. See :ref:`module-pw_build-link-deps`.
In the ``pw_log``, the backend's full implementation is placed in the
``$pw_log_BACKEND.impl`` target. ``$dir_pw_log:impl`` depends on this
backend target. The ``$pw_log_BACKEND.impl`` target may be an empty group if
the backend target can use its dependencies directly without causing circular
dependencies.
In order to break dependency cycles, the ``pw_log_BACKEND`` target may need
to directly provide dependencies through include paths only, rather than GN
``public_deps``. In this case, GN header checking can be disabled with
``check_includes = false``.
.. _module-pw_log-bazel-backend_impl:
Bazel
=====
In Bazel, log backends may avoid cyclic dependencies by placing the full
implementation in an ``impl`` target, like ``//pw_log_tokenized:impl``. The
``//pw_log:backend_impl`` label flag should be set to the ``impl`` target
required by the log backend used by the platform.
You must add a dependency on the ``@pigweed//pw_log:backend_impl`` target to
any binary using ``pw_log``.
See :ref:`docs-build_system-bazel_link-extra-lib` for a general discussion of
cyclic dependencies in low-level libraries in Bazel.
----------------------
Google Logging Adapter
----------------------
Pigweed provides a minimal C++ stream-style Google Log set of adapter
macros around PW_LOG under ``pw_log/glog_adapter.h`` 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.
Configuration
==============
.. c:macro:: PW_LOG_CFG_GLOG_BUFFER_SIZE_BYTES
The size of the stack-allocated buffer used by the Google Logging (glog)
macros. This only affects the glog macros provided through pw_log/glog.h.
Pigweed strongly recommends sticking to printf-style logging instead
of C++ stream-style Google Log logging unless absolutely necessary. The glog
macros are only provided for compatibility with non-embedded code. See
:ref:`module-pw_log-design-discussion` for more details.
Undersizing this buffer will result in truncated log messages.
-----------------
Design discussion
-----------------
.. _module-pw_log-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
See also :ref:`module-pw_log_tokenized` for details on leveraging Pigweed's
tokenizer module for logging.
See also :ref:`module-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 backend. 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).
--------------
pw_log in Java
--------------
``pw_log`` provides a thin Java logging class that uses Google's `Flogger
<https://google.github.io/flogger/>`_ API. The purpose of this wrapper is to
support logging on platforms that do not support Flogger. The main
implementation in ``pw_log/java/main`` simply wraps a
``com.google.common.flogger.FluentLogger``. An implementation that logs to
Android's ``android.util.Log`` instead is provided in
``pw_log/java/android_main``.
----------------
pw_log in Python
----------------
``pw_log`` provides utilities to decode ``LogEntries`` and the encapsulated
``LogEntry`` proto messages.
The ``Log`` class represents a decoded ``LogEntry`` in a human-readable and
consumable fashion.
The ``LogStreamDecoder`` offers APIs to decode ``LogEntries`` and ``LogEntry``
while tracking and logging log drops. It requires a ``decoded_log_handler`` to
pass decoded logs to. This class can also be customized to use an optional token
database if the message, module and thread names are tokenized; a custom
timestamp parser; and optional message parser for any extra message parsing.
``pw_log`` includes examples for customizing the ``LogStreamDecoder``:
``timestamp_parser_ns_since_boot`` parses the timestamp number from nanoseconds
since boot to an HH:MM::SS string, ``log_decoded_log`` emits a decoded ``Log``
to the provided logger in a format known to ``pw console``, and
``pw_status_code_to_name`` searches the decoded log message for a matching
pattern encapsulating the status code number and replaces it with the status
name.
Python API
==========
pw_log.log_decoder
------------------
.. automodule:: pw_log.log_decoder
:members: Log, LogStreamDecoder
:undoc-members:
:show-inheritance:
.. toctree::
:hidden:
:maxdepth: 1
protobuf
tokenized_args
Backends <backends>