Keir Mierle | af5e358 | 2019-12-30 13:11:05 -0800 | [diff] [blame] | 1 | .. _chapter-pw-log: |
| 2 | |
| 3 | .. default-domain:: cpp |
| 4 | |
| 5 | .. highlight:: cpp |
| 6 | |
| 7 | ------ |
| 8 | pw_log |
| 9 | ------ |
| 10 | Pigweed's logging module provides facilities for applications to log |
| 11 | information about the execution of their application. The module is split into |
| 12 | two components: |
| 13 | |
| 14 | 1. The facade (this module) which is only a macro interface layer |
| 15 | 2. The backend, provided elsewhere, that implements the low level logging |
| 16 | |
Keir Mierle | bc5a269 | 2020-05-21 16:52:25 -0700 | [diff] [blame] | 17 | Usage examples |
| 18 | -------------- |
| 19 | Here is a typical usage example, showing setting the module name, and using the |
| 20 | long-form names. |
Keir Mierle | af5e358 | 2019-12-30 13:11:05 -0800 | [diff] [blame] | 21 | |
| 22 | .. code-block:: cpp |
| 23 | |
| 24 | #define PW_LOG_MODULE_NAME "BLE" |
| 25 | #include "pw_log/log.h" |
| 26 | |
| 27 | int main() { |
| 28 | PW_LOG_INFO("Booting..."); |
Keir Mierle | bc5a269 | 2020-05-21 16:52:25 -0700 | [diff] [blame] | 29 | PW_LOG_DEBUG("CPU temp: %.2f", cpu_temperature); |
Keir Mierle | af5e358 | 2019-12-30 13:11:05 -0800 | [diff] [blame] | 30 | if (BootFailed()) { |
| 31 | PW_LOG_CRITICAL("Had trouble booting due to error %d", GetErrorCode()); |
| 32 | ReportErrorsAndHalt(); |
| 33 | } |
| 34 | PW_LOG_INFO("Successfully booted"); |
| 35 | } |
| 36 | |
Keir Mierle | bc5a269 | 2020-05-21 16:52:25 -0700 | [diff] [blame] | 37 | In ``.cc`` files, it is possible to dispense with the ``PW_`` part of the log |
| 38 | names and go for shorter log macros. |
| 39 | |
| 40 | .. code-block:: cpp |
| 41 | |
| 42 | #define PW_LOG_MODULE_NAME "BLE" |
| 43 | #define PW_LOG_USE_ULTRA_SHORT_NAMES 1 |
| 44 | #include "pw_log/log.h" |
| 45 | |
| 46 | int main() { |
| 47 | INF("Booting..."); |
| 48 | DBG("CPU temp: %.2f", cpu_temperature); |
| 49 | if (BootFailed()) { |
| 50 | CRT("Had trouble booting due to error %d", GetErrorCode()); |
| 51 | ReportErrorsAndHalt(); |
| 52 | } |
| 53 | INF("Successfully booted"); |
| 54 | } |
| 55 | |
| 56 | Layer diagram example: ``stm32f429i-disc1`` |
| 57 | ------------------------------------------- |
| 58 | Below is an example diagram showing how the modules connect together for the |
| 59 | ``stm32f429i-disc1`` target, where the ``pw_log`` backend is ``pw_log_basic``. |
| 60 | ``pw_log_basic`` uses the ``pw_sys_io`` module to log in plaintext, which in |
| 61 | turn outputs to the STM32F429 bare metal backend for ``pw_sys_io``, which is |
| 62 | ``pw_sys_io_baremetal_stm32f429i``. |
| 63 | |
| 64 | .. blockdiag:: |
| 65 | |
| 66 | blockdiag { |
| 67 | default_fontsize = 14; |
| 68 | orientation = portrait; |
| 69 | |
| 70 | group { |
| 71 | color = "#AAAAAA"; |
| 72 | label = "Microcontroller" |
| 73 | |
| 74 | app [label = "App code"]; |
| 75 | facade [label = "pw_log"]; |
| 76 | backend [label = "pw_log_basic"]; |
| 77 | sys_io [label = "pw_sys_io"]; |
| 78 | sys_io_bm [label = "pw_sys_io_\nstm32f429"]; |
| 79 | uart [label = "UART pins"]; |
| 80 | } |
| 81 | ftdi [label = "FTDI cable"]; |
| 82 | computer [label = "Minicom"]; |
| 83 | |
| 84 | app -> facade -> backend -> sys_io -> sys_io_bm -> uart -> ftdi -> computer; |
| 85 | |
| 86 | //app -> facade [folded]; |
| 87 | //backend -> sys_io [folded]; |
| 88 | //uart -> ftdi [folded]; |
| 89 | } |
Keir Mierle | af5e358 | 2019-12-30 13:11:05 -0800 | [diff] [blame] | 90 | |
| 91 | Logging macros |
| 92 | -------------- |
| 93 | |
Keir Mierle | bc5a269 | 2020-05-21 16:52:25 -0700 | [diff] [blame] | 94 | These are the primary macros for logging information about the functioning of a |
| 95 | system, intended to be used directly. |
| 96 | |
Keir Mierle | af5e358 | 2019-12-30 13:11:05 -0800 | [diff] [blame] | 97 | .. cpp:function:: PW_LOG(level, flags, fmt, ...) |
| 98 | |
| 99 | This is the primary mechanism for logging. |
| 100 | |
| 101 | *level* - An integer level as defined by ``pw_log/levels.h``. |
| 102 | |
| 103 | *flags* - Arbitrary flags the backend can leverage. The semantics of these |
| 104 | flags are not defined in the facade, but are instead meant as a general |
| 105 | mechanism for communication bits of information to the logging backend. |
| 106 | |
| 107 | Here are some ideas for what a backend might use flags for: |
| 108 | |
| 109 | - Example: ``HAS_PII`` - A log has personally-identifying data |
| 110 | - Example: ``HAS_DII`` - A log has device-identifying data |
| 111 | - Example: ``RELIABLE_DELIVERY`` - Ask the backend to ensure the log is |
| 112 | delivered; this may entail blocking other logs. |
| 113 | - Example: ``BEST_EFFORT`` - Don't deliver this log if it would mean blocking |
| 114 | or dropping important-flagged logs |
| 115 | |
| 116 | *fmt* - The message to log, which may contain format specifiers like ``%d`` |
| 117 | or ``%0.2f``. |
| 118 | |
| 119 | Example: |
| 120 | |
| 121 | .. code-block:: cpp |
| 122 | |
Keir Mierle | c4725d7 | 2020-06-04 12:17:38 -0700 | [diff] [blame] | 123 | PW_LOG(PW_LOG_DEFAULT_FLAGS, PW_LOG_LEVEL_INFO, "Temp is %d degrees", temp); |
Wyatt Hepler | e010bff | 2020-04-15 11:02:37 -0700 | [diff] [blame] | 124 | PW_LOG(UNRELIABLE_DELIVERY, PW_LOG_LEVEL_ERROR, "It didn't work!"); |
Keir Mierle | af5e358 | 2019-12-30 13:11:05 -0800 | [diff] [blame] | 125 | |
| 126 | .. note:: |
| 127 | |
| 128 | ``PW_LOG()`` should not be used frequently; typically only when adding |
| 129 | flags to a particular message to mark PII or to indicate delivery |
Keir Mierle | bc5a269 | 2020-05-21 16:52:25 -0700 | [diff] [blame] | 130 | guarantees. For most cases, prefer to use the direct ``PW_LOG_INFO`` or |
| 131 | ``PW_LOG_DEBUG`` style macros, which are often implemented more efficiently |
| 132 | in the backend. |
Keir Mierle | af5e358 | 2019-12-30 13:11:05 -0800 | [diff] [blame] | 133 | |
| 134 | |
| 135 | .. cpp:function:: PW_LOG_DEBUG(fmt, ...) |
| 136 | .. cpp:function:: PW_LOG_INFO(fmt, ...) |
| 137 | .. cpp:function:: PW_LOG_WARN(fmt, ...) |
| 138 | .. cpp:function:: PW_LOG_ERROR(fmt, ...) |
| 139 | .. cpp:function:: PW_LOG_CRITICAL(fmt, ...) |
| 140 | |
Keir Mierle | c4725d7 | 2020-06-04 12:17:38 -0700 | [diff] [blame] | 141 | Shorthand for `PW_LOG(PW_LOG_DEFAULT_FLAGS, <level>, fmt, ...)`. |
| 142 | |
| 143 | Filtering logs |
| 144 | -------------- |
| 145 | |
| 146 | ``pw_log`` supports compile time filtering of logs through two mechanisms. |
| 147 | |
| 148 | 1. Filter by level. Source files that define ``PW_LOG_LEVEL`` will display all |
| 149 | logs at or above the chosen level. |
| 150 | |
| 151 | Example: |
| 152 | |
| 153 | .. code-block:: cpp |
| 154 | |
| 155 | #include "pw_log/log.h" |
| 156 | |
| 157 | #define PW_LOG_LEVEL PW_LOG_LEVEL_INFO |
| 158 | |
| 159 | void DoSomething() { |
| 160 | PW_LOG_DEBUG("This won't be logged at all"); |
| 161 | PW_LOG_INFO("This is INFO level, and will display"); |
| 162 | PW_LOG_WARN("This is above INFO level, and will display"); |
| 163 | } |
| 164 | |
| 165 | 2. Filter by arbitrary expression based on ``level`` and ``flags``. Source |
| 166 | files that define ``PW_LOG_ENABLE_IF(level, flags)`` will display if the |
| 167 | given expression returns true. |
| 168 | |
| 169 | Example: |
| 170 | |
| 171 | .. code-block:: cpp |
| 172 | |
| 173 | #include "pw_log/log.h" |
| 174 | |
| 175 | // This define might be supplied by the build system. |
| 176 | #define MY_PRODUCT_LOG_PII_ENABLED false |
| 177 | |
| 178 | // This is the PII mask bit selected by the application. |
| 179 | #define MY_PRODUCT_PII_MASK (1 << 5) |
| 180 | |
| 181 | // Pigweed's log facade will call this macro to decide to log or not. In |
| 182 | // this case, it will drop logs with the PII flag set if display of PII is |
| 183 | // not enabled for the application. |
| 184 | #define PW_LOG_ENABLE_IF(level, flags) \ |
| 185 | (level >= PW_LOG_INFO && \ |
| 186 | !((flags & MY_PRODUCT_PII_MASK) && MY_PRODUCT_LOG_PII_ENABLED) |
| 187 | |
| 188 | void DoSomethingWithSensitiveInfo() { |
| 189 | PW_LOG_DEBUG("This won't be logged at all"); |
| 190 | PW_LOG_INFO("This is INFO level, and will display"); |
| 191 | |
| 192 | // In this example, this will not be logged since logging with PII |
| 193 | // is disabled by the above macros. |
| 194 | PW_LOG(PW_LOG_LEVEL_INFO, |
| 195 | MY_PRODUCT_PII_MASK, |
| 196 | "Sensitive: %d", |
| 197 | sensitive_info); |
| 198 | } |
| 199 | |
| 200 | .. attention:: |
| 201 | |
| 202 | At this time, only compile time filtering is supported. In the future, we |
| 203 | plan to add support for runtime filtering. |
Keir Mierle | af5e358 | 2019-12-30 13:11:05 -0800 | [diff] [blame] | 204 | |
| 205 | Logging attributes |
| 206 | ------------------ |
| 207 | |
| 208 | The logging facade in Pigweed is designed to facilitate the capture of at least |
| 209 | the following attributes: |
| 210 | |
| 211 | - *Level* - The log level; for example, INFO, DEBUG, ERROR, etc. Typically an |
| 212 | integer |
| 213 | - *Flags* - Bitset for e.g. RELIABLE_DELIVERY, or HAS_PII, or BEST_EFFORT |
| 214 | - *File* - The file where the log was triggered |
| 215 | - *Line* - The line number in the file where the log line occured |
| 216 | - *Function* - What function the log comes from. This is expensive in binary |
| 217 | size to use! |
| 218 | - *Module* - The user-defined module name for the log statement; e.g. “BLE” or |
| 219 | “BAT” |
| 220 | - *Message* - The message itself; with % format arguments |
| 221 | - *Arguments* - The format arguments to message |
| 222 | - *Thread* - For devices running with an RTOS, capturing the thread is very |
| 223 | useful |
| 224 | - *Others* - Processor security level? Maybe Thread is a good proxy for this |
| 225 | |
| 226 | Each backend may decide to capture different attributes to balance the tradeoff |
| 227 | between call site code size, call site run time, wire format size, logging |
| 228 | complexity, and more. |
Keir Mierle | bc5a269 | 2020-05-21 16:52:25 -0700 | [diff] [blame] | 229 | |
| 230 | Design discussion |
| 231 | ----------------- |
| 232 | |
| 233 | Why not use C++ style stream logging operators like Google Log? |
| 234 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 235 | There are multiple reasons to avoid the C++ stream logging style in embedded, |
| 236 | but the biggest reason is that C++ stream logging defeats log tokenization. By |
| 237 | having the string literals broken up between ``<<`` operators, tokenization |
| 238 | becomes impossible with current language features. |
| 239 | |
| 240 | Consider this example use of Google Log: |
| 241 | |
| 242 | .. code-block:: cpp |
| 243 | |
| 244 | LOG(INFO) << "My temperature is " << temperature << ". State: " << state; |
| 245 | |
| 246 | This log statement has two string literals. It might seem like one could convert |
| 247 | move to tokenization: |
| 248 | |
| 249 | .. code-block:: cpp |
| 250 | |
| 251 | LOG(INFO) << TOKEN("My temperature is ") << temperature << TOKEN(". State: ") << state; |
| 252 | |
| 253 | However, this doesn't work. The key problem is that the tokenization system |
| 254 | needs to allocate the string in a linker section that is excluded from the |
| 255 | final binary, but is in the final ELF executable (and so can be extracted). |
| 256 | Since there is no way to declare a string or array in a different section in |
| 257 | the middle of an experession in C++, it is not possible to tokenize an |
| 258 | expression like the above. |
| 259 | |
| 260 | In contrast, the ``printf``-style version is a single statement with a single |
| 261 | string constant, which can be expanded by the preprocessor (as part of |
| 262 | ``pw_tokenizer``) into a constant array in a special section. |
| 263 | |
| 264 | .. code-block:: cpp |
| 265 | |
| 266 | // Note: LOG_INFO can be tokenized behind the macro; transparent to users. |
| 267 | PW_LOG_INFO("My temperature is %d. State: %s", temperature, state); |
| 268 | |
| 269 | Additionally, while Pigweed is mostly C++, it a practical reality that at times |
| 270 | projects using Pigweed will need to log from third-party libraries written in |
| 271 | C. Thus, we also wanted to retain C compatibility. |
| 272 | |
| 273 | In summary, printf-style logging is better for Pigweed's target audience |
| 274 | because it: |
| 275 | |
| 276 | - works with tokenization |
| 277 | - is C compatibile |
| 278 | - has smaller call sites |
| 279 | |
| 280 | The Pigweed authors additionally maintain a C++ stream-style embedded logging |
| 281 | library for compatibility with non-embedded code. While it is effective for |
| 282 | porting server code to microcontrollers quickly, we do not advise embedded |
| 283 | projects use that approach unless absolutely necessary. |
| 284 | |
| 285 | - See also :ref:`chapter-pw-log-tokenized` for details on leveraging Pigweed's |
| 286 | tokenizer module for logging. |
| 287 | - See also :ref:`chapter-pw-tokenizer` for details on Pigweed's tokenizer, |
| 288 | which is useful for more than just logging. |
| 289 | |
| 290 | Why does the facade use header redirection instead of C functions? |
| 291 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 292 | Without header redirection, it is not possible to do sophisticated macro |
| 293 | transforms in the backkend. For example, to apply tokenization to log strings, |
| 294 | the backend must define the handling macros. Additionally, compile-time |
| 295 | filtering by log level or flags is not possible without header redirection. |
| 296 | While it may be possible to do the filtering in the facade, that would imply |
| 297 | having the same filtering implementation for all log handling, which is a |
| 298 | restriction we want to avoid. |
| 299 | |
| 300 | Why is the module name done as a preprocessor define rather than an argument? |
| 301 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 302 | APIs are a balance between power and ease of use. In the practical cases we |
| 303 | have seen over the years, most translation units only need to log to one |
| 304 | module, like ``"BLE"``, ``"PWR"``, ``"BAT"`` and so on. Thus, adding the |
| 305 | argument to each macro call seemed like too much. On the other hand, flags are |
| 306 | something that are typically added on a per-log-statement basis, and is why the |
| 307 | flags are added on a per-call basis (though hidden through the high-level |
| 308 | macros). |