Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions doc/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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` |
Expand All @@ -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` |

Expand Down Expand Up @@ -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
Expand Down
132 changes: 76 additions & 56 deletions include/ulog.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -485,94 +499,100 @@ 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)
{ (void)ev; (void)out; (void)out_size; return ULOG_STATUS_DISABLED; }

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)
Expand Down
52 changes: 52 additions & 0 deletions src/ulog.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1358,6 +1383,18 @@ 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) {
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
}
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);
}
Expand Down Expand Up @@ -1395,6 +1432,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");
Expand Down Expand Up @@ -1437,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
Expand Down Expand Up @@ -1535,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;
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading