From 808a1b8330662dae14892214edecc04c75344652 Mon Sep 17 00:00:00 2001 From: swoiszwillo Date: Tue, 18 Aug 2026 12:48:05 -0600 Subject: [PATCH 1/4] feat(zsys): colorize the log level indicator CONFIG_ZSYS_LOG_COLOR (default y) wraps the level token in ANSI codes -- red ERR, yellow WRN, green INF, uncolored DBG -- in both the sync ("E") and deferred ("") formats. Independent of ESP-IDF's CONFIG_LOG_COLORS, which only governs ESP_LOG* output. Backends get zsys_log_level_color() + ZSYS_LOG_COLOR_RESET rather than zsys_log_format_msg() growing a flag parameter, so the existing public formatter signature is unchanged. Part of #34. Co-Authored-By: Claude Opus 5 (1M context) --- components/zsys/Kconfig | 12 +++++++ .../zsys/include/boreas/zsys/log_backend.h | 25 +++++++++++++++ components/zsys/src/log.c | 32 +++++++++++++++++-- components/zsys/src/log_backend_esp.c | 4 +-- test/main/test_log.c | 30 +++++++++++++++++ 5 files changed, 98 insertions(+), 5 deletions(-) diff --git a/components/zsys/Kconfig b/components/zsys/Kconfig index 8ce1739..8061147 100644 --- a/components/zsys/Kconfig +++ b/components/zsys/Kconfig @@ -31,6 +31,18 @@ menu "Boreas System Services (zsys)" Maximum length of the pre-formatted text field in each log message. Messages longer than this are truncated. + config ZSYS_LOG_COLOR + bool "Colorize the log level indicator (ANSI/VT100)" + default y + depends on ZSYS_LOG_MODULE + help + Wrap the level indicator in ANSI color codes: red ERR, + yellow WRN, green INF, uncolored DBG. Applies to both the + sync ("E") and deferred ("") formats. + + Independent of ESP-IDF's CONFIG_LOG_COLORS, which only + governs ESP_LOG* output. + config ZSYS_LOG_MAX_BACKENDS int "Maximum number of log backends" default 4 diff --git a/components/zsys/include/boreas/zsys/log_backend.h b/components/zsys/include/boreas/zsys/log_backend.h index 9ed134a..e24ad2d 100644 --- a/components/zsys/include/boreas/zsys/log_backend.h +++ b/components/zsys/include/boreas/zsys/log_backend.h @@ -130,10 +130,35 @@ void zsys_log_backend_register(const struct log_backend *backend); * Default message formatter * -------------------------------------------------------------------------- */ +/* ANSI reset sequence, or "" when CONFIG_ZSYS_LOG_COLOR is disabled. Pair with + * zsys_log_level_color(). */ +#if defined(CONFIG_ZSYS_LOG_COLOR) +#define ZSYS_LOG_COLOR_RESET "\033[0m" +#else +#define ZSYS_LOG_COLOR_RESET "" +#endif + +/** + * ANSI color escape for a log level -- red ERR, yellow WRN, green INF, + * "" for everything else. Returns "" for every level when + * CONFIG_ZSYS_LOG_COLOR is disabled. + * + * Only for backends writing to a terminal; zsys_log_format_msg() already + * applies color itself, so a backend whose transport is not a terminal + * should strip it or format the message fields directly. + * + * @param level LOG_LEVEL_* value + * @return Escape sequence, never NULL. Close it with ZSYS_LOG_COLOR_RESET. + */ +const char *zsys_log_level_color(int level); + /** * Format a log message into a human-readable string. * Output: [12.345] module: message text * + * With CONFIG_ZSYS_LOG_COLOR the level token is wrapped in ANSI escapes + * (see zsys_log_level_color()), so the result is terminal-bound output. + * * @param msg Log message to format * @param buf Output buffer * @param buf_size Size of output buffer diff --git a/components/zsys/src/log.c b/components/zsys/src/log.c index 6ee3dfa..aa34443 100644 --- a/components/zsys/src/log.c +++ b/components/zsys/src/log.c @@ -82,6 +82,26 @@ static const char *level_to_str(int level) } } +/* ESP-IDF's LOG_COLOR_* macros are gated on CONFIG_LOG_COLORS (which governs + * ESP_LOG* only), so spell the codes out here to keep ZSYS_LOG_COLOR + * independent. Same sequences ESP-IDF emits. */ +const char *zsys_log_level_color(int level) +{ +#if defined(CONFIG_ZSYS_LOG_COLOR) + static const char *const colors[] = { + "", /* NONE */ + "\033[0;31m", /* ERR: red */ + "\033[0;33m", /* WRN: yellow */ + "\033[0;32m", /* INF: green */ + "", /* DBG: default */ + }; + return (level >= 0 && level <= LOG_LEVEL_DBG) ? colors[level] : ""; +#else + (void)level; + return ""; +#endif +} + void zsys_log_list_modules(void) { ESP_LOGI(TAG, "Registered log modules (%d):", module_count); @@ -388,9 +408,9 @@ uint32_t zsys_log_get_dropped_count(void) int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size) { uint32_t ms = (uint32_t)msg->timestamp_ms; - return snprintf(buf, buf_size, "[%lu.%03lu] <%s> %s: %s", (unsigned long)(ms / 1000), - (unsigned long)(ms % 1000), level_to_str(msg->level), msg->module, - msg->text); + return snprintf(buf, buf_size, "[%lu.%03lu] %s<%s>%s %s: %s", (unsigned long)(ms / 1000), + (unsigned long)(ms % 1000), zsys_log_level_color(msg->level), + level_to_str(msg->level), ZSYS_LOG_COLOR_RESET, msg->module, msg->text); } void zsys_log_hexdump(uint8_t level, const char *module, const void *data, size_t len, @@ -494,4 +514,10 @@ int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size) return 0; } +const char *zsys_log_level_color(int level) +{ + (void)level; + return ""; +} + #endif diff --git a/components/zsys/src/log_backend_esp.c b/components/zsys/src/log_backend_esp.c index f2e1782..868b0ba 100644 --- a/components/zsys/src/log_backend_esp.c +++ b/components/zsys/src/log_backend_esp.c @@ -39,8 +39,8 @@ static void esp_backend_put(const struct log_backend *backend, const struct log_ uint8_t lvl = (msg->level <= 4) ? msg->level : 0; /* Match standard ESP-IDF format: LETTER (timestamp_ms) tag: text */ - printf("%c (%lu) %s: %s\n", level_char[lvl], (unsigned long)esp_log_timestamp(), - msg->module, msg->text); + printf("%s%c%s (%lu) %s: %s\n", zsys_log_level_color(lvl), level_char[lvl], + ZSYS_LOG_COLOR_RESET, (unsigned long)esp_log_timestamp(), msg->module, msg->text); #endif } diff --git a/test/main/test_log.c b/test/main/test_log.c index 88b8a93..1135d91 100644 --- a/test/main/test_log.c +++ b/test/main/test_log.c @@ -181,6 +181,35 @@ static void test_log_format_msg(void) TEST_ASSERT_NOT_NULL(strstr(buf, "hello world")); } +static void test_log_level_color(void) +{ + struct log_msg msg = { + .timestamp_ms = 1, + .level = LOG_LEVEL_ERR, + .module = "mymod", + .thread = "main", + .text = "boom", + }; + + char buf[128]; + TEST_ASSERT_GREATER_THAN(0, zsys_log_format_msg(&msg, buf, sizeof(buf))); + +#if defined(CONFIG_ZSYS_LOG_COLOR) + /* ERR is red, and the level token stays intact between the escapes */ + TEST_ASSERT_EQUAL_STRING("\033[0;31m", zsys_log_level_color(LOG_LEVEL_ERR)); + TEST_ASSERT_EQUAL_STRING("\033[0;33m", zsys_log_level_color(LOG_LEVEL_WRN)); + TEST_ASSERT_EQUAL_STRING("\033[0;32m", zsys_log_level_color(LOG_LEVEL_INF)); + TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(LOG_LEVEL_DBG)); + TEST_ASSERT_NOT_NULL(strstr(buf, "\033[0;31m\033[0m")); +#else + TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(LOG_LEVEL_ERR)); + TEST_ASSERT_NULL(strchr(buf, '\033')); +#endif + /* Out-of-range levels must not index off the table */ + TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(-1)); + TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(99)); +} + static void test_log_thread_name(void) { capture_reset(); @@ -292,6 +321,7 @@ void test_log_group(void) RUN_TEST(test_log_runtime_level_filter); RUN_TEST(test_log_all_levels); RUN_TEST(test_log_format_msg); + RUN_TEST(test_log_level_color); RUN_TEST(test_log_thread_name); RUN_TEST(test_log_message_truncation); RUN_TEST(test_log_backend_count); From 075d04e96ec2382c8a8d2d485331f4e73b71c161 Mon Sep 17 00:00:00 2001 From: swoiszwillo Date: Tue, 18 Aug 2026 13:25:33 -0600 Subject: [PATCH 2/4] fix(zsys): per-backend color control, not a global format change Review found the first cut silently changed the contract of a public API documented at v0.1.0: zsys_log_format_msg() is the formatter the READMEs tell custom backends to use for "UART, RTT, network, file", and it began emitting ANSI escapes into all of them with no opt-out. Split it instead. zsys_log_format_msg() is unchanged and never colors; zsys_log_format_msg_color(msg, buf, size, bool color) is the per-backend switch, mirroring Zephyr's LOG_OUTPUT_FLAG_COLORS on struct log_output. CONFIG_ZSYS_LOG_COLOR stays a global off switch over both. The console backend opts in; a file or socket backend gets what it always got. Also from review: - Document the three divergences from Zephyr's log_output.c on the declaration: ESP-IDF's non-bold codes vs upstream's bold, INF colored by default where upstream gates it behind CONFIG_LOG_INFO_COLOR_GREEN, and level-token-only wrapping vs upstream's whole-line span. - Document why an uncolored level still emits a bare reset -- both ESP-IDF and Zephyr do the same, and it clears color left set by another writer on the same UART. - Assert the color/formatter coupling against the accessor rather than a literal, so the test body is exercised under both settings of CONFIG_ZSYS_LOG_COLOR instead of only the default; cover every level and pin that the plain formatter stays colorless. - Kconfig table in components/zsys/README.md and examples/log_demo, custom-backend snippets in both, CHANGELOG entry. - LOG_LEVEL_DBG instead of a bare 4 in the sync backend bounds check. Part of #34. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 10 +++ components/zsys/README.md | 12 ++++ .../zsys/include/boreas/zsys/log_backend.h | 64 +++++++++++++++---- components/zsys/src/log.c | 21 +++++- components/zsys/src/log_backend_esp.c | 6 +- examples/log_demo/README.md | 3 + test/main/test_log.c | 33 ++++++++-- 7 files changed, 129 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c33ff47..9bfd778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ the merge PR. ## Unreleased +- **Log level indicators are colorized by default** (#57) — + `CONFIG_ZSYS_LOG_COLOR=y` wraps the level token in ANSI codes on the console + backend: red ERR, yellow WRN, green INF, uncolored DBG. Set it to `n` to + restore plain output. Independent of ESP-IDF's `CONFIG_LOG_COLORS`, which + only governs `ESP_LOG*`. +- **New: `zsys_log_format_msg_color()`** (#57) — per-backend color control, + mirroring Zephyr's `LOG_OUTPUT_FLAG_COLORS`. `zsys_log_format_msg()` is + unchanged and still never emits color, so existing custom backends writing to + a file, socket or RTT channel need no action. + ## 0.1.0 — 2026-07-01 First tagged release. The 2026-06 hardening series is complete; the API diff --git a/components/zsys/README.md b/components/zsys/README.md index cd33d9d..078c290 100644 --- a/components/zsys/README.md +++ b/components/zsys/README.md @@ -78,6 +78,17 @@ static void my_put(const struct log_backend *b, const struct log_msg *msg) { zsys_log_format_msg(msg, buf, sizeof(buf)); uart_write(buf); } +``` + +`zsys_log_format_msg()` never emits color, so its output is safe for a file, +socket or RTT transport. A backend writing to a terminal calls +`zsys_log_format_msg_color(msg, buf, sizeof(buf), true)` instead -- the +per-backend switch Zephyr spells `LOG_OUTPUT_FLAG_COLORS`. Backends that format +the `log_msg` fields themselves can reach for `zsys_log_level_color()` and +`ZSYS_LOG_COLOR_RESET` directly. `CONFIG_ZSYS_LOG_COLOR=n` is a global off +switch over all three. + +```c static const struct log_backend_api my_api = { .put = my_put }; LOG_BACKEND_DEFINE(my_backend, &my_api, NULL); @@ -155,6 +166,7 @@ Requires `CONFIG_ZSYS_RETRY=y` (default). | `CONFIG_ZSYS_LOG_MODE_DEFERRED` | n | Deferred output via ring buffer + thread | | `CONFIG_ZSYS_LOG_BUFFER_COUNT` | 32 | Deferred queue depth | | `CONFIG_ZSYS_LOG_MSG_MAX_LEN` | 80 | Max text per message | +| `CONFIG_ZSYS_LOG_COLOR` | y | Colorize the level indicator (ANSI) | | `CONFIG_ZSYS_LOG_MAX_BACKENDS` | 4 | Max registered backends | | `CONFIG_ZSYS_LOG_THREAD_STACK_SIZE` | 2048 | Deferred output thread stack | | `CONFIG_ZSYS_LOG_THREAD_PRIORITY` | 2 | Deferred output thread priority | diff --git a/components/zsys/include/boreas/zsys/log_backend.h b/components/zsys/include/boreas/zsys/log_backend.h index e24ad2d..7a963b6 100644 --- a/components/zsys/include/boreas/zsys/log_backend.h +++ b/components/zsys/include/boreas/zsys/log_backend.h @@ -130,8 +130,11 @@ void zsys_log_backend_register(const struct log_backend *backend); * Default message formatter * -------------------------------------------------------------------------- */ -/* ANSI reset sequence, or "" when CONFIG_ZSYS_LOG_COLOR is disabled. Pair with - * zsys_log_level_color(). */ +/** + * @brief ANSI reset sequence, or "" when CONFIG_ZSYS_LOG_COLOR is disabled. + * + * Closes a sequence opened with zsys_log_level_color(). + */ #if defined(CONFIG_ZSYS_LOG_COLOR) #define ZSYS_LOG_COLOR_RESET "\033[0m" #else @@ -139,13 +142,29 @@ void zsys_log_backend_register(const struct log_backend *backend); #endif /** - * ANSI color escape for a log level -- red ERR, yellow WRN, green INF, - * "" for everything else. Returns "" for every level when - * CONFIG_ZSYS_LOG_COLOR is disabled. + * @brief ANSI color escape for a log level. + * + * Red ERR, yellow WRN, green INF, "" for DBG and everything else. Returns + * "" for every level when CONFIG_ZSYS_LOG_COLOR is disabled. For backends + * that format the log_msg fields themselves; backends using + * zsys_log_format_msg_color() get color applied for them. + * + * @note Diverges from Zephyr's log_output (subsys/logging/log_output.c) in + * three ways, all to match what ESP-IDF emits so a Boreas console is + * consistent with ESP_LOG* output on the same UART. Upstream uses bold + * codes ("\x1B[1;31m") where Boreas uses ESP-IDF's non-bold ("\033[0;31m"); + * upstream gates INF green behind CONFIG_LOG_INFO_COLOR_GREEN and DBG + * blue behind CONFIG_LOG_DBG_COLOR_BLUE, while Boreas colors INF by + * default and never colors DBG; and upstream spans the whole line + * (color_prefix() through postfix_print()) where Boreas wraps only the + * level token. * - * Only for backends writing to a terminal; zsys_log_format_msg() already - * applies color itself, so a backend whose transport is not a terminal - * should strip it or format the message fields directly. + * @note Levels with no color still pair with ZSYS_LOG_COLOR_RESET, so a DBG + * line carries a bare reset. Both references do the same -- ESP-IDF + * emits LOG_COLOR_D ("") followed by LOG_RESET_COLOR, and Zephyr's + * color_print() falls back to LOG_COLOR_CODE_DEFAULT whenever + * colors[level] is NULL. It also clears color left set by another + * writer on the same UART. * * @param level LOG_LEVEL_* value * @return Escape sequence, never NULL. Close it with ZSYS_LOG_COLOR_RESET. @@ -153,11 +172,13 @@ void zsys_log_backend_register(const struct log_backend *backend); const char *zsys_log_level_color(int level); /** - * Format a log message into a human-readable string. + * @brief Format a log message into a human-readable string. + * * Output: [12.345] module: message text * - * With CONFIG_ZSYS_LOG_COLOR the level token is wrapped in ANSI escapes - * (see zsys_log_level_color()), so the result is terminal-bound output. + * Never emits color, whatever CONFIG_ZSYS_LOG_COLOR is set to, so the result + * is safe for a file, network or RTT transport. Terminal-bound backends that + * want color call zsys_log_format_msg_color() instead. * * @param msg Log message to format * @param buf Output buffer @@ -167,6 +188,27 @@ const char *zsys_log_level_color(int level); */ int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size); +/** + * @brief Format a log message, optionally colorizing the level token. + * + * As zsys_log_format_msg(), but the caller decides whether the level token is + * wrapped in ANSI escapes -- the per-backend control Zephyr spells + * LOG_OUTPUT_FLAG_COLORS on a struct log_output. Color is applied only when + * @p color is true AND CONFIG_ZSYS_LOG_COLOR is enabled, so the Kconfig + * remains a global off switch. + * + * Deferred mode hands every backend the same struct log_msg, so this is where + * a terminal backend and a file backend part ways. + * + * @param msg Log message to format + * @param buf Output buffer + * @param buf_size Size of output buffer + * @param color Wrap the level token in ANSI escapes + * @return Number of characters written (excluding null terminator), or + * negative on error. May be >= buf_size if truncated. + */ +int zsys_log_format_msg_color(const struct log_msg *msg, char *buf, size_t buf_size, bool color); + #ifdef __cplusplus } #endif diff --git a/components/zsys/src/log.c b/components/zsys/src/log.c index aa34443..4fa13d8 100644 --- a/components/zsys/src/log.c +++ b/components/zsys/src/log.c @@ -405,12 +405,18 @@ uint32_t zsys_log_get_dropped_count(void) * Default message formatter * ------------------------------------------------------------------------- */ -int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size) +int zsys_log_format_msg_color(const struct log_msg *msg, char *buf, size_t buf_size, bool color) { uint32_t ms = (uint32_t)msg->timestamp_ms; return snprintf(buf, buf_size, "[%lu.%03lu] %s<%s>%s %s: %s", (unsigned long)(ms / 1000), - (unsigned long)(ms % 1000), zsys_log_level_color(msg->level), - level_to_str(msg->level), ZSYS_LOG_COLOR_RESET, msg->module, msg->text); + (unsigned long)(ms % 1000), color ? zsys_log_level_color(msg->level) : "", + level_to_str(msg->level), color ? ZSYS_LOG_COLOR_RESET : "", msg->module, + msg->text); +} + +int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size) +{ + return zsys_log_format_msg_color(msg, buf, buf_size, false); } void zsys_log_hexdump(uint8_t level, const char *module, const void *data, size_t len, @@ -506,6 +512,15 @@ void zsys_log_hexdump(uint8_t level, const char *module, const void *data, size_ (void)label; } +int zsys_log_format_msg_color(const struct log_msg *msg, char *buf, size_t buf_size, bool color) +{ + (void)msg; + (void)buf; + (void)buf_size; + (void)color; + return 0; +} + int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size) { (void)msg; diff --git a/components/zsys/src/log_backend_esp.c b/components/zsys/src/log_backend_esp.c index 868b0ba..9b1be76 100644 --- a/components/zsys/src/log_backend_esp.c +++ b/components/zsys/src/log_backend_esp.c @@ -17,6 +17,7 @@ #include #include "esp_log.h" +#include "zsys/log.h" #if defined(CONFIG_ZSYS_LOG_MODULE) @@ -32,11 +33,12 @@ static void esp_backend_put(const struct log_backend *backend, const struct log_ #if defined(CONFIG_ZSYS_LOG_MODE_DEFERRED) /* Structured format with the original log-time timestamp */ char buf[CONFIG_ZSYS_LOG_MSG_MAX_LEN + 64]; - zsys_log_format_msg(msg, buf, sizeof(buf)); + /* Console backend: a terminal, so opt in to color. */ + zsys_log_format_msg_color(msg, buf, sizeof(buf), true); printf("%s\n", buf); #else static const char level_char[] = {'?', 'E', 'W', 'I', 'D'}; - uint8_t lvl = (msg->level <= 4) ? msg->level : 0; + uint8_t lvl = (msg->level <= LOG_LEVEL_DBG) ? msg->level : 0; /* Match standard ESP-IDF format: LETTER (timestamp_ms) tag: text */ printf("%s%c%s (%lu) %s: %s\n", zsys_log_level_color(lvl), level_char[lvl], diff --git a/examples/log_demo/README.md b/examples/log_demo/README.md index 16d617d..29dcb07 100644 --- a/examples/log_demo/README.md +++ b/examples/log_demo/README.md @@ -72,6 +72,8 @@ I (575) log_demo: === Demo complete === static void my_put(const struct log_backend *b, const struct log_msg *msg) { char buf[128]; + /* Colorless -- safe for a file, socket or RTT channel. A terminal backend + calls zsys_log_format_msg_color(msg, buf, sizeof(buf), true) instead. */ zsys_log_format_msg(msg, buf, sizeof(buf)); my_transport_write(buf); /* UART, RTT, network, file, etc. */ } @@ -91,6 +93,7 @@ The backend is picked up automatically at `zsys_log_init()` time — on ESP targ | `CONFIG_ZSYS_LOG_MODE_DEFERRED` | n | Enable deferred mode (ring buffer + output thread) | | `CONFIG_ZSYS_LOG_BUFFER_COUNT` | 32 | Message queue depth (deferred mode) | | `CONFIG_ZSYS_LOG_MSG_MAX_LEN` | 80 | Max text length per message | +| `CONFIG_ZSYS_LOG_COLOR` | y | Colorize the level indicator (ANSI) | | `CONFIG_ZSYS_LOG_MAX_BACKENDS` | 4 | Maximum number of backends | When `CONFIG_ZSYS_LOG_MODULE` is disabled, `LOG_*` macros fall back to `ESP_LOG*` with zero overhead. diff --git a/test/main/test_log.c b/test/main/test_log.c index 1135d91..6e4c4db 100644 --- a/test/main/test_log.c +++ b/test/main/test_log.c @@ -9,6 +9,7 @@ #include "zsys/log.h" #include "zsys/log_backend.h" +#include #include /* ----------------------------------------------------------------------- @@ -183,6 +184,7 @@ static void test_log_format_msg(void) static void test_log_level_color(void) { + static const char *const names[] = {"NONE", "ERR", "WRN", "INF", "DBG"}; struct log_msg msg = { .timestamp_ms = 1, .level = LOG_LEVEL_ERR, @@ -190,21 +192,44 @@ static void test_log_level_color(void) .thread = "main", .text = "boom", }; - char buf[128]; + char plain[128]; + char expect[64]; + + /* The plain formatter is the pre-0.1.0 contract and must stay colorless + * whatever CONFIG_ZSYS_LOG_COLOR is set to -- backends writing to a file, + * socket or RTT channel depend on it. */ TEST_ASSERT_GREATER_THAN(0, zsys_log_format_msg(&msg, buf, sizeof(buf))); + TEST_ASSERT_NULL(strchr(buf, '\033')); + TEST_ASSERT_NOT_NULL(strstr(buf, "")); + + /* ...and is exactly what the color variant produces with color=false */ + TEST_ASSERT_GREATER_THAN(0, zsys_log_format_msg_color(&msg, plain, sizeof(plain), false)); + TEST_ASSERT_EQUAL_STRING(buf, plain); + + /* With color=true every level's token is wrapped in whatever escape this + * build defines. Asserted against the accessor rather than a literal, so + * it holds under both CONFIG_ZSYS_LOG_COLOR settings. */ + for (int lvl = LOG_LEVEL_NONE; lvl <= LOG_LEVEL_DBG; lvl++) { + msg.level = (uint8_t)lvl; + TEST_ASSERT_GREATER_THAN(0, + zsys_log_format_msg_color(&msg, buf, sizeof(buf), true)); + snprintf(expect, sizeof(expect), "%s<%s>%s", zsys_log_level_color(lvl), names[lvl], + ZSYS_LOG_COLOR_RESET); + TEST_ASSERT_NOT_NULL(strstr(buf, expect)); + } #if defined(CONFIG_ZSYS_LOG_COLOR) - /* ERR is red, and the level token stays intact between the escapes */ + /* Pin the palette: ESP-IDF's non-bold codes, DBG deliberately uncolored */ TEST_ASSERT_EQUAL_STRING("\033[0;31m", zsys_log_level_color(LOG_LEVEL_ERR)); TEST_ASSERT_EQUAL_STRING("\033[0;33m", zsys_log_level_color(LOG_LEVEL_WRN)); TEST_ASSERT_EQUAL_STRING("\033[0;32m", zsys_log_level_color(LOG_LEVEL_INF)); TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(LOG_LEVEL_DBG)); - TEST_ASSERT_NOT_NULL(strstr(buf, "\033[0;31m\033[0m")); #else TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(LOG_LEVEL_ERR)); - TEST_ASSERT_NULL(strchr(buf, '\033')); + TEST_ASSERT_EQUAL_STRING("", ZSYS_LOG_COLOR_RESET); #endif + /* Out-of-range levels must not index off the table */ TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(-1)); TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(99)); From 263512e6dd32c7e10b73a5a1b4bb3908e5ac8405 Mon Sep 17 00:00:00 2001 From: swoiszwillo Date: Tue, 18 Aug 2026 13:48:13 -0600 Subject: [PATCH 3/4] fix(zsys): null-terminate buf in the log-disabled formatter stubs With CONFIG_ZSYS_LOG_MODULE=n both formatter stubs returned 0 -- which claims "wrote an empty string" -- while leaving buf untouched, so a caller that printed buf on a non-negative return read uninitialized memory. Terminate it. zsys_log_format_msg() now delegates to the color variant in this block too, matching the enabled build. Not theoretical: a =n build compiles both symbols into libzsys.a. Reported by Copilot on #57. Co-Authored-By: Claude Opus 5 (1M context) --- components/zsys/src/log.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/zsys/src/log.c b/components/zsys/src/log.c index 4fa13d8..c5d6bb5 100644 --- a/components/zsys/src/log.c +++ b/components/zsys/src/log.c @@ -515,18 +515,20 @@ void zsys_log_hexdump(uint8_t level, const char *module, const void *data, size_ int zsys_log_format_msg_color(const struct log_msg *msg, char *buf, size_t buf_size, bool color) { (void)msg; - (void)buf; - (void)buf_size; (void)color; + + /* Returning 0 claims "wrote an empty string", so leave one behind -- + * a caller that prints buf on a non-negative return must not read + * uninitialized memory. */ + if (buf_size > 0) { + buf[0] = '\0'; + } return 0; } int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size) { - (void)msg; - (void)buf; - (void)buf_size; - return 0; + return zsys_log_format_msg_color(msg, buf, buf_size, false); } const char *zsys_log_level_color(int level) From b8c108c4c36c94f8ecddfcf425ac62b1d6d16c1e Mon Sep 17 00:00:00 2001 From: swoiszwillo Date: Tue, 18 Aug 2026 14:30:38 -0600 Subject: [PATCH 4/4] feat(zsys): use Zephyr's color conventions for LOG_* output The first cut reproduced ESP-IDF's palette, which is what #34 asked for. Reversed on review of the on-target capture: Boreas implements Zephyr's logging API, so LOG_* output should look like Zephyr's. ESP_LOG* traffic from ESP-IDF internals is left alone and keeps ESP-IDF's coloring. Verified against zephyr/subsys/logging/{log_output.c,Kconfig.formatting}: - Bold codes (LOG_COLOR_CODE_* copied verbatim, "\x1B[1;31m") rather than ESP-IDF's non-bold "\033[0;31m". - ERR red and WRN yellow only. INF and DBG are uncolored unless the new ZSYS_LOG_INFO_COLOR_GREEN / ZSYS_LOG_DBG_COLOR_BLUE are set, mirroring upstream's sub-options, which are likewise off by default. - Color spans the level indicator through the end of the message, leaving the timestamp uncolored: upstream calls color_prefix() after timestamp_print() and color_postfix() after the body. The sync format leads with the level, so there the span covers the whole line. - CONFIG_ZSYS_LOG_COLOR renamed CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR after upstream's CONFIG_LOG_BACKEND_SHOW_COLOR. Never released, so no migration note; help text is upstream's wording. The mixed console this produces is deliberate and documented on the declaration: a UART carrying both LOG_* and ESP_LOG* will show two palettes, and matching ESP-IDF would mean diverging from Zephyr for the API Boreas actually implements. Part of #34. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 16 ++++-- components/zsys/Kconfig | 30 ++++++++--- components/zsys/README.md | 4 +- .../zsys/include/boreas/zsys/log_backend.h | 52 +++++++++---------- components/zsys/src/log.c | 44 +++++++++++----- components/zsys/src/log_backend_esp.c | 8 +-- examples/log_demo/README.md | 4 +- test/main/test_log.c | 32 ++++++++---- 8 files changed, 126 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bfd778..92e1beb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,17 @@ the merge PR. ## Unreleased -- **Log level indicators are colorized by default** (#57) — - `CONFIG_ZSYS_LOG_COLOR=y` wraps the level token in ANSI codes on the console - backend: red ERR, yellow WRN, green INF, uncolored DBG. Set it to `n` to - restore plain output. Independent of ESP-IDF's `CONFIG_LOG_COLORS`, which - only governs `ESP_LOG*`. +- **LOG_* output is colorized by default, using Zephyr's palette** (#57) — + `CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR=y` (mirroring upstream's + `CONFIG_LOG_BACKEND_SHOW_COLOR`) prints errors in bold red and warnings in + bold yellow, spanning the level indicator through the end of the message with + the timestamp left uncolored. INF and DBG are uncolored unless + `CONFIG_ZSYS_LOG_INFO_COLOR_GREEN` / `CONFIG_ZSYS_LOG_DBG_COLOR_BLUE` are set, + as upstream. Set the parent symbol to `n` for plain output. + + Governs `LOG_*` (zsys) output only. `ESP_LOG*` traffic from ESP-IDF internals + keeps ESP-IDF's own non-bold palette under `CONFIG_LOG_COLORS`, so a console + carrying both will not look uniform. - **New: `zsys_log_format_msg_color()`** (#57) — per-backend color control, mirroring Zephyr's `LOG_OUTPUT_FLAG_COLORS`. `zsys_log_format_msg()` is unchanged and still never emits color, so existing custom backends writing to diff --git a/components/zsys/Kconfig b/components/zsys/Kconfig index 8061147..1963378 100644 --- a/components/zsys/Kconfig +++ b/components/zsys/Kconfig @@ -31,17 +31,33 @@ menu "Boreas System Services (zsys)" Maximum length of the pre-formatted text field in each log message. Messages longer than this are truncated. - config ZSYS_LOG_COLOR - bool "Colorize the log level indicator (ANSI/VT100)" + config ZSYS_LOG_BACKEND_SHOW_COLOR + bool "Colors in the backend" default y depends on ZSYS_LOG_MODULE help - Wrap the level indicator in ANSI color codes: red ERR, - yellow WRN, green INF, uncolored DBG. Applies to both the - sync ("E") and deferred ("") formats. + When enabled the backend prints errors in red and warnings in + yellow, mirroring Zephyr's CONFIG_LOG_BACKEND_SHOW_COLOR. As + upstream, the color spans the level indicator through the end + of the message, and INF/DBG are uncolored unless the options + below are set. + + Governs LOG_* (zsys) output only. ESP_LOG* traffic from + ESP-IDF internals keeps ESP-IDF's own coloring, controlled by + CONFIG_LOG_COLORS. + + config ZSYS_LOG_INFO_COLOR_GREEN + bool "Use green color for info level logs" + depends on ZSYS_LOG_BACKEND_SHOW_COLOR + help + Mirrors Zephyr's CONFIG_LOG_INFO_COLOR_GREEN. Off by default + upstream; enable for ESP-IDF-style output, where INF is green. - Independent of ESP-IDF's CONFIG_LOG_COLORS, which only - governs ESP_LOG* output. + config ZSYS_LOG_DBG_COLOR_BLUE + bool "Use blue color for debug level logs" + depends on ZSYS_LOG_BACKEND_SHOW_COLOR + help + Mirrors Zephyr's CONFIG_LOG_DBG_COLOR_BLUE. Off by default. config ZSYS_LOG_MAX_BACKENDS int "Maximum number of log backends" diff --git a/components/zsys/README.md b/components/zsys/README.md index 078c290..de12cc7 100644 --- a/components/zsys/README.md +++ b/components/zsys/README.md @@ -166,7 +166,9 @@ Requires `CONFIG_ZSYS_RETRY=y` (default). | `CONFIG_ZSYS_LOG_MODE_DEFERRED` | n | Deferred output via ring buffer + thread | | `CONFIG_ZSYS_LOG_BUFFER_COUNT` | 32 | Deferred queue depth | | `CONFIG_ZSYS_LOG_MSG_MAX_LEN` | 80 | Max text per message | -| `CONFIG_ZSYS_LOG_COLOR` | y | Colorize the level indicator (ANSI) | +| `CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR` | y | Color ERR red / WRN yellow (Zephyr palette) | +| `CONFIG_ZSYS_LOG_INFO_COLOR_GREEN` | n | Also color INF green | +| `CONFIG_ZSYS_LOG_DBG_COLOR_BLUE` | n | Also color DBG blue | | `CONFIG_ZSYS_LOG_MAX_BACKENDS` | 4 | Max registered backends | | `CONFIG_ZSYS_LOG_THREAD_STACK_SIZE` | 2048 | Deferred output thread stack | | `CONFIG_ZSYS_LOG_THREAD_PRIORITY` | 2 | Deferred output thread priority | diff --git a/components/zsys/include/boreas/zsys/log_backend.h b/components/zsys/include/boreas/zsys/log_backend.h index 7a963b6..c22d100 100644 --- a/components/zsys/include/boreas/zsys/log_backend.h +++ b/components/zsys/include/boreas/zsys/log_backend.h @@ -131,12 +131,12 @@ void zsys_log_backend_register(const struct log_backend *backend); * -------------------------------------------------------------------------- */ /** - * @brief ANSI reset sequence, or "" when CONFIG_ZSYS_LOG_COLOR is disabled. + * @brief ANSI reset sequence, or "" when color is disabled. * * Closes a sequence opened with zsys_log_level_color(). */ -#if defined(CONFIG_ZSYS_LOG_COLOR) -#define ZSYS_LOG_COLOR_RESET "\033[0m" +#if defined(CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR) +#define ZSYS_LOG_COLOR_RESET "\x1B[0m" #else #define ZSYS_LOG_COLOR_RESET "" #endif @@ -144,27 +144,24 @@ void zsys_log_backend_register(const struct log_backend *backend); /** * @brief ANSI color escape for a log level. * - * Red ERR, yellow WRN, green INF, "" for DBG and everything else. Returns - * "" for every level when CONFIG_ZSYS_LOG_COLOR is disabled. For backends - * that format the log_msg fields themselves; backends using + * Follows Zephyr: bold red ERR, bold yellow WRN, and nothing else, unless + * CONFIG_ZSYS_LOG_INFO_COLOR_GREEN / CONFIG_ZSYS_LOG_DBG_COLOR_BLUE are set. + * Returns "" for every level when CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR is off. + * + * For backends that format the log_msg fields themselves; backends using * zsys_log_format_msg_color() get color applied for them. * - * @note Diverges from Zephyr's log_output (subsys/logging/log_output.c) in - * three ways, all to match what ESP-IDF emits so a Boreas console is - * consistent with ESP_LOG* output on the same UART. Upstream uses bold - * codes ("\x1B[1;31m") where Boreas uses ESP-IDF's non-bold ("\033[0;31m"); - * upstream gates INF green behind CONFIG_LOG_INFO_COLOR_GREEN and DBG - * blue behind CONFIG_LOG_DBG_COLOR_BLUE, while Boreas colors INF by - * default and never colors DBG; and upstream spans the whole line - * (color_prefix() through postfix_print()) where Boreas wraps only the - * level token. - * - * @note Levels with no color still pair with ZSYS_LOG_COLOR_RESET, so a DBG - * line carries a bare reset. Both references do the same -- ESP-IDF - * emits LOG_COLOR_D ("") followed by LOG_RESET_COLOR, and Zephyr's + * @note Governs LOG_* (zsys) output only. ESP_LOG* traffic from ESP-IDF + * internals is colored by ESP-IDF under CONFIG_LOG_COLORS, which uses + * a different, non-bold palette. A console carrying both will not look + * uniform; that is deliberate, since matching ESP-IDF here would mean + * diverging from Zephyr for the API Boreas actually implements. + * + * @note A level with no color still pairs with ZSYS_LOG_COLOR_RESET, so an + * uncolored line carries a bare reset. Upstream does the same -- * color_print() falls back to LOG_COLOR_CODE_DEFAULT whenever - * colors[level] is NULL. It also clears color left set by another - * writer on the same UART. + * colors[level] is NULL, on the prefix and the postfix both -- and it + * clears color left set by another writer on the same UART. * * @param level LOG_LEVEL_* value * @return Escape sequence, never NULL. Close it with ZSYS_LOG_COLOR_RESET. @@ -176,9 +173,9 @@ const char *zsys_log_level_color(int level); * * Output: [12.345] module: message text * - * Never emits color, whatever CONFIG_ZSYS_LOG_COLOR is set to, so the result - * is safe for a file, network or RTT transport. Terminal-bound backends that - * want color call zsys_log_format_msg_color() instead. + * Never emits color, whatever the color options are set to, so the result is + * safe for a file, network or RTT transport. Terminal-bound backends that want + * color call zsys_log_format_msg_color() instead. * * @param msg Log message to format * @param buf Output buffer @@ -194,12 +191,15 @@ int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size); * As zsys_log_format_msg(), but the caller decides whether the level token is * wrapped in ANSI escapes -- the per-backend control Zephyr spells * LOG_OUTPUT_FLAG_COLORS on a struct log_output. Color is applied only when - * @p color is true AND CONFIG_ZSYS_LOG_COLOR is enabled, so the Kconfig - * remains a global off switch. + * @p color is true AND CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR is enabled, so the + * Kconfig remains a global off switch. * * Deferred mode hands every backend the same struct log_msg, so this is where * a terminal backend and a file backend part ways. * + * @note As upstream, the color spans the level indicator through the end of + * the message; the leading timestamp stays uncolored. + * * @param msg Log message to format * @param buf Output buffer * @param buf_size Size of output buffer diff --git a/components/zsys/src/log.c b/components/zsys/src/log.c index c5d6bb5..1b65455 100644 --- a/components/zsys/src/log.c +++ b/components/zsys/src/log.c @@ -82,18 +82,34 @@ static const char *level_to_str(int level) } } -/* ESP-IDF's LOG_COLOR_* macros are gated on CONFIG_LOG_COLORS (which governs - * ESP_LOG* only), so spell the codes out here to keep ZSYS_LOG_COLOR - * independent. Same sequences ESP-IDF emits. */ +/* Upstream's codes and names, verbatim from zephyr/subsys/logging/log_output.c. + * Deliberately NOT ESP-IDF's LOG_COLOR_* (esp_log_color.h): those are non-bold + * and gated on CONFIG_LOG_COLORS, which governs ESP_LOG* only. */ +#define LOG_COLOR_CODE_DEFAULT "\x1B[0m" +#define LOG_COLOR_CODE_RED "\x1B[1;31m" +#define LOG_COLOR_CODE_GREEN "\x1B[1;32m" +#define LOG_COLOR_CODE_YELLOW "\x1B[1;33m" +#define LOG_COLOR_CODE_BLUE "\x1B[1;34m" + const char *zsys_log_level_color(int level) { -#if defined(CONFIG_ZSYS_LOG_COLOR) +#if defined(CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR) + /* Mirrors upstream's colors[]: ERR and WRN only, unless the INF/DBG + * options are set. */ static const char *const colors[] = { - "", /* NONE */ - "\033[0;31m", /* ERR: red */ - "\033[0;33m", /* WRN: yellow */ - "\033[0;32m", /* INF: green */ - "", /* DBG: default */ + "", /* NONE */ + LOG_COLOR_CODE_RED, /* ERR */ + LOG_COLOR_CODE_YELLOW, /* WRN */ +#if defined(CONFIG_ZSYS_LOG_INFO_COLOR_GREEN) + LOG_COLOR_CODE_GREEN, /* INF */ +#else + "", /* INF */ +#endif +#if defined(CONFIG_ZSYS_LOG_DBG_COLOR_BLUE) + LOG_COLOR_CODE_BLUE, /* DBG */ +#else + "", /* DBG */ +#endif }; return (level >= 0 && level <= LOG_LEVEL_DBG) ? colors[level] : ""; #else @@ -408,10 +424,14 @@ uint32_t zsys_log_get_dropped_count(void) int zsys_log_format_msg_color(const struct log_msg *msg, char *buf, size_t buf_size, bool color) { uint32_t ms = (uint32_t)msg->timestamp_ms; - return snprintf(buf, buf_size, "[%lu.%03lu] %s<%s>%s %s: %s", (unsigned long)(ms / 1000), + + /* Upstream spans the color from the level indicator through the end of + * the message, leaving the timestamp uncolored -- color_prefix() runs + * after timestamp_print() and color_postfix() after the body. */ + return snprintf(buf, buf_size, "[%lu.%03lu] %s<%s> %s: %s%s", (unsigned long)(ms / 1000), (unsigned long)(ms % 1000), color ? zsys_log_level_color(msg->level) : "", - level_to_str(msg->level), color ? ZSYS_LOG_COLOR_RESET : "", msg->module, - msg->text); + level_to_str(msg->level), msg->module, msg->text, + color ? ZSYS_LOG_COLOR_RESET : ""); } int zsys_log_format_msg(const struct log_msg *msg, char *buf, size_t buf_size) diff --git a/components/zsys/src/log_backend_esp.c b/components/zsys/src/log_backend_esp.c index 9b1be76..3c20778 100644 --- a/components/zsys/src/log_backend_esp.c +++ b/components/zsys/src/log_backend_esp.c @@ -40,9 +40,11 @@ static void esp_backend_put(const struct log_backend *backend, const struct log_ static const char level_char[] = {'?', 'E', 'W', 'I', 'D'}; uint8_t lvl = (msg->level <= LOG_LEVEL_DBG) ? msg->level : 0; - /* Match standard ESP-IDF format: LETTER (timestamp_ms) tag: text */ - printf("%s%c%s (%lu) %s: %s\n", zsys_log_level_color(lvl), level_char[lvl], - ZSYS_LOG_COLOR_RESET, (unsigned long)esp_log_timestamp(), msg->module, msg->text); + /* Match standard ESP-IDF format: LETTER (timestamp_ms) tag: text. + * The level leads here, so upstream's "level indicator through end of + * message" span covers the whole line. */ + printf("%s%c (%lu) %s: %s%s\n", zsys_log_level_color(lvl), level_char[lvl], + (unsigned long)esp_log_timestamp(), msg->module, msg->text, ZSYS_LOG_COLOR_RESET); #endif } diff --git a/examples/log_demo/README.md b/examples/log_demo/README.md index 29dcb07..e3f917b 100644 --- a/examples/log_demo/README.md +++ b/examples/log_demo/README.md @@ -93,7 +93,9 @@ The backend is picked up automatically at `zsys_log_init()` time — on ESP targ | `CONFIG_ZSYS_LOG_MODE_DEFERRED` | n | Enable deferred mode (ring buffer + output thread) | | `CONFIG_ZSYS_LOG_BUFFER_COUNT` | 32 | Message queue depth (deferred mode) | | `CONFIG_ZSYS_LOG_MSG_MAX_LEN` | 80 | Max text length per message | -| `CONFIG_ZSYS_LOG_COLOR` | y | Colorize the level indicator (ANSI) | +| `CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR` | y | Color ERR red / WRN yellow (Zephyr palette) | +| `CONFIG_ZSYS_LOG_INFO_COLOR_GREEN` | n | Also color INF green | +| `CONFIG_ZSYS_LOG_DBG_COLOR_BLUE` | n | Also color DBG blue | | `CONFIG_ZSYS_LOG_MAX_BACKENDS` | 4 | Maximum number of backends | When `CONFIG_ZSYS_LOG_MODULE` is disabled, `LOG_*` macros fall back to `ESP_LOG*` with zero overhead. diff --git a/test/main/test_log.c b/test/main/test_log.c index 6e4c4db..a43d3c2 100644 --- a/test/main/test_log.c +++ b/test/main/test_log.c @@ -197,7 +197,7 @@ static void test_log_level_color(void) char expect[64]; /* The plain formatter is the pre-0.1.0 contract and must stay colorless - * whatever CONFIG_ZSYS_LOG_COLOR is set to -- backends writing to a file, + * whatever the color options are set to -- backends writing to a file, * socket or RTT channel depend on it. */ TEST_ASSERT_GREATER_THAN(0, zsys_log_format_msg(&msg, buf, sizeof(buf))); TEST_ASSERT_NULL(strchr(buf, '\033')); @@ -209,22 +209,36 @@ static void test_log_level_color(void) /* With color=true every level's token is wrapped in whatever escape this * build defines. Asserted against the accessor rather than a literal, so - * it holds under both CONFIG_ZSYS_LOG_COLOR settings. */ + * it holds under every color configuration. */ for (int lvl = LOG_LEVEL_NONE; lvl <= LOG_LEVEL_DBG; lvl++) { msg.level = (uint8_t)lvl; TEST_ASSERT_GREATER_THAN(0, zsys_log_format_msg_color(&msg, buf, sizeof(buf), true)); - snprintf(expect, sizeof(expect), "%s<%s>%s", zsys_log_level_color(lvl), names[lvl], - ZSYS_LOG_COLOR_RESET); + snprintf(expect, sizeof(expect), "%s<%s> mymod: boom%s", zsys_log_level_color(lvl), + names[lvl], ZSYS_LOG_COLOR_RESET); TEST_ASSERT_NOT_NULL(strstr(buf, expect)); + + /* Upstream leaves the timestamp outside the color */ + TEST_ASSERT_EQUAL('[', buf[0]); } -#if defined(CONFIG_ZSYS_LOG_COLOR) - /* Pin the palette: ESP-IDF's non-bold codes, DBG deliberately uncolored */ - TEST_ASSERT_EQUAL_STRING("\033[0;31m", zsys_log_level_color(LOG_LEVEL_ERR)); - TEST_ASSERT_EQUAL_STRING("\033[0;33m", zsys_log_level_color(LOG_LEVEL_WRN)); - TEST_ASSERT_EQUAL_STRING("\033[0;32m", zsys_log_level_color(LOG_LEVEL_INF)); +#if defined(CONFIG_ZSYS_LOG_BACKEND_SHOW_COLOR) + /* Pin upstream's palette: bold codes, ERR and WRN only by default */ + TEST_ASSERT_EQUAL_STRING("\x1B[1;31m", zsys_log_level_color(LOG_LEVEL_ERR)); + TEST_ASSERT_EQUAL_STRING("\x1B[1;33m", zsys_log_level_color(LOG_LEVEL_WRN)); + TEST_ASSERT_EQUAL_STRING("\x1B[0m", ZSYS_LOG_COLOR_RESET); + +#if defined(CONFIG_ZSYS_LOG_INFO_COLOR_GREEN) + TEST_ASSERT_EQUAL_STRING("\x1B[1;32m", zsys_log_level_color(LOG_LEVEL_INF)); +#else + TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(LOG_LEVEL_INF)); +#endif +#if defined(CONFIG_ZSYS_LOG_DBG_COLOR_BLUE) + TEST_ASSERT_EQUAL_STRING("\x1B[1;34m", zsys_log_level_color(LOG_LEVEL_DBG)); +#else TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(LOG_LEVEL_DBG)); +#endif + #else TEST_ASSERT_EQUAL_STRING("", zsys_log_level_color(LOG_LEVEL_ERR)); TEST_ASSERT_EQUAL_STRING("", ZSYS_LOG_COLOR_RESET);