From 5cd054dcedb0fe60e3902eeb1ff0a757691aaae0 Mon Sep 17 00:00:00 2001 From: L-diy Date: Tue, 31 Oct 2023 18:51:48 +0100 Subject: [PATCH] Add a logging system for uniform log and debugging messages --- README.md | 78 +++++++++++++++++++++++++++ project/Core/Inc/log.h | 118 +++++++++++++++++++++++++++++++++++++++++ project/Core/Src/log.c | 80 ++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 project/Core/Inc/log.h create mode 100644 project/Core/Src/log.c diff --git a/README.md b/README.md index 489eac3..6192e98 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ - lwip version 2.1.2 - CubeIDE version 1.12.1 + - STM32CubeMX version 6.8.1 - Firmware Lib (stm32f7) 1.17.1 ## Tasks @@ -28,3 +29,80 @@ You can choose from the following options: - `eclipse_format.xml`: - For Eclipse-based editors, including STM32CubeIDE. - You can import it within eclipse settings, `Preferences -> LANGUAGE -> Code Style -> Formatter` tab. + +## Logging and Debugging Messages +The logging system is designed to make log messages clearer and improve their uniformity across the project. +It makes it easier to see where a log message comes from and what its verbosity is. + +To use the logging system, include `log.h` in your source file, and use the following macros to print log messages: +```c +LOG_DEBUG(TAG, fmt, ...) +LOG_INFO(TAG, fmt, ...) +LOG_WARN(TAG, fmt, ...) +LOG_ERROR(TAG, fmt, ...) +``` +The same format specifiers as in `printf` can be used. + +The `TAG` parameter is a string that identifies the source of the log message. +It is recommended to use one tag for each source file / module, and to name the tag after the source file / module. +See the example below. + +### Global Log Level +You can control the verbosity of the logging output by setting a global log level in `log.h`. +This log level filters out messages with a lower priority. + +### Custom Log Levels +Additionally, you have the flexibility to override the log level for individual tags (but not lower than the global log level). +This can be archived by calling the following function: +```c +logger_set_level(LOG_TAG, LOG_LEVEL); +``` +If the log level for a tag is not set, the global log level is used. + +### Colorful Log Messages +For improved readability, log messages can be printed in color by configuring `LOGGER_USE_COLOR` to `1` in `log.h`. + +### Log Output Format +Each log entry is formatted to include the following information: + + - Log level (D for Debug, I for Info, W for Warning, E for Error) + - Timestamp (in milliseconds since boot) + - Tag + - The log message + +For instance, a log entry may look like this: + +`I (2009) LTDC: This is a log message` + +### Example +```c +#include "log.h" + +// Don't use a define for the tag, as the pointer to the tag is used +static const char *TAG = "main"; + +int main(void) { + // Set log level for tag "main" to LOG_INFO + logger_set_level(TAG, LOG_INFO); + + LOG_DEBUG(TAG, "This message will not be printed"); + LOG_INFO(TAG, "This message will be printed"); + LOG_WARN(TAG, "This message will be printed"); + LOG_ERROR(TAG, "This message will be printed"); + + for (int i = 0; i < 3; i++) { + LOG_INFO(TAG, "Iteration %d of %d", i, 3); + } + + return 0; +} +``` +Result: +``` +I (2009) main: This message will be printed +W (2026) main: This message will be printed +E (2033) main: This message will be printed +I (2040) main: Iteration 0 of 3 +I (2047) main: Iteration 1 of 3 +I (2054) main: Iteration 2 of 3 +``` diff --git a/project/Core/Inc/log.h b/project/Core/Inc/log.h new file mode 100644 index 0000000..cf7f4ed --- /dev/null +++ b/project/Core/Inc/log.h @@ -0,0 +1,118 @@ +/** + * @file log.h + * @brief Logger header + * @author Lorenz C. + * + * This logging library provides a simple logging interface with different verbosity levels. + * Each tag can have its own log level. + * Only messages with a log level greater or equal to the log level of the tag and the global log level will be printed. + */ + +#ifndef LOG_H +#define LOG_H + +#include + +/** + * @brief Log levels + */ +typedef enum { + LOG_LEVEL_DEBUG, + LOG_LEVEL_INFO, + LOG_LEVEL_WARN, + LOG_LEVEL_ERROR, +} log_level_t; + +/* For internal use only. + * Use the LOG_* macros instead e.g., LOG_DEBUG(TAG, "Debug message"); + */ +uint32_t logger_get_timestamp(void); +void logger_write(const char* format, ...); + +/** + * @brief Set the log level for a tag + * If the tag is not already in the list, it will be added. + * + * @param[in] tag The tag to set the log level for + * @param[in] level The log level to set (member of @ref log_level_t) + */ +void logger_set_log_level(const char* tag, log_level_t level); + +/** + * @brief Get the log level for a tag + * + * @param[in] tag The tag to get the log level for + * @return The log level for the tag (member of @ref log_level_t) + */ +log_level_t logger_get_log_level(const char* tag); + +/** + * @brief The global minimum log level. + * Messages with a log level lower than this will not be logged. + * And log statements with a log level lower than this will optimized away by the compiler. + */ +#define LOGGER_MIN_LOG_LEVEL LOG_LEVEL_DEBUG + +/** + * @brief The maximum number of tags that can be used. + */ +#define LOGGER_MAX_TAG_ENTRIES 20 + +/** + * @brief Whether to use color in the log output. + * This is only supported in terminals that support ANSI escape codes. + */ +#define LOGGER_USE_COLOR 0 + +#if LOGGER_USE_COLOR +#define LOG_RESET_COLOR "\033[0m" +#define LOG_COLOR_E "\033[0;31m" // Red +#define LOG_COLOR_W "\033[0;33m" // Brown +#define LOG_COLOR_I "\033[0;32m" // Green +#define LOG_COLOR_D +#else +#define LOG_RESET_COLOR +#define LOG_COLOR_E +#define LOG_COLOR_W +#define LOG_COLOR_I +#define LOG_COLOR_D +#endif + +#define LOG_FORMAT(letter, format) LOG_COLOR_##letter #letter " (%lu) %s: " format LOG_RESET_COLOR "\r\n" + +#define LOGGER_LOG(level, tag, format, ...) \ + do { \ + if (level >= LOGGER_MIN_LOG_LEVEL && level >= logger_get_log_level(tag)) { \ + if (level == LOG_LEVEL_DEBUG) { \ + logger_write(LOG_FORMAT(D, format), logger_get_timestamp(), tag, ##__VA_ARGS__); \ + } else if (level == LOG_LEVEL_INFO) { \ + logger_write(LOG_FORMAT(I, format), logger_get_timestamp(), tag, ##__VA_ARGS__); \ + } else if (level == LOG_LEVEL_WARN) { \ + logger_write(LOG_FORMAT(W, format), logger_get_timestamp(), tag, ##__VA_ARGS__); \ + } else if (level == LOG_LEVEL_ERROR) { \ + logger_write(LOG_FORMAT(E, format), logger_get_timestamp(), tag, ##__VA_ARGS__); \ + } \ + } \ + } while (0) + +/** + * @brief Macro to log a debug message (LOG_LEVEL_DEBUG) + */ +#define LOG_DEBUG(tag, format, ...) LOGGER_LOG(LOG_LEVEL_DEBUG, tag, format, ##__VA_ARGS__) + +/** + * @brief Macro to log an info message (LOG_LEVEL_INFO) + */ +#define LOG_INFO(tag, format, ...) LOGGER_LOG(LOG_LEVEL_INFO, tag, format, ##__VA_ARGS__) + +/** + * @brief Macro to log a warning message (LOG_LEVEL_WARN) + */ +#define LOG_WARN(tag, format, ...) LOGGER_LOG(LOG_LEVEL_WARN, tag, format, ##__VA_ARGS__) + +/** + * @brief Macro to log an error message (LOG_LEVEL_ERROR) + */ +#define LOG_ERROR(tag, format, ...) LOGGER_LOG(LOG_LEVEL_ERROR, tag, format, ##__VA_ARGS__) + +#endif // LOG_H diff --git a/project/Core/Src/log.c b/project/Core/Src/log.c new file mode 100644 index 0000000..1d90d5d --- /dev/null +++ b/project/Core/Src/log.c @@ -0,0 +1,80 @@ +/** + * @file log.c + * @brief Logger implementation + * @author Lorenz C. + */ + +#include +#include +#include +#include "stm32f7xx_hal.h" +#include "log.h" + +/** + * @brief Entry in the tag list containing the tag and the log level + */ +typedef struct { + const char* tag; + log_level_t level; +} tag_entry_t; + +static tag_entry_t tag_entries[LOGGER_MAX_TAG_ENTRIES]; +static size_t tag_entries_count = 0; +static const char* TAG = "logger"; + + +void logger_set_log_level(const char* tag, log_level_t level) { + // Check if the tag is already in the list + for (int i = 0; i < tag_entries_count; i++) { + if (tag_entries[i].tag == tag) { + tag_entries[i].level = level; + return; + } + } + + // The tag is not in the list yet, so add it + if (tag_entries_count < LOGGER_MAX_TAG_ENTRIES) { + tag_entries[tag_entries_count].tag = tag; + tag_entries[tag_entries_count].level = level; + tag_entries_count++; + } else { + LOG_WARN(TAG, "Could not add tag %s to list: List is full. Try increasing LOGGER_MAX_TAG_ENTRIES", tag); + } +} + +log_level_t logger_get_log_level(const char* tag) { + log_level_t level = LOGGER_MIN_LOG_LEVEL; + + // Try to find the tag in the list + for (int i = 0; i < tag_entries_count; i++) { + if (tag_entries[i].tag == tag) { + level = tag_entries[i].level; + break; + } + } + + return level; +} + +/** + * @brief Get the current timestamp to be used in the logger + * + * @return The current timestamp in milliseconds since boot + */ +uint32_t logger_get_timestamp(void) { + return HAL_GetTick(); +} + +/** + * @brief Write a log message to the console + * For now, this is just a wrapper around printf. + * + * @param[in] format The format string (see printf) + * @param[in] ... + */ +void logger_write(const char* format, ...) { + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); +}