From 420eeb64edaa69fcf228be3a69553f75fe850442 Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 15 May 2026 23:07:29 +0200 Subject: [PATCH 1/2] Add ulog_output_level_get() and ulog_topic_level_get() PR #160 --- doc/features.md | 20 ++++++ include/ulog.h | 132 +++++++++++++++++++++---------------- src/ulog.c | 43 ++++++++++++ tests/unit/test_output.cpp | 17 +++++ tests/unit/test_topics.cpp | 21 ++++++ 5 files changed, 177 insertions(+), 56 deletions(-) diff --git a/doc/features.md b/doc/features.md index e8f50d54..a1fc4020 100644 --- a/doc/features.md +++ b/doc/features.md @@ -168,6 +168,15 @@ In this case the stdout-printed line will be: INFO src/main.c:66: Info message 3.000000 ``` +The current log level of an output can be acquired using the `ulog_output_level_get(ulog_output_id output, ulog_level *level)` function: + +```c +ulog_level lvl; +ulog_output_level_get(ULOG_OUTPUT_STDOUT, &lvl); + +printf("loglevel: %s\n", ulog_level_to_string(lvl)); +``` + ### Events The events care information depending on the static configuration. The whole list of possible data: @@ -281,6 +290,7 @@ When the feature is enabled all logging macros are replaced with empty stubs or | ulog_log | `(void)0` | | ulog_output_add | `ULOG_OUTPUT_INVALID` | | ulog_output_add_file | `ULOG_OUTPUT_INVALID` | +| ulog_output_level_get | `ULOG_STATUS_DISABLED` | | ulog_output_level_set | `ULOG_STATUS_DISABLED` | | ulog_output_level_set_all | `ULOG_STATUS_DISABLED` | | ulog_output_remove | `ULOG_STATUS_DISABLED` | @@ -291,6 +301,7 @@ When the feature is enabled all logging macros are replaced with empty stubs or | ulog_topic_add | `ULOG_TOPIC_ID_INVALID` | | ulog_topic_config | `ULOG_STATUS_DISABLED` | | ulog_topic_get_id | `ULOG_TOPIC_ID_INVALID` | +| ulog_topic_level_get | `ULOG_STATUS_DISABLED` | | ulog_topic_level_set | `ULOG_STATUS_DISABLED` | | ulog_topic_remove | `ULOG_STATUS_DISABLED` | @@ -419,6 +430,15 @@ ulog_topic_info("network", "Connected to server"); // filtered out topic ulog_topic_debug("storage", "No free space"); // filtered out level DEBUG < INFO ``` +The current loglevel of each topic can be acquired using the `ulog_topic_level_get()` function: + +```c +ulog_level lvl; +ulog_topic_level_get("network", &lvl); + +printf("network loglevel: %s\n", ulog_level_to_string(lvl)); +``` + Topics can be removed by using the `ulog_topic_remove()` function. ### Extra Outputs diff --git a/include/ulog.h b/include/ulog.h index 9d0ca1b2..77129b44 100644 --- a/include/ulog.h +++ b/include/ulog.h @@ -283,6 +283,13 @@ typedef void (*ulog_output_handler_fn)(ulog_event *ev, void *arg); #if ULOG_BUILD_DISABLED != 1 +/// @brief Gets the current log level for a specific output +/// @param output Output handle to configure +/// @param level Current log level for this output will be stored here +/// @return ULOG_STATUS_OK on success, ULOG_STATUS_INVALID_ARGUMENT if invalid +/// parameters, ULOG_STATUS_NOT_FOUND if output not found +ulog_status ulog_output_level_get(ulog_output_id output, ulog_level *out_level); + /// @brief Sets the minimum log level for a specific output /// @param output Output handle to configure /// @param level Minimum log level for this output @@ -400,6 +407,13 @@ ulog_topic_id ulog_topic_add(const char *topic_name, ulog_output_id output, /// @return ULOG_STATUS_OK on success, ULOG_STATUS_NOT_FOUND if topic not found ulog_status ulog_topic_remove(const char *topic_name); +/// @brief Gets the current log level for a topic (requires +/// ULOG_BUILD_TOPICS!=0 or ULOG_BUILD_DYNAMIC_CONFIG=1) +/// @param topic_name Topic name string (empty or NULL names are invalid) +/// @param level Current log level for this topic will be stored here +/// @return ULOG_STATUS_OK on success, ULOG_STATUS_NOT_FOUND if topic not found +ulog_status ulog_topic_level_get(const char *topic_name, ulog_level *level); + /// @brief Sets the minimum log level for a topic (requires /// ULOG_BUILD_TOPICS!=0 or ULOG_BUILD_DYNAMIC_CONFIG=1) /// @param topic_name Topic name string (empty or NULL names are invalid) @@ -460,7 +474,7 @@ ulog_topic_id ulog_topic_get_id(const char *topic_name); /// @param ... Format arguments for the message void ulog_log(ulog_level level, const char *file, int line, const char *topic, const char *message, ...); - + /// @brief Clean up all topic, outputs and other dynamic resources ulog_status ulog_cleanup(void); @@ -485,28 +499,28 @@ ulog_status ulog_cleanup(void); #endif // clang-format off -ULOG_STATIC_INLINE ulog_status ulog_cleanup(void) +ULOG_STATIC_INLINE ulog_status ulog_cleanup(void) { return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_color_config(bool enabled) + +ULOG_STATIC_INLINE ulog_status ulog_color_config(bool enabled) { (void)enabled; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE const char* ulog_event_get_file(ulog_event *ev) + +ULOG_STATIC_INLINE const char* ulog_event_get_file(ulog_event *ev) { (void)ev; return ""; } - -ULOG_STATIC_INLINE ulog_level ulog_event_get_level(ulog_event *ev) + +ULOG_STATIC_INLINE ulog_level ulog_event_get_level(ulog_event *ev) { (void)ev; return ULOG_LEVEL_0; } - -ULOG_STATIC_INLINE int ulog_event_get_line(ulog_event *ev) + +ULOG_STATIC_INLINE int ulog_event_get_line(ulog_event *ev) { (void)ev; return -1; } - -ULOG_STATIC_INLINE ulog_status ulog_event_get_message(ulog_event *ev, char *buffer, size_t buffer_size) + +ULOG_STATIC_INLINE ulog_status ulog_event_get_message(ulog_event *ev, char *buffer, size_t buffer_size) { (void)ev; (void)buffer; (void)buffer_size; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE struct tm* ulog_event_get_time(ulog_event *ev) + +ULOG_STATIC_INLINE struct tm* ulog_event_get_time(ulog_event *ev) { (void)ev; return NULL; } - -ULOG_STATIC_INLINE ulog_topic_id ulog_event_get_topic(ulog_event *ev) + +ULOG_STATIC_INLINE ulog_topic_id ulog_event_get_topic(ulog_event *ev) { (void)ev; return ULOG_TOPIC_ID_INVALID; } ULOG_STATIC_INLINE ulog_status ulog_event_to_cstr(ulog_event *ev, char *out, size_t out_size) @@ -514,65 +528,71 @@ ULOG_STATIC_INLINE ulog_status ulog_event_to_cstr(ulog_event *ev, char *out, siz ULOG_STATIC_INLINE ulog_status ulog_event_to_cstr_colored(ulog_event *ev, char *out, size_t out_size) { (void)ev; (void)out; (void)out_size; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_level_config(ulog_level_config_style style) + +ULOG_STATIC_INLINE ulog_status ulog_level_config(ulog_level_config_style style) { (void)style; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_level_reset_levels(void) + +ULOG_STATIC_INLINE ulog_status ulog_level_reset_levels(void) { return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_level_set_new_levels(const ulog_level_descriptor *new_levels) + +ULOG_STATIC_INLINE ulog_status ulog_level_set_new_levels(const ulog_level_descriptor *new_levels) { (void)new_levels; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE const char* ulog_level_to_string(ulog_level level) + +ULOG_STATIC_INLINE const char* ulog_level_to_string(ulog_level level) { (void)level; return "?"; } - -ULOG_STATIC_INLINE ulog_status ulog_lock_set_fn(ulog_lock_fn function, void *lock_arg) + +ULOG_STATIC_INLINE ulog_status ulog_lock_set_fn(ulog_lock_fn function, void *lock_arg) { (void)function; (void)lock_arg; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE void ulog_log(ulog_level level, const char *file, int line, const char *topic, const char *message, ...) + +ULOG_STATIC_INLINE void ulog_log(ulog_level level, const char *file, int line, const char *topic, const char *message, ...) { (void)level; (void)file; (void)line; (void)topic; (void)message; } - -ULOG_STATIC_INLINE ulog_output_id ulog_output_add(ulog_output_handler_fn handler, void *arg, ulog_level level) + +ULOG_STATIC_INLINE ulog_output_id ulog_output_add(ulog_output_handler_fn handler, void *arg, ulog_level level) { (void)handler; (void)arg; (void)level; return ULOG_OUTPUT_INVALID; } - -ULOG_STATIC_INLINE ulog_output_id ulog_output_add_file(FILE *file, ulog_level level) + +ULOG_STATIC_INLINE ulog_output_id ulog_output_add_file(FILE *file, ulog_level level) { (void)file; (void)level; return ULOG_OUTPUT_INVALID; } - -ULOG_STATIC_INLINE ulog_status ulog_output_level_set(ulog_output_id output, ulog_level level) + +ULOG_STATIC_INLINE ulog_status ulog_output_level_get(ulog_output_id output, ulog_level *level) { (void)output; (void)level; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_output_level_set_all(ulog_level level) + +ULOG_STATIC_INLINE ulog_status ulog_output_level_set(ulog_output_id output, ulog_level level) + { (void)output; (void)level; return ULOG_STATUS_DISABLED; } + +ULOG_STATIC_INLINE ulog_status ulog_output_level_set_all(ulog_level level) { (void)level; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_output_remove(ulog_output_id output) + +ULOG_STATIC_INLINE ulog_status ulog_output_remove(ulog_output_id output) { (void)output; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_prefix_config(bool enabled) + +ULOG_STATIC_INLINE ulog_status ulog_prefix_config(bool enabled) { (void)enabled; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_prefix_set_fn(ulog_prefix_fn function) + +ULOG_STATIC_INLINE ulog_status ulog_prefix_set_fn(ulog_prefix_fn function) { (void)function; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_source_location_config(bool enabled) + +ULOG_STATIC_INLINE ulog_status ulog_source_location_config(bool enabled) { (void)enabled; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_time_config(bool enabled) + +ULOG_STATIC_INLINE ulog_status ulog_time_config(bool enabled) { (void)enabled; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_topic_id ulog_topic_add(const char *topic_name, ulog_output_id output, ulog_level level) + +ULOG_STATIC_INLINE ulog_topic_id ulog_topic_add(const char *topic_name, ulog_output_id output, ulog_level level) { (void)topic_name; (void)output; (void)level; return ULOG_TOPIC_ID_INVALID; } - -ULOG_STATIC_INLINE ulog_status ulog_topic_config(bool enabled) + +ULOG_STATIC_INLINE ulog_status ulog_topic_config(bool enabled) { (void)enabled; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_topic_id ulog_topic_get_id(const char *topic_name) + +ULOG_STATIC_INLINE ulog_topic_id ulog_topic_get_id(const char *topic_name) { (void)topic_name; return ULOG_TOPIC_ID_INVALID; } - -ULOG_STATIC_INLINE ulog_status ulog_topic_level_set(const char *topic_name, ulog_level level) + +ULOG_STATIC_INLINE ulog_status ulog_topic_level_set(const char *topic_name, ulog_level level) { (void)topic_name; (void)level; return ULOG_STATUS_DISABLED; } - -ULOG_STATIC_INLINE ulog_status ulog_topic_remove(const char *topic_name) + +ULOG_STATIC_INLINE ulog_status ulog_topic_level_get(const char *topic_name, ulog_level *level) + { (void)topic_name; (void)level; return ULOG_STATUS_DISABLED; } + +ULOG_STATIC_INLINE ulog_status ulog_topic_remove(const char *topic_name) { (void)topic_name; return ULOG_STATUS_DISABLED; } #define ulog_trace(...) ((void)0) diff --git a/src/ulog.c b/src/ulog.c index 211c7b3e..491d70ec 100644 --- a/src/ulog.c +++ b/src/ulog.c @@ -1029,6 +1029,18 @@ static void output_stdout_handler(ulog_event *ev, void *arg) { // Public // ================ +ulog_status ulog_output_level_get(ulog_output_id output, ulog_level *out_level) { + if (output < ULOG_OUTPUT_STDOUT || output >= OUTPUT_TOTAL_NUM || out_level == NULL) { + return ULOG_STATUS_INVALID_ARGUMENT; + } + + if (output_data.outputs[output].handler == NULL) { + return ULOG_STATUS_NOT_FOUND; // Output exists but no handler assigned + } + *out_level = output_data.outputs[output].level; + return ULOG_STATUS_OK; +} + ulog_status ulog_output_level_set(ulog_output_id output, ulog_level level) { if (!level_is_valid(level)) { return ULOG_STATUS_INVALID_ARGUMENT; @@ -1294,6 +1306,19 @@ static void topic_print(print_target *tgt, ulog_event *ev) { } } +/// @brief Get the topic level +/// @param topic - Topic ID +/// @param level - Current log level will be stored here +/// @return ULOG_STATUS_OK if success, ULOG_STATUS_NOT_FOUND if topic not found +static ulog_status topic_get_level(int topic, ulog_level *level) { + topic_t *t = topic_get(topic); + if (t != NULL) { + *level = t->level; + return ULOG_STATUS_OK; + } + return ULOG_STATUS_NOT_FOUND; +} + /// @brief Sets the topic level /// @param topic - Topic ID /// @param level - Log level to set @@ -1358,6 +1383,17 @@ ulog_status ulog_topic_level_set(const char *topic_name, ulog_level level) { return topic_set_level(topic_id, level); } +ulog_status ulog_topic_level_get(const char *topic_name, ulog_level *level) { + ulog_topic_id topic_id = ulog_topic_get_id(topic_name); + if (topic_id == ULOG_TOPIC_ID_INVALID) { + return ULOG_STATUS_NOT_FOUND; // Topic not found, do nothing + } + if (level == NULL) { + return ULOG_STATUS_INVALID_ARGUMENT; + } + return topic_get_level(topic_id, level); +} + ulog_topic_id ulog_topic_get_id(const char *topic_name) { return topic_str_to_id(topic_name); } @@ -1395,6 +1431,13 @@ ulog_status ulog_topic_level_set(const char *topic_name, ulog_level level) { return ULOG_STATUS_DISABLED; } +ulog_status ulog_topic_level_get(const char *topic_name, ulog_level *level) { + (void)(topic_name); + (void)(level); + warn_not_enabled("ULOG_BUILD_TOPICS_MODE"); + return ULOG_STATUS_DISABLED; +} + ulog_topic_id ulog_topic_get_id(const char *topic_name) { (void)(topic_name); warn_not_enabled("ULOG_BUILD_TOPICS_MODE"); diff --git a/tests/unit/test_output.cpp b/tests/unit/test_output.cpp index 44d4997f..ea223f2f 100644 --- a/tests/unit/test_output.cpp +++ b/tests/unit/test_output.cpp @@ -128,6 +128,23 @@ TEST_CASE_FIXTURE(OutputTestFixture, "Output Level Set Specific") { } } +TEST_CASE_FIXTURE(OutputTestFixture, "Output Level Get Specific") { + SUBCASE("Get stdout level") { + ulog_level level; + ulog_status result = ulog_output_level_set(ULOG_OUTPUT_STDOUT, ULOG_LEVEL_TRACE); + CHECK(result == ULOG_STATUS_OK); + result = ulog_output_level_get(ULOG_OUTPUT_STDOUT, &level); + CHECK(result == ULOG_STATUS_OK); + CHECK(level == ULOG_LEVEL_TRACE); + + result = ulog_output_level_set(ULOG_OUTPUT_STDOUT, ULOG_LEVEL_WARN); + CHECK(result == ULOG_STATUS_OK); + result = ulog_output_level_get(ULOG_OUTPUT_STDOUT, &level); + CHECK(result == ULOG_STATUS_OK); + CHECK(level == ULOG_LEVEL_WARN); + } +} + TEST_CASE_FIXTURE(OutputTestFixture, "Output Add Custom Handler") { SUBCASE("Add valid custom handler") { int test_arg = 42; diff --git a/tests/unit/test_topics.cpp b/tests/unit/test_topics.cpp index 4210e35a..95dd1c79 100644 --- a/tests/unit/test_topics.cpp +++ b/tests/unit/test_topics.cpp @@ -48,6 +48,27 @@ TEST_CASE_FIXTURE(TestFixture, "Topics: Levels") { CHECK(ut_callback_get_message_count() == 5); } +TEST_CASE_FIXTURE(TestFixture, "Topics: Get Level") { + ulog_level level; + ulog_status result; + + ulog_topic_add("topic", ULOG_OUTPUT_ALL, ULOG_LEVEL_TRACE); + + result = ulog_topic_level_set("topic", ULOG_LEVEL_TRACE); + CHECK(result == ULOG_STATUS_OK); + result = ulog_topic_level_get("topic", &level); + CHECK(result == ULOG_STATUS_OK); + CHECK(level == ULOG_LEVEL_TRACE); + + result = ulog_topic_level_set("topic", ULOG_LEVEL_WARN); + CHECK(result == ULOG_STATUS_OK); + result = ulog_topic_level_get("topic", &level); + CHECK(result == ULOG_STATUS_OK); + CHECK(level == ULOG_LEVEL_WARN); + + ulog_topic_remove("topic"); +} + TEST_CASE_FIXTURE(TestFixture, "Topics: Cannot create topic with empty name") { int res; From 260a76ce95d2d7f7ad1c377b63ec95a81d5deb5f Mon Sep 17 00:00:00 2001 From: Andrei Gramakov Date: Sat, 6 Jun 2026 11:51:07 +0200 Subject: [PATCH 2/2] Fix minor issues, update changelog --- CHANGELOG.md | 4 +++- src/ulog.c | 15 ++++++++++++--- tests/unit/test_topics.cpp | 13 +++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 808838fa..47f2f0b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [v7.1.0] - May 22, 2026 +## [v7.1.0] - June 06, 2026 ### Added - `ulog_event_to_cstr_colored()` — converts a log event to a string with ANSI colour codes, always emitting colour regardless of the runtime colour configuration +- `ulog_output_level_get()` — retrieves the current log level configured for a specific output +- `ulog_topic_level_get()` — retrieves the current log level configured for a named topic ### Fixed diff --git a/src/ulog.c b/src/ulog.c index 491d70ec..ade7e913 100644 --- a/src/ulog.c +++ b/src/ulog.c @@ -1384,13 +1384,14 @@ ulog_status ulog_topic_level_set(const char *topic_name, ulog_level level) { } ulog_status ulog_topic_level_get(const char *topic_name, ulog_level *level) { + if (is_str_empty(topic_name) || level == NULL) { + return ULOG_STATUS_INVALID_ARGUMENT; + } + ulog_topic_id topic_id = ulog_topic_get_id(topic_name); if (topic_id == ULOG_TOPIC_ID_INVALID) { return ULOG_STATUS_NOT_FOUND; // Topic not found, do nothing } - if (level == NULL) { - return ULOG_STATUS_INVALID_ARGUMENT; - } return topic_get_level(topic_id, level); } @@ -1480,6 +1481,10 @@ ulog_status ulog_topic_remove(const char *topic_name) { // ================ ulog_topic_id topic_str_to_id(const char *str) { + if (is_str_empty(str)) { + return ULOG_TOPIC_ID_INVALID; + } + for (int i = 0; i < TOPIC_STATIC_NUM; i++) { if (is_str_empty(topic_data.topics[i].name)) { continue; // Skip empty slot; continue searching @@ -1578,6 +1583,10 @@ static topic_t *topic_get_last(void) { } ulog_topic_id topic_str_to_id(const char *str) { + if (is_str_empty(str)) { + return ULOG_TOPIC_ID_INVALID; + } + for (topic_t *t = topic_get_first(); t != NULL; t = topic_get_next(t)) { if (!is_str_empty(t->name) && strcmp(t->name, str) == 0) { return t->id; diff --git a/tests/unit/test_topics.cpp b/tests/unit/test_topics.cpp index 95dd1c79..21eab2fe 100644 --- a/tests/unit/test_topics.cpp +++ b/tests/unit/test_topics.cpp @@ -69,6 +69,19 @@ TEST_CASE_FIXTURE(TestFixture, "Topics: Get Level") { ulog_topic_remove("topic"); } +TEST_CASE_FIXTURE(TestFixture, "Topics: Get Level Invalid Arguments") { + ulog_level level = ULOG_LEVEL_TRACE; + + ulog_topic_add("topic", ULOG_OUTPUT_ALL, ULOG_LEVEL_WARN); + + CHECK(ulog_topic_level_get(NULL, &level) == ULOG_STATUS_INVALID_ARGUMENT); + CHECK(ulog_topic_level_get("", &level) == ULOG_STATUS_INVALID_ARGUMENT); + CHECK(ulog_topic_level_get("topic", NULL) == ULOG_STATUS_INVALID_ARGUMENT); + CHECK(ulog_topic_level_get("missing", &level) == ULOG_STATUS_NOT_FOUND); + + ulog_topic_remove("topic"); +} + TEST_CASE_FIXTURE(TestFixture, "Topics: Cannot create topic with empty name") { int res;