Shrink the logger

* Move _write function to the log.c
* Change the way to setup the logger
* Update the readme
* Add terminal commands
This commit is contained in:
2023-10-31 21:14:02 +01:00
parent c9df2dee99
commit 11eba9dd84
4 changed files with 130 additions and 181 deletions

View File

@@ -52,38 +52,56 @@ You can control the verbosity of the logging output by setting a global log leve
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:
before you include `log.h`, you can define custom log levels by defining the following macros:
```c
logger_set_log_level(LOG_TAG, LOG_LEVEL);
// All log messages will be printed
#define LOGGER_LEVEL_ALL
#include "log.h"
```
```c
// Info and higher priority messages will be printed
#define LOGGER_LEVEL_INFO
#include "log.h"
```
```c
// Only warnings and errors will be printed
#define LOGGER_LEVEL_WARN
#include "log.h"
```
```c
// Only log messages with level ERROR will be printed
#define LOGGER_LEVEL_ERROR
#include "log.h"
```
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`.
For improved readability, log messages can be printed in color by configuring `LOGGER_USE_COLOR` to `1` in `log.h` or before you include `log.h`. \
Default is `0`
### 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)
- Log level ([Debug], [Info], [Warn], [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`
`[Info] (2009) [LTDC]: This is a log message`
### Example
```c
#define LOGGER_LEVEL_INFO
#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_LEVEL_INFO
logger_set_log_level(TAG, LOG_LEVEL_INFO);
LOG_DEBUG(TAG, "This message will not be printed");
LOG_INFO(TAG, "This message will be printed");
@@ -99,10 +117,10 @@ int main(void) {
```
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
[Info] (2009) main: This message will be printed
[Warning] (2026) main: This message will be printed
[Error] (2033) main: This message will be printed
[Info] (2040) main: Iteration 0 of 3
[Info] (2047) main: Iteration 1 of 3
[Info] (2054) main: Iteration 2 of 3
```

View File

@@ -12,64 +12,64 @@
#define LOG_H
#include <stdint.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/unistd.h>
/**
* @brief Log levels
*/
typedef enum {
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARN,
LOG_LEVEL_ERROR,
} log_level_t;
#ifdef LOGGER_LEVEL_ERROR
#define LOGGER_LEVEL 4
#endif
#ifdef LOGGER_LEVEL_WARN
#define LOGGER_LEVEL 3
#endif
#ifdef LOGGER_LEVEL_INFO
#define LOGGER_LEVEL 2
#endif
#ifdef LOGGER_LEVEL_ALL
#define LOGGER_LEVEL 1
#endif
/* 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.
*/
#ifndef LOGGER_LEVEL
#define LOGGER_LEVEL 3
#endif
#ifndef LOGGER_USE_COLOR
#define LOGGER_USE_COLOR 0
#endif
#define ANSI_ESC "\x1B"
#define ANSI_CSI "\x9B"
#define ANSI_DCS "\x90"
#define ANSI_OSC "\x9D"
#define CLEAR_SCREEN "\033c"
#define CURSOR_RESET ANSI_ESC "[H"
#define CURSOR_UP(n) ANSI_ESC "[" #n "A"
#define CURSOR_DOWN(n) ANSI_ESC "[" #n "B"
#define CURSOR_RIGHT(n) ANSI_ESC "[" #n "C"
#define CURSOR_LEFT(n) ANSI_ESC "[" #n "D"
#define CURSOR_NEXT_N_LINES(n) ANSI_ESC "[" #n "E"
#define CURSOR_PREV_N_LINES(n) ANSI_ESC "[" #n "F"
#define CURSOR_COL(n) ANSI_ESC "[" #n "G"
#define CURSOR_POS ANSI_ESC "[" #n ";" #n "H"
#define CURSOR_SAVE ANSI_ESC "7"
#define CURSOR_RESTORE ANSI_ESC "8"
#define ERASE_FROM_CURSOR_TO_END ANSI_ESC "[0J"
#define ERASE_FROM_CURSOR_TO_BEGINNING ANSI_ESC "[1J"
#define ERASE_ENTIRE_SCREEN ANSI_ESC "[2J"
#define ERASE_FROM_CURSOR_TO_END_LINE ANSI_ESC "[0K"
#define ERASE_FROM_CURSOR_TO_BEGINNING_LINE ANSI_ESC "[1K"
#define ERASE_ENTIRE_LINE ANSI_ESC "[2K"
#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
#define LOG_COLOR_D LOG_RESET_COLOR // Default
#else
#define LOG_RESET_COLOR
#define LOG_COLOR_E
@@ -78,41 +78,29 @@ log_level_t logger_get_log_level(const char* tag);
#define LOG_COLOR_D
#endif
#define LOG_FORMAT(letter, format) LOG_COLOR_##letter #letter " (%lu) %s: " format LOG_RESET_COLOR "\r\n"
#if LOGGER_LEVEL <= 1
#define LOG_DEBUG(tag, fmt, ...) printf(LOG_Color_D"[Debug] (%lu) [%s]: "fmt LOG_RESET_COLOR, logger_get_timestamp(), tag, ##__VA_ARGS__)
#else
#define LOG_DEBUG(tag, fmt, ...)
#endif
#if LOGGER_LEVEL <= 2
#define LOG_INFO(tag, fmt, ...) printf(LOG_COLOR_I"[Info] (%lu) [%s]: "fmt LOG_RESET_COLOR, logger_get_timestamp(), tag, ##__VA_ARGS__)
#else
#define LOG_INFO(tag, fmt, ...)
#endif
#if LOGGER_LEVEL <= 3
#define LOG_WARN(tag, fmt, ...) printf(LOG_COLOR_W"[Warning] (%lu) [%s]: "fmt LOG_RESET_COLOR, logger_get_timestamp(), tag, ##__VA_ARGS__)
#else
#define LOG_WARN(tag, fmt, ...)
#endif
#if LOGGER_LEVEL <= 4
#define LOG_ERROR(tag, fmt, ...) printf(LOG_COLOR_E"[Error] (%lu) [%s]: "fmt LOG_RESET_COLOR, logger_get_timestamp(), tag, ##__VA_ARGS__)
#else
#define LOG_ERROR(tag, fmt, ...)
#endif
#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)
/* For internal use only.
* Use the LOG_* macros instead e.g., LOG_DEBUG(TAG, "Debug message");
*/
#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
uint32_t logger_get_timestamp(void);
int _write(int file, char *data, int len);

View File

@@ -1,7 +1,7 @@
/**
* @file log.c
* @brief Logger implementation
* @author Lorenz C.
* @author Lorenz C. && Speetjens S.
*/
#include <stdarg.h>
@@ -10,71 +10,33 @@
#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;
}
extern UART_HandleTypeDef huart1;
/**
* @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();
int _write(int file, char *data, int len) {
HAL_StatusTypeDef status;
switch (file) {
case STDOUT_FILENO:
case STDERR_FILENO:
status = HAL_UART_Transmit(&huart1, (uint8_t*)data, len, HAL_MAX_DELAY);
if (status != HAL_OK) {
errno = EIO;
return -1;
}
break;
default:
errno = EBADF;
return -1;
}
return len;
}
/**
* @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);
}
uint32_t logger_get_timestamp(void) {
return HAL_GetTick();
}

View File

@@ -22,8 +22,7 @@
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <errno.h>
#include <sys/unistd.h>
#include "log.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
@@ -431,24 +430,6 @@ static void MX_GPIO_Init(void)
}
/* USER CODE BEGIN 4 */
int _write(int file, char *data, int len) {
HAL_StatusTypeDef status;
switch (file) {
case STDOUT_FILENO:
case STDERR_FILENO:
status = HAL_UART_Transmit(&huart1, (uint8_t*)data, len, HAL_MAX_DELAY);
if (status != HAL_OK) {
errno = EIO;
return -1;
}
break;
default:
errno = EBADF;
return -1;
}
return len;
}
/* USER CODE END 4 */
/**