feat: add log levels to logger
Replace log_message with log_debug/info/warn/err and a level filter controlled by logger_set_level. Extract the printf sink into log_write so CMock can intercept it in test_logger. Add 9 tests covering emit and suppression behaviour per level.
This commit is contained in:
@@ -91,6 +91,8 @@ to the custom scripts instead of system-installed packages.
|
|||||||
- Use conventional commit prefixes (`feat:`, `fix:`, `docs:`, `chore:`,
|
- Use conventional commit prefixes (`feat:`, `fix:`, `docs:`, `chore:`,
|
||||||
etc.)
|
etc.)
|
||||||
- Separate subject from body with a blank line
|
- Separate subject from body with a blank line
|
||||||
|
- Do **not** add yourself as a co-author (`Co-Authored-By:` trailers are
|
||||||
|
forbidden)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ add_subdirectory(ctdd)
|
|||||||
add_executable(main main.c)
|
add_executable(main main.c)
|
||||||
target_include_directories(main PRIVATE .)
|
target_include_directories(main PRIVATE .)
|
||||||
target_compile_features(main PRIVATE c_std_23)
|
target_compile_features(main PRIVATE c_std_23)
|
||||||
target_link_libraries(main PRIVATE ctdd_str ctdd_report ctdd_logger)
|
target_link_libraries(main PRIVATE ctdd_str ctdd_report ctdd_logger ctdd_log_write)
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|||||||
+7
-2
@@ -2,12 +2,17 @@ add_library(ctdd_str STATIC str.c)
|
|||||||
target_include_directories(ctdd_str PUBLIC "${CMAKE_SOURCE_DIR}")
|
target_include_directories(ctdd_str PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||||
target_compile_features(ctdd_str PRIVATE c_std_23)
|
target_compile_features(ctdd_str PRIVATE c_std_23)
|
||||||
|
|
||||||
# Reporter — calls log_message(); symbol resolved by the final binary
|
# Reporter — calls log_info(); symbol resolved by the final binary
|
||||||
add_library(ctdd_report STATIC report.c)
|
add_library(ctdd_report STATIC report.c)
|
||||||
target_include_directories(ctdd_report PUBLIC "${CMAKE_SOURCE_DIR}")
|
target_include_directories(ctdd_report PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||||
target_compile_features(ctdd_report PRIVATE c_std_23)
|
target_compile_features(ctdd_report PRIVATE c_std_23)
|
||||||
|
|
||||||
# Real log_message implementation — linked into production binaries only
|
# Level-filtering logger — calls log_write(); symbol resolved by the final binary
|
||||||
add_library(ctdd_logger STATIC logger.c)
|
add_library(ctdd_logger STATIC logger.c)
|
||||||
target_include_directories(ctdd_logger PUBLIC "${CMAKE_SOURCE_DIR}")
|
target_include_directories(ctdd_logger PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||||
target_compile_features(ctdd_logger PRIVATE c_std_23)
|
target_compile_features(ctdd_logger PRIVATE c_std_23)
|
||||||
|
|
||||||
|
# Real log_write implementation — linked into production binaries only
|
||||||
|
add_library(ctdd_log_write STATIC log_write.c)
|
||||||
|
target_include_directories(ctdd_log_write PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||||
|
target_compile_features(ctdd_log_write PRIVATE c_std_23)
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#include "ctdd/log_write.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void log_write(char const* msg) {
|
||||||
|
printf("%s\n", msg);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void log_write(char const* msg);
|
||||||
+14
-2
@@ -1,6 +1,18 @@
|
|||||||
#include "ctdd/logger.h"
|
#include "ctdd/logger.h"
|
||||||
|
#include "ctdd/log_write.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
void log_message(char const* msg) {
|
static log_level s_level = LOG_DEBUG;
|
||||||
printf("[LOG] %s\n", msg);
|
|
||||||
|
void logger_set_level(log_level level) { s_level = level; }
|
||||||
|
|
||||||
|
static void emit(char const* prefix, char const* msg) {
|
||||||
|
char buf[512];
|
||||||
|
snprintf(buf, sizeof(buf), "[%s] %s", prefix, msg);
|
||||||
|
log_write(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void log_debug(char const* msg) { if (s_level <= LOG_DEBUG) emit("DEBUG", msg); }
|
||||||
|
void log_info(char const* msg) { if (s_level <= LOG_INFO) emit("INFO", msg); }
|
||||||
|
void log_warn(char const* msg) { if (s_level <= LOG_WARN) emit("WARN", msg); }
|
||||||
|
void log_err(char const* msg) { if (s_level <= LOG_ERROR) emit("ERROR", msg); }
|
||||||
|
|||||||
+7
-1
@@ -1,3 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
void log_message(char const* msg);
|
typedef enum { LOG_DEBUG = 0, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_NONE } log_level;
|
||||||
|
|
||||||
|
void logger_set_level(log_level level);
|
||||||
|
void log_debug(char const* msg);
|
||||||
|
void log_info(char const* msg);
|
||||||
|
void log_warn(char const* msg);
|
||||||
|
void log_err(char const* msg);
|
||||||
|
|||||||
+1
-1
@@ -5,5 +5,5 @@
|
|||||||
void report_value(char const* label, int value) {
|
void report_value(char const* label, int value) {
|
||||||
char buf[256];
|
char buf[256];
|
||||||
snprintf(buf, sizeof(buf), "%s: %d", label, value);
|
snprintf(buf, sizeof(buf), "%s: %d", label, value);
|
||||||
log_message(buf);
|
log_info(buf);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) {
|
|||||||
|
|
||||||
char upper[32];
|
char upper[32];
|
||||||
str_upper(upper, "hello, tdd", sizeof(upper));
|
str_upper(upper, "hello, tdd", sizeof(upper));
|
||||||
log_message(upper);
|
log_info(upper);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ function(cmock_generate_mock target header)
|
|||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
target_sources("${target}" PRIVATE "${mock_src}")
|
target_sources("${target}" PRIVATE "${mock_src}")
|
||||||
# MOCK_GEN_DIR for the generated header; header_dir so the generated
|
|
||||||
# #include "logger.h" resolves without the ctdd/ prefix
|
|
||||||
target_include_directories("${target}" PRIVATE "${MOCK_GEN_DIR}" "${header_dir}")
|
target_include_directories("${target}" PRIVATE "${MOCK_GEN_DIR}" "${header_dir}")
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
@@ -37,10 +35,18 @@ target_link_libraries(test_str PRIVATE ctdd_str Unity::Unity)
|
|||||||
target_compile_features(test_str PRIVATE c_std_23)
|
target_compile_features(test_str PRIVATE c_std_23)
|
||||||
add_test(NAME test_str COMMAND test_str)
|
add_test(NAME test_str COMMAND test_str)
|
||||||
|
|
||||||
# report tests — CMock-generated mock for log_message
|
# report tests — mocks logger.h so log_info calls are intercepted
|
||||||
add_executable(test_report test_report.c)
|
add_executable(test_report test_report.c)
|
||||||
target_include_directories(test_report PRIVATE "${CMAKE_SOURCE_DIR}")
|
target_include_directories(test_report PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
target_link_libraries(test_report PRIVATE ctdd_report Unity::Unity CMock::CMock)
|
target_link_libraries(test_report PRIVATE ctdd_report Unity::Unity CMock::CMock)
|
||||||
target_compile_features(test_report PRIVATE c_std_23)
|
target_compile_features(test_report PRIVATE c_std_23)
|
||||||
cmock_generate_mock(test_report "${CMAKE_SOURCE_DIR}/ctdd/logger.h")
|
cmock_generate_mock(test_report "${CMAKE_SOURCE_DIR}/ctdd/logger.h")
|
||||||
add_test(NAME test_report COMMAND test_report)
|
add_test(NAME test_report COMMAND test_report)
|
||||||
|
|
||||||
|
# logger tests — mocks log_write.h so output calls are intercepted
|
||||||
|
add_executable(test_logger test_logger.c)
|
||||||
|
target_include_directories(test_logger PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
target_link_libraries(test_logger PRIVATE ctdd_logger Unity::Unity CMock::CMock)
|
||||||
|
target_compile_features(test_logger PRIVATE c_std_23)
|
||||||
|
cmock_generate_mock(test_logger "${CMAKE_SOURCE_DIR}/ctdd/log_write.h")
|
||||||
|
add_test(NAME test_logger COMMAND test_logger)
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#include "unity.h"
|
||||||
|
#include "ctdd/logger.h"
|
||||||
|
#include "Mocklog_write.h"
|
||||||
|
|
||||||
|
void setUp(void) { Mocklog_write_Init(); logger_set_level(LOG_DEBUG); }
|
||||||
|
void tearDown(void) { Mocklog_write_Verify(); Mocklog_write_Destroy(); }
|
||||||
|
|
||||||
|
void test_log_debug_emits_at_debug_level(void) {
|
||||||
|
log_write_Expect("[DEBUG] hello");
|
||||||
|
log_debug("hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_log_info_emits_at_debug_level(void) {
|
||||||
|
log_write_Expect("[INFO] world");
|
||||||
|
log_info("world");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_log_warn_emits_at_warn_level(void) {
|
||||||
|
logger_set_level(LOG_WARN);
|
||||||
|
log_write_Expect("[WARN] alert");
|
||||||
|
log_warn("alert");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_log_err_emits_at_warn_level(void) {
|
||||||
|
logger_set_level(LOG_WARN);
|
||||||
|
log_write_Expect("[ERROR] fatal");
|
||||||
|
log_err("fatal");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_log_debug_suppressed_at_info_level(void) {
|
||||||
|
logger_set_level(LOG_INFO);
|
||||||
|
log_debug("silent");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_log_info_suppressed_at_warn_level(void) {
|
||||||
|
logger_set_level(LOG_WARN);
|
||||||
|
log_info("silent");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_log_warn_suppressed_at_error_level(void) {
|
||||||
|
logger_set_level(LOG_ERROR);
|
||||||
|
log_warn("silent");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_log_none_suppresses_all(void) {
|
||||||
|
logger_set_level(LOG_NONE);
|
||||||
|
log_debug("silent");
|
||||||
|
log_info("silent");
|
||||||
|
log_warn("silent");
|
||||||
|
log_err("silent");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_level_can_be_raised_then_lowered(void) {
|
||||||
|
logger_set_level(LOG_ERROR);
|
||||||
|
log_info("silent");
|
||||||
|
logger_set_level(LOG_DEBUG);
|
||||||
|
log_write_Expect("[INFO] now visible");
|
||||||
|
log_info("now visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_log_debug_emits_at_debug_level);
|
||||||
|
RUN_TEST(test_log_info_emits_at_debug_level);
|
||||||
|
RUN_TEST(test_log_warn_emits_at_warn_level);
|
||||||
|
RUN_TEST(test_log_err_emits_at_warn_level);
|
||||||
|
RUN_TEST(test_log_debug_suppressed_at_info_level);
|
||||||
|
RUN_TEST(test_log_info_suppressed_at_warn_level);
|
||||||
|
RUN_TEST(test_log_warn_suppressed_at_error_level);
|
||||||
|
RUN_TEST(test_log_none_suppresses_all);
|
||||||
|
RUN_TEST(test_level_can_be_raised_then_lowered);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
+4
-4
@@ -6,22 +6,22 @@ void setUp(void) { Mocklogger_Init(); }
|
|||||||
void tearDown(void) { Mocklogger_Verify(); Mocklogger_Destroy(); }
|
void tearDown(void) { Mocklogger_Verify(); Mocklogger_Destroy(); }
|
||||||
|
|
||||||
void test_report_formats_label_and_value(void) {
|
void test_report_formats_label_and_value(void) {
|
||||||
log_message_Expect("count: 42");
|
log_info_Expect("count: 42");
|
||||||
report_value("count", 42);
|
report_value("count", 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_report_negative_value(void) {
|
void test_report_negative_value(void) {
|
||||||
log_message_Expect("score: -5");
|
log_info_Expect("score: -5");
|
||||||
report_value("score", -5);
|
report_value("score", -5);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_report_zero(void) {
|
void test_report_zero(void) {
|
||||||
log_message_Expect("total: 0");
|
log_info_Expect("total: 0");
|
||||||
report_value("total", 0);
|
report_value("total", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_report_calls_log_exactly_once(void) {
|
void test_report_calls_log_exactly_once(void) {
|
||||||
log_message_Expect("x: 1");
|
log_info_Expect("x: 1");
|
||||||
report_value("x", 1);
|
report_value("x", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user