Add a logging system for uniform log and debugging messages
This commit is contained in:
78
README.md
78
README.md
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
- lwip version 2.1.2
|
- lwip version 2.1.2
|
||||||
- CubeIDE version 1.12.1
|
- CubeIDE version 1.12.1
|
||||||
|
- STM32CubeMX version 6.8.1
|
||||||
- Firmware Lib (stm32f7) 1.17.1
|
- Firmware Lib (stm32f7) 1.17.1
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
@@ -28,3 +29,80 @@ You can choose from the following options:
|
|||||||
- `eclipse_format.xml`:
|
- `eclipse_format.xml`:
|
||||||
- For Eclipse-based editors, including STM32CubeIDE.
|
- For Eclipse-based editors, including STM32CubeIDE.
|
||||||
- You can import it within eclipse settings, `Preferences -> LANGUAGE -> Code Style -> Formatter` tab.
|
- 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
|
||||||
|
```
|
||||||
|
|||||||
118
project/Core/Inc/log.h
Normal file
118
project/Core/Inc/log.h
Normal file
@@ -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 <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
80
project/Core/Src/log.c
Normal file
80
project/Core/Src/log.c
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* @file log.c
|
||||||
|
* @brief Logger implementation
|
||||||
|
* @author Lorenz C.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user