diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8450693 --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +BasedOnStyle: LLVM +ColumnLimit: 100 +BinPackArguments: false +BinPackParameters: false +AllowAllArgumentsOnNextLine: false +AlignAfterOpenBracket: BlockIndent +UseTab: ForIndentation +IndentWidth: 4 +TabWidth: 4 +ContinuationIndentWidth: 4 +AllowShortFunctionsOnASingleLine: None diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d89c76d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{c,cc,cpp,h,hpp,ino}] +indent_style = tab +indent_size = tab +tab_width = 4 diff --git a/.gitignore b/.gitignore index 78f49b6..6346d5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .venv build/ build_prev_runner/ -.vscode \ No newline at end of file diff --git a/.vscode/bin/clang-format b/.vscode/bin/clang-format new file mode 100755 index 0000000..0df371f --- /dev/null +++ b/.vscode/bin/clang-format @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if command -v clang-format >/dev/null 2>&1; then + exec clang-format "$@" +fi + +_home_dir="${HOME:-}" +if [ -n "$_home_dir" ]; then + _candidate="$(ls -1d "$_home_dir"/.vscode/extensions/ms-vscode.cpptools-*-linux-x64/LLVM/bin/clang-format 2>/dev/null | tail -n 1 || true)" + if [ -n "$_candidate" ] && [ -x "$_candidate" ]; then + exec "$_candidate" "$@" + fi +fi + +echo "clang-format executable not found." >&2 +echo "Install clang-format system-wide or install/update ms-vscode.cpptools." >&2 +exit 127 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..f814711 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "pioarduino.pioarduino-ide", + "xaver.clang-format" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..24368c8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,30 @@ +{ + "files.associations": { + "*.ino": "cpp" + }, + "editor.defaultFormatter": "xaver.clang-format", + "C_Cpp.formatting": "Disabled", + "clang-format.style": "file", + "clang-format.executable": "${workspaceRoot}/.vscode/bin/clang-format", + "[cpp]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + }, + "[c]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + }, + "[arduino]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..20e66d5 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,12 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Format Firmware Sources", + "type": "shell", + "command": "bash ${workspaceFolder}/scripts/format_cpp.sh", + "group": "build", + "problemMatcher": [] + } + ] +} diff --git a/README.md b/README.md index c71a990..7dcb818 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,13 @@ ctest --test-dir build The suite exercises buffering, log level filtering, and sync behavior. Hardware smoke tests reside in `examples/`. +## Formatting Baseline + +This repository follows the firmware formatting baseline from `esptoolkit-template`: +- `.clang-format` is the source of truth for C/C++/INO layout. +- `.editorconfig` enforces tabs (`tab_width = 4`), LF endings, and final newline. +- Format all tracked firmware sources with `bash scripts/format_cpp.sh`. + ## License MIT — see [LICENSE.md](LICENSE.md). diff --git a/examples/basic_usage/basic_usage.ino b/examples/basic_usage/basic_usage.ino index 2ba6964..4a30a98 100644 --- a/examples/basic_usage/basic_usage.ino +++ b/examples/basic_usage/basic_usage.ino @@ -5,23 +5,26 @@ ESPLogger logger; class HeartbeatReporter { - public: - explicit HeartbeatReporter(ESPLogger& logger) : _logger(logger) {} - - void log(uint32_t counter) { - _logger.info("APP", "Heartbeat %lu", static_cast(counter)); - - if (counter % 5 == 0) { - _logger.warn("APP", "Simulated warning at count %lu", static_cast(counter)); - } - - if (counter % 9 == 0) { - _logger.error("APP", "Simulated error at count %lu", static_cast(counter)); - } - } - - private: - ESPLogger& _logger; + public: + explicit HeartbeatReporter(ESPLogger &logger) : _logger(logger) { + } + + void log(uint32_t counter) { + _logger.info("APP", "Heartbeat %lu", static_cast(counter)); + + if (counter % 5 == 0) { + _logger + .warn("APP", "Simulated warning at count %lu", static_cast(counter)); + } + + if (counter % 9 == 0) { + _logger + .error("APP", "Simulated error at count %lu", static_cast(counter)); + } + } + + private: + ESPLogger &_logger; }; static HeartbeatReporter heartbeat(logger); @@ -30,47 +33,51 @@ static HeartbeatReporter heartbeat(logger); static std::vector lastSyncedLogs; static bool loggerStopped = false; -void logSyncCallback(const std::vector& logs) { - lastSyncedLogs = logs; - Serial.printf("Synced %u log entries\n", static_cast(logs.size())); +void logSyncCallback(const std::vector &logs) { + lastSyncedLogs = logs; + Serial.printf("Synced %u log entries\n", static_cast(logs.size())); } void setup() { - Serial.begin(115200); + Serial.begin(115200); - // Quick start: omit the config entirely and call logger.init() to use all defaults. - LoggerConfig config; - config.syncIntervalMS = 3000; // sync every 3 seconds - config.maxLogInRam = 25; // keep a small buffer in RAM - config.consoleLogLevel = LogLevel::Info; + // Quick start: omit the config entirely and call logger.init() to use all defaults. + LoggerConfig config; + config.syncIntervalMS = 3000; // sync every 3 seconds + config.maxLogInRam = 25; // keep a small buffer in RAM + config.consoleLogLevel = LogLevel::Info; - if (!logger.init(config)) { - Serial.println("Failed to initialise logger!"); - return; - } + if (!logger.init(config)) { + Serial.println("Failed to initialise logger!"); + return; + } - logger.onSync(logSyncCallback); + logger.onSync(logSyncCallback); - logger.info("INIT", "ESPLogger ready. Max buffer: %u", static_cast(config.maxLogInRam)); + logger + .info("INIT", "ESPLogger ready. Max buffer: %u", static_cast(config.maxLogInRam)); } void loop() { - static uint32_t counter = 0; - if (loggerStopped) { - delay(1000); - return; - } - - logger.debug("LOOP", "This debug message only shows when consoleLogLevel <= Debug (%lu)", - static_cast(counter)); - - heartbeat.log(counter); - - counter++; - if (counter >= 30) { - logger.deinit(); - loggerStopped = true; - Serial.println("Logger deinitialized after demo run"); - } - delay(1000); + static uint32_t counter = 0; + if (loggerStopped) { + delay(1000); + return; + } + + logger.debug( + "LOOP", + "This debug message only shows when consoleLogLevel <= Debug (%lu)", + static_cast(counter) + ); + + heartbeat.log(counter); + + counter++; + if (counter >= 30) { + logger.deinit(); + loggerStopped = true; + Serial.println("Logger deinitialized after demo run"); + } + delay(1000); } diff --git a/examples/custom_sync/custom_sync.ino b/examples/custom_sync/custom_sync.ino index c0f3ebb..8d5d53d 100644 --- a/examples/custom_sync/custom_sync.ino +++ b/examples/custom_sync/custom_sync.ino @@ -5,16 +5,17 @@ ESPLogger logger; class SensorSampler { - public: - explicit SensorSampler(ESPLogger& logger) : _logger(logger) {} + public: + explicit SensorSampler(ESPLogger &logger) : _logger(logger) { + } - void logReading() { - const float reading = analogRead(A0) / 1023.0f; - _logger.debug("DATA", "Sensor reading: %0.2f", reading); - } + void logReading() { + const float reading = analogRead(A0) / 1023.0f; + _logger.debug("DATA", "Sensor reading: %0.2f", reading); + } - private: - ESPLogger& _logger; + private: + ESPLogger &_logger; }; static SensorSampler sampler(logger); @@ -24,57 +25,59 @@ constexpr uint32_t kManualSyncIntervalMS = 5000; uint32_t lastSyncMs = 0; uint32_t sampleCount = 0; bool loggerStopped = false; -} // namespace - -void persistLogs(const std::vector& logs) { - Serial.printf("Persisting %u buffered entries\n", static_cast(logs.size())); - for (const auto& entry : logs) { - Serial.printf(" [%u][%ld][%s] %s\n", - static_cast(entry.millis), - static_cast(entry.timestamp), - entry.tag.c_str(), - entry.message.c_str()); - } +} // namespace + +void persistLogs(const std::vector &logs) { + Serial.printf("Persisting %u buffered entries\n", static_cast(logs.size())); + for (const auto &entry : logs) { + Serial.printf( + " [%u][%ld][%s] %s\n", + static_cast(entry.millis), + static_cast(entry.timestamp), + entry.tag.c_str(), + entry.message.c_str() + ); + } } void setup() { - Serial.begin(115200); + Serial.begin(115200); - // Defaults are fine for many cases; call logger.init() without a config to use them. - LoggerConfig config; - config.enableSyncTask = false; // we will drive sync manually - config.maxLogInRam = 50; - config.consoleLogLevel = LogLevel::Debug; + // Defaults are fine for many cases; call logger.init() without a config to use them. + LoggerConfig config; + config.enableSyncTask = false; // we will drive sync manually + config.maxLogInRam = 50; + config.consoleLogLevel = LogLevel::Debug; - if (!logger.init(config)) { - Serial.println("ESPLogger init failed"); - return; - } + if (!logger.init(config)) { + Serial.println("ESPLogger init failed"); + return; + } - logger.onSync(persistLogs); + logger.onSync(persistLogs); - logger.info("SYNC", "Manual sync example ready"); + logger.info("SYNC", "Manual sync example ready"); } void loop() { - if (loggerStopped) { - delay(1000); - return; - } - - sampler.logReading(); - sampleCount++; - - if (millis() - lastSyncMs >= kManualSyncIntervalMS) { - lastSyncMs = millis(); - logger.sync(); // trigger persistence callback immediately - } - - if (sampleCount >= 40) { - logger.deinit(); - loggerStopped = true; - Serial.println("Logger deinitialized after manual-sync demo"); - } - - delay(250); + if (loggerStopped) { + delay(1000); + return; + } + + sampler.logReading(); + sampleCount++; + + if (millis() - lastSyncMs >= kManualSyncIntervalMS) { + lastSyncMs = millis(); + logger.sync(); // trigger persistence callback immediately + } + + if (sampleCount >= 40) { + logger.deinit(); + loggerStopped = true; + Serial.println("Logger deinitialized after manual-sync demo"); + } + + delay(250); } diff --git a/examples/json_logging/json_logging.ino b/examples/json_logging/json_logging.ino index 4d01988..ce42774 100644 --- a/examples/json_logging/json_logging.ino +++ b/examples/json_logging/json_logging.ino @@ -7,62 +7,62 @@ ESPLogger logger; namespace { uint32_t sampleIndex = 0; bool loggerStopped = false; -} // namespace +} // namespace void setup() { - Serial.begin(115200); + Serial.begin(115200); - LoggerConfig config; - config.enableSyncTask = false; - config.consoleLogLevel = LogLevel::Debug; - config.usePrettyJson = true; + LoggerConfig config; + config.enableSyncTask = false; + config.consoleLogLevel = LogLevel::Debug; + config.usePrettyJson = true; - if (!logger.init(config)) { - Serial.println("ESPLogger init failed"); - return; - } + if (!logger.init(config)) { + Serial.println("ESPLogger init failed"); + return; + } - logger.info("JSON", "ArduinoJson v7 logging example ready"); + logger.info("JSON", "ArduinoJson v7 logging example ready"); } void loop() { - if (loggerStopped) { - delay(1000); - return; - } + if (loggerStopped) { + delay(1000); + return; + } - JsonDocument payload; - payload["sample"] = static_cast(sampleIndex); - payload["status"] = sampleIndex % 2 == 0 ? "ok" : "warn"; - payload["ready"] = true; + JsonDocument payload; + payload["sample"] = static_cast(sampleIndex); + payload["status"] = sampleIndex % 2 == 0 ? "ok" : "warn"; + payload["ready"] = true; - logger.debug("JSON", payload); - logger.info("JSON", payload.as()); + logger.debug("JSON", payload); + logger.info("JSON", payload.as()); - if (sampleIndex == 2) { - logger.warn("JSON", "Switching to compact JSON output"); - LoggerConfig updated = logger.currentConfig(); - updated.usePrettyJson = false; - logger.deinit(); - if (!logger.init(updated)) { - Serial.println("ESPLogger re-init failed"); - loggerStopped = true; - return; - } - logger.info("JSON", "Compact JSON output enabled"); - } + if (sampleIndex == 2) { + logger.warn("JSON", "Switching to compact JSON output"); + LoggerConfig updated = logger.currentConfig(); + updated.usePrettyJson = false; + logger.deinit(); + if (!logger.init(updated)) { + Serial.println("ESPLogger re-init failed"); + loggerStopped = true; + return; + } + logger.info("JSON", "Compact JSON output enabled"); + } - if (sampleIndex == 4) { - logger.error("JSON", payload["sample"].as()); - } + if (sampleIndex == 4) { + logger.error("JSON", payload["sample"].as()); + } - sampleIndex++; + sampleIndex++; - if (sampleIndex >= 6) { - logger.deinit(); - loggerStopped = true; - Serial.println("Logger deinitialized after JSON demo"); - } + if (sampleIndex >= 6) { + logger.deinit(); + loggerStopped = true; + Serial.println("Logger deinitialized after JSON demo"); + } - delay(1500); + delay(1500); } diff --git a/scripts/format_cpp.sh b/scripts/format_cpp.sh new file mode 100755 index 0000000..7d17b04 --- /dev/null +++ b/scripts/format_cpp.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -euo pipefail + +_repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +_clang_format="${_repo_root}/.vscode/bin/clang-format" + +if [ ! -x "${_clang_format}" ]; then + echo "clang-format wrapper not found: ${_clang_format}" >&2 + exit 1 +fi + +mapfile -d '' _format_files < <( + git -C "${_repo_root}" ls-files -z -- '*.c' '*.cc' '*.cpp' '*.h' '*.hpp' '*.ino' +) + +if [ "${#_format_files[@]}" -eq 0 ]; then + echo "No tracked C/C++/INO files found to format." + exit 0 +fi + +"${_clang_format}" -i --style=file "${_format_files[@]}" + +echo "Formatted ${#_format_files[@]} files." diff --git a/src/esp_logger/logger.cpp b/src/esp_logger/logger.cpp index 5d57792..e47eba6 100644 --- a/src/esp_logger/logger.cpp +++ b/src/esp_logger/logger.cpp @@ -40,11 +40,13 @@ class LockGuard { constexpr const char *kSyncTaskName = "ESPLoggerSync"; #if ESPLOGGER_USE_ESP_LOG -static void logWithEsp(LogLevel level, - const char *tag, - uint32_t millisValue, - std::time_t timestamp, - const std::string &message) { +static void logWithEsp( + LogLevel level, + const char *tag, + uint32_t millisValue, + std::time_t timestamp, + const std::string &message +) { const unsigned long millisUnsigned = static_cast(millisValue); const int64_t timestampValue = static_cast(timestamp); @@ -84,11 +86,13 @@ static void logWithPrintf(LogLevel level, const char *tag, const std::string &me } } -static void logToConsole(LogLevel level, - const char *tag, - uint32_t millisValue, - std::time_t timestamp, - const std::string &message) { +static void logToConsole( + LogLevel level, + const char *tag, + uint32_t millisValue, + std::time_t timestamp, + const std::string &message +) { #if ESPLOGGER_USE_ESP_LOG logWithEsp(level, tag, millisValue, timestamp, message); #else @@ -166,13 +170,14 @@ bool ESPLogger::init(const LoggerConfig &config) { if (shouldCreateTask) { _running = true; const BaseType_t created = xTaskCreatePinnedToCore( - &ESPLogger::syncTaskThunk, - kSyncTaskName, - _config.stackSize, - this, - _config.priority, - &_syncTask, - _config.coreId); + &ESPLogger::syncTaskThunk, + kSyncTaskName, + _config.stackSize, + this, + _config.priority, + &_syncTask, + _config.coreId + ); if (created != pdPASS) { _running = false; @@ -289,7 +294,11 @@ void ESPLogger::error(const char *tag, const char *fmt, ...) { #if ESPLOGGER_HAS_ARDUINOJSON_V7 void ESPLogger::debug(const char *tag, const ArduinoJson::JsonDocument &json) { - logMessage(LogLevel::Debug, tag, serializeJsonMessage(json.as())); + logMessage( + LogLevel::Debug, + tag, + serializeJsonMessage(json.as()) + ); } void ESPLogger::info(const char *tag, const ArduinoJson::JsonDocument &json) { @@ -301,7 +310,11 @@ void ESPLogger::warn(const char *tag, const ArduinoJson::JsonDocument &json) { } void ESPLogger::error(const char *tag, const ArduinoJson::JsonDocument &json) { - logMessage(LogLevel::Error, tag, serializeJsonMessage(json.as())); + logMessage( + LogLevel::Error, + tag, + serializeJsonMessage(json.as()) + ); } void ESPLogger::debug(const char *tag, ArduinoJson::JsonVariantConst json) { @@ -337,9 +350,12 @@ std::vector ESPLogger::getLogs(LogLevel level) { LockGuard guard(_mutex); std::vector matches; matches.reserve(_logs.size()); - std::copy_if(_logs.begin(), _logs.end(), std::back_inserter(matches), [level](const Log &entry) { - return entry.level == level; - }); + std::copy_if( + _logs.begin(), + _logs.end(), + std::back_inserter(matches), + [level](const Log &entry) { return entry.level == level; } + ); return matches; } @@ -415,7 +431,13 @@ void ESPLogger::logMessage(LogLevel level, const char *tag, std::string message) return; } - Log entry{level, tag != nullptr ? tag : "", static_cast(millis()), std::time(nullptr), std::move(message)}; + Log entry{ + level, + tag != nullptr ? tag : "", + static_cast(millis()), + std::time(nullptr), + std::move(message) + }; bool shouldLogToConsole = false; bool shouldInvokeLiveCallback = false; @@ -480,16 +502,16 @@ std::string ESPLogger::formatMessage(const char *fmt, va_list args) { std::string ESPLogger::serializeJsonMessage(ArduinoJson::JsonVariantConst json) const { const bool usePrettyJson = _config.usePrettyJson; const size_t required = - usePrettyJson ? ArduinoJson::measureJsonPretty(json) : ArduinoJson::measureJson(json); + usePrettyJson ? ArduinoJson::measureJsonPretty(json) : ArduinoJson::measureJson(json); if (required == 0) { return {}; } InternalCharVector buffer(required + 1, '\0', _charAllocator); - const size_t written = usePrettyJson - ? ArduinoJson::serializeJsonPretty(json, buffer.data(), buffer.size()) - : ArduinoJson::serializeJson(json, buffer.data(), buffer.size()); + const size_t written = + usePrettyJson ? ArduinoJson::serializeJsonPretty(json, buffer.data(), buffer.size()) + : ArduinoJson::serializeJson(json, buffer.data(), buffer.size()); if (written == 0) { return {}; diff --git a/src/esp_logger/logger.h b/src/esp_logger/logger.h index c4609dd..168df20 100644 --- a/src/esp_logger/logger.h +++ b/src/esp_logger/logger.h @@ -32,82 +32,84 @@ #include "esp_logger/logger_config.h" struct Log { - LogLevel level; - std::string tag; - uint32_t millis; - std::time_t timestamp; - std::string message; + LogLevel level; + std::string tag; + uint32_t millis; + std::time_t timestamp; + std::string message; }; -using SyncCallback = std::function&)>; -using LiveCallback = std::function; +using SyncCallback = std::function &)>; +using LiveCallback = std::function; using InternalLogDeque = std::deque>; using InternalCharVector = std::vector>; using InternalLogVector = std::vector>; class ESPLogger { public: - ESPLogger() = default; - ~ESPLogger(); + ESPLogger() = default; + ~ESPLogger(); - bool init(const LoggerConfig& config = LoggerConfig{}); - void deinit(); - bool isInitialized() const { return _initialized; } + bool init(const LoggerConfig &config = LoggerConfig{}); + void deinit(); + bool isInitialized() const { + return _initialized; + } - void onSync(SyncCallback callback); - void attach(LiveCallback callback); - void detach(); + void onSync(SyncCallback callback); + void attach(LiveCallback callback); + void detach(); - void sync(); + void sync(); - void debug(const char* tag, const char* fmt, ...) __attribute__((format(printf, 3, 4))); - void info(const char* tag, const char* fmt, ...) __attribute__((format(printf, 3, 4))); - void warn(const char* tag, const char* fmt, ...) __attribute__((format(printf, 3, 4))); - void error(const char* tag, const char* fmt, ...) __attribute__((format(printf, 3, 4))); + void debug(const char *tag, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + void info(const char *tag, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + void warn(const char *tag, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + void error(const char *tag, const char *fmt, ...) __attribute__((format(printf, 3, 4))); #if ESPLOGGER_HAS_ARDUINOJSON_V7 - void debug(const char* tag, const ArduinoJson::JsonDocument& json); - void info(const char* tag, const ArduinoJson::JsonDocument& json); - void warn(const char* tag, const ArduinoJson::JsonDocument& json); - void error(const char* tag, const ArduinoJson::JsonDocument& json); - - void debug(const char* tag, ArduinoJson::JsonVariantConst json); - void info(const char* tag, ArduinoJson::JsonVariantConst json); - void warn(const char* tag, ArduinoJson::JsonVariantConst json); - void error(const char* tag, ArduinoJson::JsonVariantConst json); + void debug(const char *tag, const ArduinoJson::JsonDocument &json); + void info(const char *tag, const ArduinoJson::JsonDocument &json); + void warn(const char *tag, const ArduinoJson::JsonDocument &json); + void error(const char *tag, const ArduinoJson::JsonDocument &json); + + void debug(const char *tag, ArduinoJson::JsonVariantConst json); + void info(const char *tag, ArduinoJson::JsonVariantConst json); + void warn(const char *tag, ArduinoJson::JsonVariantConst json); + void error(const char *tag, ArduinoJson::JsonVariantConst json); #endif - std::vector getAllLogs(); - int getLogCount(LogLevel level); - std::vector getLogs(LogLevel level); - static int getLogCount(const std::vector& logs, LogLevel level); - static std::vector getLogs(const std::vector& logs, LogLevel level); - std::vector getLastLogs(size_t count); + std::vector getAllLogs(); + int getLogCount(LogLevel level); + std::vector getLogs(LogLevel level); + static int getLogCount(const std::vector &logs, LogLevel level); + static std::vector getLogs(const std::vector &logs, LogLevel level); + std::vector getLastLogs(size_t count); - LoggerConfig currentConfig() const; - void setLogLevel(LogLevel level); - LogLevel logLevel() const; + LoggerConfig currentConfig() const; + void setLogLevel(LogLevel level); + LogLevel logLevel() const; private: - void logInternal(LogLevel level, const char* tag, const char* fmt, va_list args); - void logMessage(LogLevel level, const char* tag, std::string message); - std::string formatMessage(const char* fmt, va_list args); + void logInternal(LogLevel level, const char *tag, const char *fmt, va_list args); + void logMessage(LogLevel level, const char *tag, std::string message); + std::string formatMessage(const char *fmt, va_list args); #if ESPLOGGER_HAS_ARDUINOJSON_V7 - std::string serializeJsonMessage(ArduinoJson::JsonVariantConst json) const; + std::string serializeJsonMessage(ArduinoJson::JsonVariantConst json) const; #endif - void performSync(); - static void syncTaskThunk(void* arg); - void syncTaskLoop(); - - LoggerConfig _config{}; - bool _initialized = false; - bool _running = false; - TaskHandle_t _syncTask = nullptr; - SemaphoreHandle_t _mutex = nullptr; - InternalLogDeque _logs; - SyncCallback _syncCallback; - LiveCallback _liveCallback; - LogLevel _logLevel = LogLevel::Debug; - bool _usePSRAMBuffers = false; - LoggerAllocator _logAllocator{}; - LoggerAllocator _charAllocator{}; + void performSync(); + static void syncTaskThunk(void *arg); + void syncTaskLoop(); + + LoggerConfig _config{}; + bool _initialized = false; + bool _running = false; + TaskHandle_t _syncTask = nullptr; + SemaphoreHandle_t _mutex = nullptr; + InternalLogDeque _logs; + SyncCallback _syncCallback; + LiveCallback _liveCallback; + LogLevel _logLevel = LogLevel::Debug; + bool _usePSRAMBuffers = false; + LoggerAllocator _logAllocator{}; + LoggerAllocator _charAllocator{}; }; diff --git a/src/esp_logger/logger_allocator.h b/src/esp_logger/logger_allocator.h index 5fed6f5..ab87ab2 100644 --- a/src/esp_logger/logger_allocator.h +++ b/src/esp_logger/logger_allocator.h @@ -18,77 +18,76 @@ namespace logger_allocator_detail { inline void *allocate(std::size_t bytes, bool usePSRAMBuffers) noexcept { #if ESP_LOGGER_HAS_BUFFER_MANAGER - return ESPBufferManager::allocate(bytes, usePSRAMBuffers); + return ESPBufferManager::allocate(bytes, usePSRAMBuffers); #else - (void)usePSRAMBuffers; - return std::malloc(bytes); + (void)usePSRAMBuffers; + return std::malloc(bytes); #endif } inline void deallocate(void *ptr) noexcept { #if ESP_LOGGER_HAS_BUFFER_MANAGER - ESPBufferManager::deallocate(ptr); + ESPBufferManager::deallocate(ptr); #else - std::free(ptr); + std::free(ptr); #endif } -} // namespace logger_allocator_detail +} // namespace logger_allocator_detail -template -class LoggerAllocator { - public: - using value_type = T; +template class LoggerAllocator { + public: + using value_type = T; - LoggerAllocator() noexcept = default; - explicit LoggerAllocator(bool usePSRAMBuffers) noexcept : _usePSRAMBuffers(usePSRAMBuffers) {} + LoggerAllocator() noexcept = default; + explicit LoggerAllocator(bool usePSRAMBuffers) noexcept : _usePSRAMBuffers(usePSRAMBuffers) { + } - template - LoggerAllocator(const LoggerAllocator &other) noexcept : _usePSRAMBuffers(other.usePSRAMBuffers()) {} + template + LoggerAllocator(const LoggerAllocator &other) noexcept + : _usePSRAMBuffers(other.usePSRAMBuffers()) { + } - T *allocate(std::size_t n) { - if (n == 0) { - return nullptr; - } - if (n > (std::numeric_limits::max() / sizeof(T))) { + T *allocate(std::size_t n) { + if (n == 0) { + return nullptr; + } + if (n > (std::numeric_limits::max() / sizeof(T))) { #if defined(__cpp_exceptions) - throw std::bad_alloc(); + throw std::bad_alloc(); #else - std::abort(); + std::abort(); #endif - } + } - void *memory = logger_allocator_detail::allocate(n * sizeof(T), _usePSRAMBuffers); - if (memory == nullptr) { + void *memory = logger_allocator_detail::allocate(n * sizeof(T), _usePSRAMBuffers); + if (memory == nullptr) { #if defined(__cpp_exceptions) - throw std::bad_alloc(); + throw std::bad_alloc(); #else - std::abort(); + std::abort(); #endif - } - return static_cast(memory); - } + } + return static_cast(memory); + } - void deallocate(T *ptr, std::size_t) noexcept { - logger_allocator_detail::deallocate(ptr); - } + void deallocate(T *ptr, std::size_t) noexcept { + logger_allocator_detail::deallocate(ptr); + } - bool usePSRAMBuffers() const noexcept { - return _usePSRAMBuffers; - } + bool usePSRAMBuffers() const noexcept { + return _usePSRAMBuffers; + } - template - bool operator==(const LoggerAllocator &other) const noexcept { - return _usePSRAMBuffers == other.usePSRAMBuffers(); - } + template bool operator==(const LoggerAllocator &other) const noexcept { + return _usePSRAMBuffers == other.usePSRAMBuffers(); + } - template - bool operator!=(const LoggerAllocator &other) const noexcept { - return !(*this == other); - } + template bool operator!=(const LoggerAllocator &other) const noexcept { + return !(*this == other); + } - private: - template - friend class LoggerAllocator; + private: + template friend class LoggerAllocator; - bool _usePSRAMBuffers = false; + bool _usePSRAMBuffers = false; }; diff --git a/src/esp_logger/logger_config.h b/src/esp_logger/logger_config.h index 8214207..342f7f3 100644 --- a/src/esp_logger/logger_config.h +++ b/src/esp_logger/logger_config.h @@ -5,23 +5,18 @@ #include -enum class LogLevel { - Debug = 0, - Info, - Warn, - Error -}; +enum class LogLevel { Debug = 0, Info, Warn, Error }; struct LoggerConfig { - static constexpr BaseType_t any = tskNO_AFFINITY; // Use any available core + static constexpr BaseType_t any = tskNO_AFFINITY; // Use any available core - uint32_t syncIntervalMS = 5000; - uint32_t stackSize = 4096 * sizeof(StackType_t); - BaseType_t coreId = any; - size_t maxLogInRam = 100; - UBaseType_t priority = 1; - LogLevel consoleLogLevel = LogLevel::Debug; - bool enableSyncTask = true; - bool usePrettyJson = true; - bool usePSRAMBuffers = false; + uint32_t syncIntervalMS = 5000; + uint32_t stackSize = 4096 * sizeof(StackType_t); + BaseType_t coreId = any; + size_t maxLogInRam = 100; + UBaseType_t priority = 1; + LogLevel consoleLogLevel = LogLevel::Debug; + bool enableSyncTask = true; + bool usePrettyJson = true; + bool usePSRAMBuffers = false; }; diff --git a/test/logger_json_tests.cpp b/test/logger_json_tests.cpp index d072a94..8fe26f4 100644 --- a/test/logger_json_tests.cpp +++ b/test/logger_json_tests.cpp @@ -12,225 +12,248 @@ namespace { -[[noreturn]] void fail(const std::string& message) { - throw std::runtime_error(message); +[[noreturn]] void fail(const std::string &message) { + throw std::runtime_error(message); } template -void expect_equal(const T& actual, const T& expected, const std::string& message) { - if (!(actual == expected)) { - fail(message); - } +void expect_equal(const T &actual, const T &expected, const std::string &message) { + if (!(actual == expected)) { + fail(message); + } } -void expect_true(bool condition, const std::string& message) { - if (!condition) { - fail(message); - } +void expect_true(bool condition, const std::string &message) { + if (!condition) { + fail(message); + } } void test_default_config_enables_pretty_json() { - LoggerConfig config; - expect_true(config.usePrettyJson, "Default usePrettyJson should be true"); + LoggerConfig config; + expect_true(config.usePrettyJson, "Default usePrettyJson should be true"); } void test_init_preserves_use_pretty_json_false() { - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.usePrettyJson = false; + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.usePrettyJson = false; - if (!logger.init(config)) { - fail("Logger failed to initialize"); - } + if (!logger.init(config)) { + fail("Logger failed to initialize"); + } - expect_true(!logger.currentConfig().usePrettyJson, "init should preserve usePrettyJson=false"); - logger.deinit(); + expect_true(!logger.currentConfig().usePrettyJson, "init should preserve usePrettyJson=false"); + logger.deinit(); } void test_json_document_logs_compact_when_disabled() { - test_support::resetMillis(); - - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.usePrettyJson = false; - - if (!logger.init(config)) { - fail("Logger failed to initialize"); - } - - ArduinoJson::JsonDocument doc; - doc["hello"] = "world"; - doc["answer"] = 7; - logger.debug("JSON", doc); - - const auto logs = logger.getAllLogs(); - expect_equal(logs.size(), static_cast(1), "Compact JSON log should be buffered"); - expect_equal(logs.front().message, std::string("{\"hello\":\"world\",\"answer\":7}"), - "Compact JSON serialization mismatch"); - - logger.deinit(); + test_support::resetMillis(); + + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.usePrettyJson = false; + + if (!logger.init(config)) { + fail("Logger failed to initialize"); + } + + ArduinoJson::JsonDocument doc; + doc["hello"] = "world"; + doc["answer"] = 7; + logger.debug("JSON", doc); + + const auto logs = logger.getAllLogs(); + expect_equal(logs.size(), static_cast(1), "Compact JSON log should be buffered"); + expect_equal( + logs.front().message, + std::string("{\"hello\":\"world\",\"answer\":7}"), + "Compact JSON serialization mismatch" + ); + + logger.deinit(); } void test_json_document_logs_pretty_by_default() { - test_support::resetMillis(); + test_support::resetMillis(); - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; - if (!logger.init(config)) { - fail("Logger failed to initialize"); - } + if (!logger.init(config)) { + fail("Logger failed to initialize"); + } - ArduinoJson::JsonDocument doc; - doc["hello"] = "world"; - logger.info("JSON", doc); + ArduinoJson::JsonDocument doc; + doc["hello"] = "world"; + logger.info("JSON", doc); - const auto logs = logger.getAllLogs(); - expect_equal(logs.size(), static_cast(1), "Pretty JSON log should be buffered"); - expect_equal(logs.front().message, std::string("{\n \"hello\": \"world\"\n}"), - "Pretty JSON serialization mismatch"); + const auto logs = logger.getAllLogs(); + expect_equal(logs.size(), static_cast(1), "Pretty JSON log should be buffered"); + expect_equal( + logs.front().message, + std::string("{\n \"hello\": \"world\"\n}"), + "Pretty JSON serialization mismatch" + ); - logger.deinit(); + logger.deinit(); } void test_json_variant_logs_nested_object() { - test_support::resetMillis(); + test_support::resetMillis(); - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; - if (!logger.init(config)) { - fail("Logger failed to initialize"); - } + if (!logger.init(config)) { + fail("Logger failed to initialize"); + } - ArduinoJson::JsonDocument nested; - nested["child"] = "value"; - logger.warn("JSON", nested.as()); + ArduinoJson::JsonDocument nested; + nested["child"] = "value"; + logger.warn("JSON", nested.as()); - const auto logs = logger.getAllLogs(); - expect_equal(logs.size(), static_cast(1), "Variant object log should be buffered"); - expect_equal(logs.front().message, std::string("{\n \"child\": \"value\"\n}"), - "Variant object serialization mismatch"); + const auto logs = logger.getAllLogs(); + expect_equal(logs.size(), static_cast(1), "Variant object log should be buffered"); + expect_equal( + logs.front().message, + std::string("{\n \"child\": \"value\"\n}"), + "Variant object serialization mismatch" + ); - logger.deinit(); + logger.deinit(); } void test_json_variant_logs_scalar() { - test_support::resetMillis(); + test_support::resetMillis(); - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.usePrettyJson = false; + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.usePrettyJson = false; - if (!logger.init(config)) { - fail("Logger failed to initialize"); - } + if (!logger.init(config)) { + fail("Logger failed to initialize"); + } - logger.error("JSON", ArduinoJson::JsonVariantConst(7)); + logger.error("JSON", ArduinoJson::JsonVariantConst(7)); - const auto logs = logger.getAllLogs(); - expect_equal(logs.size(), static_cast(1), "Scalar variant log should be buffered"); - expect_equal(logs.front().message, std::string("7"), "Scalar variant serialization mismatch"); + const auto logs = logger.getAllLogs(); + expect_equal(logs.size(), static_cast(1), "Scalar variant log should be buffered"); + expect_equal(logs.front().message, std::string("7"), "Scalar variant serialization mismatch"); - logger.deinit(); + logger.deinit(); } void test_live_callback_receives_json_message() { - test_support::resetMillis(); + test_support::resetMillis(); - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.usePrettyJson = false; + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.usePrettyJson = false; - if (!logger.init(config)) { - fail("Logger failed to initialize"); - } + if (!logger.init(config)) { + fail("Logger failed to initialize"); + } - std::vector liveLogs; - logger.attach([&liveLogs](const Log& entry) { liveLogs.push_back(entry); }); + std::vector liveLogs; + logger.attach([&liveLogs](const Log &entry) { liveLogs.push_back(entry); }); - ArduinoJson::JsonDocument doc; - doc["hello"] = "world"; - logger.info("LIVE", doc); + ArduinoJson::JsonDocument doc; + doc["hello"] = "world"; + logger.info("LIVE", doc); - expect_equal(liveLogs.size(), static_cast(1), "Live callback should receive JSON log"); - expect_equal(liveLogs.front().message, std::string("{\"hello\":\"world\"}"), - "Live callback JSON payload mismatch"); + expect_equal(liveLogs.size(), static_cast(1), "Live callback should receive JSON log"); + expect_equal( + liveLogs.front().message, + std::string("{\"hello\":\"world\"}"), + "Live callback JSON payload mismatch" + ); - logger.deinit(); + logger.deinit(); } void test_sync_callback_receives_json_message() { - test_support::resetMillis(); - - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.usePrettyJson = false; - - if (!logger.init(config)) { - fail("Logger failed to initialize"); - } - - std::vector syncedLogs; - logger.onSync([&syncedLogs](const std::vector& logs) { syncedLogs = logs; }); - - ArduinoJson::JsonDocument doc; - doc["hello"] = "world"; - logger.warn("SYNC", doc); - logger.sync(); - - expect_equal(syncedLogs.size(), static_cast(1), "Sync callback should receive JSON log"); - expect_equal(syncedLogs.front().message, std::string("{\"hello\":\"world\"}"), - "Sync callback JSON payload mismatch"); - - logger.deinit(); + test_support::resetMillis(); + + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.usePrettyJson = false; + + if (!logger.init(config)) { + fail("Logger failed to initialize"); + } + + std::vector syncedLogs; + logger.onSync([&syncedLogs](const std::vector &logs) { syncedLogs = logs; }); + + ArduinoJson::JsonDocument doc; + doc["hello"] = "world"; + logger.warn("SYNC", doc); + logger.sync(); + + expect_equal( + syncedLogs.size(), + static_cast(1), + "Sync callback should receive JSON log" + ); + expect_equal( + syncedLogs.front().message, + std::string("{\"hello\":\"world\"}"), + "Sync callback JSON payload mismatch" + ); + + logger.deinit(); } void test_printf_logging_still_works_in_json_build() { - test_support::resetMillis(); + test_support::resetMillis(); - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; - if (!logger.init(config)) { - fail("Logger failed to initialize"); - } + if (!logger.init(config)) { + fail("Logger failed to initialize"); + } - logger.info("TEXT", "value %d", 5); + logger.info("TEXT", "value %d", 5); - const auto logs = logger.getAllLogs(); - expect_equal(logs.size(), static_cast(1), "Formatted log should still be buffered"); - expect_equal(logs.front().message, std::string("value 5"), "Formatted logging should remain unchanged"); + const auto logs = logger.getAllLogs(); + expect_equal(logs.size(), static_cast(1), "Formatted log should still be buffered"); + expect_equal( + logs.front().message, + std::string("value 5"), + "Formatted logging should remain unchanged" + ); - logger.deinit(); + logger.deinit(); } -} // namespace +} // namespace int main() { - try { - test_default_config_enables_pretty_json(); - test_init_preserves_use_pretty_json_false(); - test_json_document_logs_compact_when_disabled(); - test_json_document_logs_pretty_by_default(); - test_json_variant_logs_nested_object(); - test_json_variant_logs_scalar(); - test_live_callback_receives_json_message(); - test_sync_callback_receives_json_message(); - test_printf_logging_still_works_in_json_build(); - } catch (const std::exception& ex) { - std::cerr << "Test failure: " << ex.what() << '\n'; - return 1; - } - - std::cout << "All JSON tests passed\n"; - return 0; + try { + test_default_config_enables_pretty_json(); + test_init_preserves_use_pretty_json_false(); + test_json_document_logs_compact_when_disabled(); + test_json_document_logs_pretty_by_default(); + test_json_variant_logs_nested_object(); + test_json_variant_logs_scalar(); + test_live_callback_receives_json_message(); + test_sync_callback_receives_json_message(); + test_printf_logging_still_works_in_json_build(); + } catch (const std::exception &ex) { + std::cerr << "Test failure: " << ex.what() << '\n'; + return 1; + } + + std::cout << "All JSON tests passed\n"; + return 0; } diff --git a/test/logger_test_stubs.cpp b/test/logger_test_stubs.cpp index e18d24c..cb4f980 100644 --- a/test/logger_test_stubs.cpp +++ b/test/logger_test_stubs.cpp @@ -9,73 +9,76 @@ namespace { struct FakeSemaphore { - std::mutex mutex; + std::mutex mutex; }; std::atomic g_fakeMillis{0}; std::atomic g_fakeTicks{0}; -} // namespace +} // namespace extern "C" unsigned long millis(void) { - return g_fakeMillis.fetch_add(1) + 1; + return g_fakeMillis.fetch_add(1) + 1; } extern "C" SemaphoreHandle_t xSemaphoreCreateMutex(void) { - return reinterpret_cast(new (std::nothrow) FakeSemaphore{}); + return reinterpret_cast(new (std::nothrow) FakeSemaphore{}); } extern "C" BaseType_t xSemaphoreTake(SemaphoreHandle_t handle, TickType_t /*ticks*/) { - if (handle == nullptr) { - return pdFAIL; - } - auto *sem = reinterpret_cast(handle); - sem->mutex.lock(); - return pdPASS; + if (handle == nullptr) { + return pdFAIL; + } + auto *sem = reinterpret_cast(handle); + sem->mutex.lock(); + return pdPASS; } extern "C" BaseType_t xSemaphoreGive(SemaphoreHandle_t handle) { - if (handle == nullptr) { - return pdFAIL; - } - auto *sem = reinterpret_cast(handle); - sem->mutex.unlock(); - return pdPASS; + if (handle == nullptr) { + return pdFAIL; + } + auto *sem = reinterpret_cast(handle); + sem->mutex.unlock(); + return pdPASS; } extern "C" void vSemaphoreDelete(SemaphoreHandle_t handle) { - auto *sem = reinterpret_cast(handle); - delete sem; + auto *sem = reinterpret_cast(handle); + delete sem; } -extern "C" BaseType_t xTaskCreatePinnedToCore(TaskFunction_t task, - const char * /*name*/, - uint32_t /*stackDepth*/, - void * /*parameters*/, - UBaseType_t /*priority*/, - TaskHandle_t *createdTask, - BaseType_t /*coreId*/) { - if (createdTask != nullptr) { - *createdTask = reinterpret_cast(task); - } - return pdPASS; +extern "C" BaseType_t xTaskCreatePinnedToCore( + TaskFunction_t task, + const char * /*name*/, + uint32_t /*stackDepth*/, + void * /*parameters*/, + UBaseType_t /*priority*/, + TaskHandle_t *createdTask, + BaseType_t /*coreId*/ +) { + if (createdTask != nullptr) { + *createdTask = reinterpret_cast(task); + } + return pdPASS; } -extern "C" void vTaskDelete(TaskHandle_t /*task*/) {} +extern "C" void vTaskDelete(TaskHandle_t /*task*/) { +} extern "C" void vTaskDelay(TickType_t ticks) { - g_fakeTicks.fetch_add(ticks); + g_fakeTicks.fetch_add(ticks); } extern "C" TickType_t xTaskGetTickCount(void) { - return g_fakeTicks.load(); + return g_fakeTicks.load(); } namespace test_support { void resetMillis(unsigned long start) { - g_fakeMillis.store(start); - g_fakeTicks.store(static_cast(start)); + g_fakeMillis.store(start); + g_fakeTicks.store(static_cast(start)); } -} // namespace test_support +} // namespace test_support diff --git a/test/logger_tests.cpp b/test/logger_tests.cpp index 5ea20fd..4ce1b64 100644 --- a/test/logger_tests.cpp +++ b/test/logger_tests.cpp @@ -12,473 +12,584 @@ namespace { [[noreturn]] void fail(const std::string &message) { - throw std::runtime_error(message); + throw std::runtime_error(message); } template void expect_equal(const T &actual, const T &expected, const std::string &message) { - if (!(actual == expected)) { - fail(message); - } + if (!(actual == expected)) { + fail(message); + } } void expect_true(bool condition, const std::string &message) { - if (!condition) { - fail(message); - } + if (!condition) { + fail(message); + } } void test_init_with_default_config() { - ESPLogger logger; - - if (!logger.init()) { - fail("ESPLogger failed to initialize with default config"); - } - - expect_true(logger.isInitialized(), "ESPLogger should be initialized with default config"); - expect_equal(logger.logLevel(), LogLevel::Debug, "Default console log level should be Debug"); - - const auto current = logger.currentConfig(); - expect_true(current.enableSyncTask, "Default config should enable the sync task"); - expect_equal(current.maxLogInRam, static_cast(100), "Default maxLogInRam should be 100"); - expect_true(current.usePrettyJson, "Default usePrettyJson should be true"); - expect_true(!current.usePSRAMBuffers, "Default usePSRAMBuffers should be false"); - - logger.deinit(); - expect_true(!logger.isInitialized(), "ESPLogger should be deinitialized after default init test"); + ESPLogger logger; + + if (!logger.init()) { + fail("ESPLogger failed to initialize with default config"); + } + + expect_true(logger.isInitialized(), "ESPLogger should be initialized with default config"); + expect_equal(logger.logLevel(), LogLevel::Debug, "Default console log level should be Debug"); + + const auto current = logger.currentConfig(); + expect_true(current.enableSyncTask, "Default config should enable the sync task"); + expect_equal( + current.maxLogInRam, + static_cast(100), + "Default maxLogInRam should be 100" + ); + expect_true(current.usePrettyJson, "Default usePrettyJson should be true"); + expect_true(!current.usePSRAMBuffers, "Default usePSRAMBuffers should be false"); + + logger.deinit(); + expect_true( + !logger.isInitialized(), + "ESPLogger should be deinitialized after default init test" + ); } void test_init_applies_normalized_config() { - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 0; - config.consoleLogLevel = LogLevel::Warn; - config.usePrettyJson = false; - config.usePSRAMBuffers = true; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - expect_true(logger.isInitialized(), "ESPLogger should be initialized"); - expect_equal(logger.logLevel(), LogLevel::Warn, "Log level should match config"); - - const auto current = logger.currentConfig(); - expect_equal(current.maxLogInRam, static_cast(1), "maxLogInRam should normalize to 1"); - expect_equal(current.consoleLogLevel, LogLevel::Warn, "consoleLogLevel should remain Warn"); - expect_true(!current.usePrettyJson, "usePrettyJson should match config"); - expect_true(current.usePSRAMBuffers, "usePSRAMBuffers should match config"); - - logger.deinit(); - expect_true(!logger.isInitialized(), "ESPLogger should be deinitialized"); + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 0; + config.consoleLogLevel = LogLevel::Warn; + config.usePrettyJson = false; + config.usePSRAMBuffers = true; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + expect_true(logger.isInitialized(), "ESPLogger should be initialized"); + expect_equal(logger.logLevel(), LogLevel::Warn, "Log level should match config"); + + const auto current = logger.currentConfig(); + expect_equal(current.maxLogInRam, static_cast(1), "maxLogInRam should normalize to 1"); + expect_equal(current.consoleLogLevel, LogLevel::Warn, "consoleLogLevel should remain Warn"); + expect_true(!current.usePrettyJson, "usePrettyJson should match config"); + expect_true(current.usePSRAMBuffers, "usePSRAMBuffers should match config"); + + logger.deinit(); + expect_true(!logger.isInitialized(), "ESPLogger should be deinitialized"); } void test_deinit_is_safe_before_init() { - ESPLogger logger; - expect_true(!logger.isInitialized(), "Logger should start deinitialized"); + ESPLogger logger; + expect_true(!logger.isInitialized(), "Logger should start deinitialized"); - logger.deinit(); - expect_true(!logger.isInitialized(), "deinit should be safe before init"); - expect_true(logger.getAllLogs().empty(), "Pre-init deinit should leave log storage empty"); + logger.deinit(); + expect_true(!logger.isInitialized(), "deinit should be safe before init"); + expect_true(logger.getAllLogs().empty(), "Pre-init deinit should leave log storage empty"); } void test_deinit_is_idempotent() { - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 4; - config.consoleLogLevel = LogLevel::Debug; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - logger.info("TEST", "first"); - logger.deinit(); - expect_true(!logger.isInitialized(), "Logger should be deinitialized after first deinit"); - - logger.deinit(); - expect_true(!logger.isInitialized(), "Logger should remain deinitialized after second deinit"); - expect_true(logger.getAllLogs().empty(), "Idempotent deinit should not leave buffered logs"); + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 4; + config.consoleLogLevel = LogLevel::Debug; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + logger.info("TEST", "first"); + logger.deinit(); + expect_true(!logger.isInitialized(), "Logger should be deinitialized after first deinit"); + + logger.deinit(); + expect_true(!logger.isInitialized(), "Logger should remain deinitialized after second deinit"); + expect_true(logger.getAllLogs().empty(), "Idempotent deinit should not leave buffered logs"); } void test_reinit_after_deinit() { - ESPLogger logger; - - LoggerConfig firstConfig; - firstConfig.enableSyncTask = false; - firstConfig.maxLogInRam = 3; - firstConfig.consoleLogLevel = LogLevel::Warn; - - if (!logger.init(firstConfig)) { - fail("First init failed"); - } - logger.warn("REINIT", "one"); - logger.deinit(); - expect_true(!logger.isInitialized(), "Logger should be deinitialized before second init"); - - LoggerConfig secondConfig = firstConfig; - secondConfig.maxLogInRam = 8; - secondConfig.consoleLogLevel = LogLevel::Error; - - if (!logger.init(secondConfig)) { - fail("Second init failed"); - } - - expect_true(logger.isInitialized(), "Logger should reinitialize successfully"); - expect_true(logger.getAllLogs().empty(), "Reinit should start with an empty log buffer"); - expect_equal(logger.currentConfig().maxLogInRam, static_cast(8), "Second init config should be applied"); - expect_equal(logger.logLevel(), LogLevel::Error, "Second init log level should be applied"); - - logger.deinit(); + ESPLogger logger; + + LoggerConfig firstConfig; + firstConfig.enableSyncTask = false; + firstConfig.maxLogInRam = 3; + firstConfig.consoleLogLevel = LogLevel::Warn; + + if (!logger.init(firstConfig)) { + fail("First init failed"); + } + logger.warn("REINIT", "one"); + logger.deinit(); + expect_true(!logger.isInitialized(), "Logger should be deinitialized before second init"); + + LoggerConfig secondConfig = firstConfig; + secondConfig.maxLogInRam = 8; + secondConfig.consoleLogLevel = LogLevel::Error; + + if (!logger.init(secondConfig)) { + fail("Second init failed"); + } + + expect_true(logger.isInitialized(), "Logger should reinitialize successfully"); + expect_true(logger.getAllLogs().empty(), "Reinit should start with an empty log buffer"); + expect_equal( + logger.currentConfig().maxLogInRam, + static_cast(8), + "Second init config should be applied" + ); + expect_equal(logger.logLevel(), LogLevel::Error, "Second init log level should be applied"); + + logger.deinit(); } void test_stores_logs_up_to_configured_capacity() { - test_support::resetMillis(); - - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 2; - config.consoleLogLevel = LogLevel::Debug; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - logger.debug("TEST", "first %d", 1); - logger.info("TEST", "second"); - logger.warn("TEST", "third"); - - const auto logs = logger.getAllLogs(); - expect_equal(logs.size(), static_cast(2), "Should keep only the most recent logs"); - expect_equal(logs.front().level, LogLevel::Info, "First log level incorrect"); - expect_equal(logs.front().message, std::string("second"), "First log message incorrect"); - expect_equal(logs.back().level, LogLevel::Warn, "Last log level incorrect"); - expect_equal(logs.back().message, std::string("third"), "Last log message incorrect"); - - const auto last = logger.getLastLogs(1); - expect_equal(last.size(), static_cast(1), "getLastLogs should honor requested count"); - expect_equal(last.front().message, std::string("third"), "getLastLogs should return most recent message"); - - logger.deinit(); + test_support::resetMillis(); + + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 2; + config.consoleLogLevel = LogLevel::Debug; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + logger.debug("TEST", "first %d", 1); + logger.info("TEST", "second"); + logger.warn("TEST", "third"); + + const auto logs = logger.getAllLogs(); + expect_equal(logs.size(), static_cast(2), "Should keep only the most recent logs"); + expect_equal(logs.front().level, LogLevel::Info, "First log level incorrect"); + expect_equal(logs.front().message, std::string("second"), "First log message incorrect"); + expect_equal(logs.back().level, LogLevel::Warn, "Last log level incorrect"); + expect_equal(logs.back().message, std::string("third"), "Last log message incorrect"); + + const auto last = logger.getLastLogs(1); + expect_equal(last.size(), static_cast(1), "getLastLogs should honor requested count"); + expect_equal( + last.front().message, + std::string("third"), + "getLastLogs should return most recent message" + ); + + logger.deinit(); } void test_sync_callback_receives_buffered_logs() { - test_support::resetMillis(); - - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 10; - config.consoleLogLevel = LogLevel::Debug; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - std::vector received; - logger.onSync([&received](const std::vector &logs) { received = logs; }); - - logger.info("TAG", "message %d", 1); - logger.error("TAG", "message %d", 2); - - expect_true(received.empty(), "Callback should not run until sync is triggered"); - - logger.sync(); - - expect_equal(received.size(), static_cast(2), "Sync should flush buffered logs"); - expect_true(logger.getAllLogs().empty(), "Logs should be cleared after sync"); - expect_equal(received.front().level, LogLevel::Info, "First flushed level incorrect"); - expect_equal(received.front().message, std::string("message 1"), "First flushed message incorrect"); - expect_equal(received.back().level, LogLevel::Error, "Second flushed level incorrect"); - expect_equal(received.back().message, std::string("message 2"), "Second flushed message incorrect"); - - logger.deinit(); + test_support::resetMillis(); + + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 10; + config.consoleLogLevel = LogLevel::Debug; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + std::vector received; + logger.onSync([&received](const std::vector &logs) { received = logs; }); + + logger.info("TAG", "message %d", 1); + logger.error("TAG", "message %d", 2); + + expect_true(received.empty(), "Callback should not run until sync is triggered"); + + logger.sync(); + + expect_equal(received.size(), static_cast(2), "Sync should flush buffered logs"); + expect_true(logger.getAllLogs().empty(), "Logs should be cleared after sync"); + expect_equal(received.front().level, LogLevel::Info, "First flushed level incorrect"); + expect_equal( + received.front().message, + std::string("message 1"), + "First flushed message incorrect" + ); + expect_equal(received.back().level, LogLevel::Error, "Second flushed level incorrect"); + expect_equal( + received.back().message, + std::string("message 2"), + "Second flushed message incorrect" + ); + + logger.deinit(); } void test_deinit_flushes_and_clears_callbacks() { - test_support::resetMillis(); - - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 8; - config.consoleLogLevel = LogLevel::Debug; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - size_t liveCallCount = 0; - size_t syncCallCount = 0; - size_t lastSyncedBatchSize = 0; - - logger.attach([&liveCallCount](const Log &) { ++liveCallCount; }); - logger.onSync([&syncCallCount, &lastSyncedBatchSize](const std::vector &logs) { - ++syncCallCount; - lastSyncedBatchSize = logs.size(); - }); - - logger.info("TEARDOWN", "before deinit"); - expect_equal(liveCallCount, static_cast(1), "Live callback should run before deinit"); - - logger.deinit(); - expect_true(!logger.isInitialized(), "Logger should be deinitialized"); - expect_equal(syncCallCount, static_cast(1), "deinit should flush buffered logs exactly once"); - expect_equal(lastSyncedBatchSize, static_cast(1), "deinit flush should include pending log entries"); - - if (!logger.init(config)) { - fail("ESPLogger failed to reinitialize"); - } - - logger.info("TEARDOWN", "after deinit"); - logger.sync(); - - expect_equal(liveCallCount, static_cast(1), "Live callback should be cleared by deinit"); - expect_equal(syncCallCount, static_cast(1), "Sync callback should be cleared by deinit"); - - logger.deinit(); + test_support::resetMillis(); + + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 8; + config.consoleLogLevel = LogLevel::Debug; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + size_t liveCallCount = 0; + size_t syncCallCount = 0; + size_t lastSyncedBatchSize = 0; + + logger.attach([&liveCallCount](const Log &) { ++liveCallCount; }); + logger.onSync([&syncCallCount, &lastSyncedBatchSize](const std::vector &logs) { + ++syncCallCount; + lastSyncedBatchSize = logs.size(); + }); + + logger.info("TEARDOWN", "before deinit"); + expect_equal(liveCallCount, static_cast(1), "Live callback should run before deinit"); + + logger.deinit(); + expect_true(!logger.isInitialized(), "Logger should be deinitialized"); + expect_equal( + syncCallCount, + static_cast(1), + "deinit should flush buffered logs exactly once" + ); + expect_equal( + lastSyncedBatchSize, + static_cast(1), + "deinit flush should include pending log entries" + ); + + if (!logger.init(config)) { + fail("ESPLogger failed to reinitialize"); + } + + logger.info("TEARDOWN", "after deinit"); + logger.sync(); + + expect_equal( + liveCallCount, + static_cast(1), + "Live callback should be cleared by deinit" + ); + expect_equal( + syncCallCount, + static_cast(1), + "Sync callback should be cleared by deinit" + ); + + logger.deinit(); } void test_live_callback_receives_logs_immediately() { - test_support::resetMillis(); - - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 10; - config.consoleLogLevel = LogLevel::Error; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - std::vector liveLogs; - logger.attach([&liveLogs](const Log &entry) { liveLogs.push_back(entry); }); - - logger.info("LIVE", "first %d", 1); - logger.warn("LIVE", "second %d", 2); - - expect_equal(liveLogs.size(), static_cast(2), "Live callback should run on every log call"); - expect_equal(liveLogs.front().level, LogLevel::Info, "Live callback first level mismatch"); - expect_equal(liveLogs.front().tag, std::string("LIVE"), "Live callback first tag mismatch"); - expect_equal(liveLogs.front().message, std::string("first 1"), "Live callback first message mismatch"); - expect_equal(liveLogs.back().level, LogLevel::Warn, "Live callback second level mismatch"); - expect_equal(liveLogs.back().message, std::string("second 2"), "Live callback second message mismatch"); - expect_equal(logger.getAllLogs().size(), static_cast(2), "Live callback should not affect RAM log buffering"); - - logger.deinit(); + test_support::resetMillis(); + + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 10; + config.consoleLogLevel = LogLevel::Error; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + std::vector liveLogs; + logger.attach([&liveLogs](const Log &entry) { liveLogs.push_back(entry); }); + + logger.info("LIVE", "first %d", 1); + logger.warn("LIVE", "second %d", 2); + + expect_equal( + liveLogs.size(), + static_cast(2), + "Live callback should run on every log call" + ); + expect_equal(liveLogs.front().level, LogLevel::Info, "Live callback first level mismatch"); + expect_equal(liveLogs.front().tag, std::string("LIVE"), "Live callback first tag mismatch"); + expect_equal( + liveLogs.front().message, + std::string("first 1"), + "Live callback first message mismatch" + ); + expect_equal(liveLogs.back().level, LogLevel::Warn, "Live callback second level mismatch"); + expect_equal( + liveLogs.back().message, + std::string("second 2"), + "Live callback second message mismatch" + ); + expect_equal( + logger.getAllLogs().size(), + static_cast(2), + "Live callback should not affect RAM log buffering" + ); + + logger.deinit(); } void test_detach_disables_live_callback() { - test_support::resetMillis(); - - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 10; - config.consoleLogLevel = LogLevel::Debug; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - size_t callCount = 0; - logger.attach([&callCount](const Log &) { ++callCount; }); - logger.info("DETACH", "before"); - logger.detach(); - logger.info("DETACH", "after"); - - expect_equal(callCount, static_cast(1), "detach should stop live callback notifications"); - - logger.deinit(); + test_support::resetMillis(); + + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 10; + config.consoleLogLevel = LogLevel::Debug; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + size_t callCount = 0; + logger.attach([&callCount](const Log &) { ++callCount; }); + logger.info("DETACH", "before"); + logger.detach(); + logger.info("DETACH", "after"); + + expect_equal( + callCount, + static_cast(1), + "detach should stop live callback notifications" + ); + + logger.deinit(); } void test_live_and_sync_callbacks_can_be_used_together() { - test_support::resetMillis(); + test_support::resetMillis(); - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 10; - config.consoleLogLevel = LogLevel::Debug; + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 10; + config.consoleLogLevel = LogLevel::Debug; - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } - std::vector liveLogs; - std::vector syncedLogs; + std::vector liveLogs; + std::vector syncedLogs; - logger.attach([&liveLogs](const Log &entry) { liveLogs.push_back(entry); }); - logger.onSync([&syncedLogs](const std::vector &logs) { syncedLogs = logs; }); + logger.attach([&liveLogs](const Log &entry) { liveLogs.push_back(entry); }); + logger.onSync([&syncedLogs](const std::vector &logs) { syncedLogs = logs; }); - logger.debug("COEXIST", "a"); - logger.error("COEXIST", "b"); + logger.debug("COEXIST", "a"); + logger.error("COEXIST", "b"); - expect_equal(liveLogs.size(), static_cast(2), "Live callback should observe both entries"); - expect_true(syncedLogs.empty(), "Sync callback should not run until sync() is called"); + expect_equal( + liveLogs.size(), + static_cast(2), + "Live callback should observe both entries" + ); + expect_true(syncedLogs.empty(), "Sync callback should not run until sync() is called"); - logger.sync(); + logger.sync(); - expect_equal(syncedLogs.size(), static_cast(2), "Sync callback should still receive buffered entries"); - expect_equal(syncedLogs.front().message, std::string("a"), "First synced message mismatch"); - expect_equal(syncedLogs.back().message, std::string("b"), "Second synced message mismatch"); + expect_equal( + syncedLogs.size(), + static_cast(2), + "Sync callback should still receive buffered entries" + ); + expect_equal(syncedLogs.front().message, std::string("a"), "First synced message mismatch"); + expect_equal(syncedLogs.back().message, std::string("b"), "Second synced message mismatch"); - logger.deinit(); + logger.deinit(); } void test_set_log_level_updates_config() { - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 5; - config.consoleLogLevel = LogLevel::Debug; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - logger.setLogLevel(LogLevel::Error); - - expect_equal(logger.logLevel(), LogLevel::Error, "Log level should update"); - expect_equal(logger.currentConfig().consoleLogLevel, LogLevel::Error, "Config should reflect new console log level"); - - logger.deinit(); + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 5; + config.consoleLogLevel = LogLevel::Debug; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + logger.setLogLevel(LogLevel::Error); + + expect_equal(logger.logLevel(), LogLevel::Error, "Log level should update"); + expect_equal( + logger.currentConfig().consoleLogLevel, + LogLevel::Error, + "Config should reflect new console log level" + ); + + logger.deinit(); } void test_multiple_logger_instances_operate_independently() { - test_support::resetMillis(); - - ESPLogger first; - ESPLogger second; - - LoggerConfig configA; - configA.enableSyncTask = false; - configA.maxLogInRam = 3; - configA.consoleLogLevel = LogLevel::Debug; - - LoggerConfig configB = configA; - configB.maxLogInRam = 5; - - if (!first.init(configA) || !second.init(configB)) { - fail("Failed to initialize independent loggers"); - } - - first.info("FIRST", "one"); - second.warn("SECOND", "alpha"); - first.error("FIRST", "two"); - second.debug("SECOND", "beta"); - - const auto firstLogs = first.getAllLogs(); - const auto secondLogs = second.getAllLogs(); - - expect_equal(firstLogs.size(), static_cast(2), "First logger should keep its own entries"); - expect_equal(firstLogs.front().tag, std::string("FIRST"), "First logger tag mismatch"); - expect_equal(secondLogs.size(), static_cast(2), "Second logger should keep its own entries"); - expect_equal(secondLogs.front().tag, std::string("SECOND"), "Second logger tag mismatch"); - - first.deinit(); - second.deinit(); + test_support::resetMillis(); + + ESPLogger first; + ESPLogger second; + + LoggerConfig configA; + configA.enableSyncTask = false; + configA.maxLogInRam = 3; + configA.consoleLogLevel = LogLevel::Debug; + + LoggerConfig configB = configA; + configB.maxLogInRam = 5; + + if (!first.init(configA) || !second.init(configB)) { + fail("Failed to initialize independent loggers"); + } + + first.info("FIRST", "one"); + second.warn("SECOND", "alpha"); + first.error("FIRST", "two"); + second.debug("SECOND", "beta"); + + const auto firstLogs = first.getAllLogs(); + const auto secondLogs = second.getAllLogs(); + + expect_equal( + firstLogs.size(), + static_cast(2), + "First logger should keep its own entries" + ); + expect_equal(firstLogs.front().tag, std::string("FIRST"), "First logger tag mismatch"); + expect_equal( + secondLogs.size(), + static_cast(2), + "Second logger should keep its own entries" + ); + expect_equal(secondLogs.front().tag, std::string("SECOND"), "Second logger tag mismatch"); + + first.deinit(); + second.deinit(); } void test_get_logs_by_level() { - test_support::resetMillis(); - - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 5; - config.consoleLogLevel = LogLevel::Debug; - - if (!logger.init(config)) { - fail("Failed to initialize logger"); - } - - logger.debug("TAG", "message a"); - logger.info("TAG", "message b"); - logger.warn("TAG", "message c"); - logger.info("TAG", "message d"); - - expect_equal(logger.getLogCount(LogLevel::Info), 2, "getLogCount should count matching levels only"); - expect_equal(logger.getLogCount(LogLevel::Error), 0, "getLogCount should return zero when no logs match"); - - const auto infoLogs = logger.getLogs(LogLevel::Info); - expect_equal(infoLogs.size(), static_cast(2), "getLogs should filter by level"); - expect_equal(infoLogs.front().message, std::string("message b"), "First filtered log mismatch"); - expect_equal(infoLogs.back().message, std::string("message d"), "Second filtered log mismatch"); - - expect_equal(logger.getAllLogs().size(), static_cast(4), "getLogs should not mutate stored entries"); - - logger.deinit(); + test_support::resetMillis(); + + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 5; + config.consoleLogLevel = LogLevel::Debug; + + if (!logger.init(config)) { + fail("Failed to initialize logger"); + } + + logger.debug("TAG", "message a"); + logger.info("TAG", "message b"); + logger.warn("TAG", "message c"); + logger.info("TAG", "message d"); + + expect_equal( + logger.getLogCount(LogLevel::Info), + 2, + "getLogCount should count matching levels only" + ); + expect_equal( + logger.getLogCount(LogLevel::Error), + 0, + "getLogCount should return zero when no logs match" + ); + + const auto infoLogs = logger.getLogs(LogLevel::Info); + expect_equal(infoLogs.size(), static_cast(2), "getLogs should filter by level"); + expect_equal(infoLogs.front().message, std::string("message b"), "First filtered log mismatch"); + expect_equal(infoLogs.back().message, std::string("message d"), "Second filtered log mismatch"); + + expect_equal( + logger.getAllLogs().size(), + static_cast(4), + "getLogs should not mutate stored entries" + ); + + logger.deinit(); } void test_static_helpers_on_snapshot() { - std::vector snapshot = { - {LogLevel::Debug, "TAG", 1, 1, "a"}, - {LogLevel::Info, "TAG", 2, 2, "b"}, - {LogLevel::Warn, "TAG", 3, 3, "c"}, - {LogLevel::Info, "TAG", 4, 4, "d"}, - }; - - expect_equal(ESPLogger::getLogCount(snapshot, LogLevel::Info), 2, "Static getLogCount should work on snapshots"); - expect_equal(ESPLogger::getLogCount(snapshot, LogLevel::Error), 0, "Static getLogCount should return zero if no match"); - - const auto warnLogs = ESPLogger::getLogs(snapshot, LogLevel::Warn); - expect_equal(warnLogs.size(), static_cast(1), "Static getLogs should filter snapshots"); - expect_equal(warnLogs.front().message, std::string("c"), "Static getLogs should preserve message order"); + std::vector snapshot = { + {LogLevel::Debug, "TAG", 1, 1, "a"}, + {LogLevel::Info, "TAG", 2, 2, "b"}, + {LogLevel::Warn, "TAG", 3, 3, "c"}, + {LogLevel::Info, "TAG", 4, 4, "d"}, + }; + + expect_equal( + ESPLogger::getLogCount(snapshot, LogLevel::Info), + 2, + "Static getLogCount should work on snapshots" + ); + expect_equal( + ESPLogger::getLogCount(snapshot, LogLevel::Error), + 0, + "Static getLogCount should return zero if no match" + ); + + const auto warnLogs = ESPLogger::getLogs(snapshot, LogLevel::Warn); + expect_equal(warnLogs.size(), static_cast(1), "Static getLogs should filter snapshots"); + expect_equal( + warnLogs.front().message, + std::string("c"), + "Static getLogs should preserve message order" + ); } void test_destructor_calls_deinit_and_flushes_pending_logs() { - test_support::resetMillis(); - - std::vector flushedLogs; - { - ESPLogger logger; - LoggerConfig config; - config.enableSyncTask = false; - config.maxLogInRam = 4; - config.consoleLogLevel = LogLevel::Debug; - - if (!logger.init(config)) { - fail("ESPLogger failed to initialize"); - } - - logger.onSync([&flushedLogs](const std::vector &logs) { flushedLogs = logs; }); - logger.info("DTOR", "pending"); - } - - expect_equal(flushedLogs.size(), static_cast(1), "Destructor should deinit and flush pending logs"); - expect_equal(flushedLogs.front().message, std::string("pending"), "Destructor flush should preserve message"); + test_support::resetMillis(); + + std::vector flushedLogs; + { + ESPLogger logger; + LoggerConfig config; + config.enableSyncTask = false; + config.maxLogInRam = 4; + config.consoleLogLevel = LogLevel::Debug; + + if (!logger.init(config)) { + fail("ESPLogger failed to initialize"); + } + + logger.onSync([&flushedLogs](const std::vector &logs) { flushedLogs = logs; }); + logger.info("DTOR", "pending"); + } + + expect_equal( + flushedLogs.size(), + static_cast(1), + "Destructor should deinit and flush pending logs" + ); + expect_equal( + flushedLogs.front().message, + std::string("pending"), + "Destructor flush should preserve message" + ); } -} // namespace +} // namespace int main() { - try { - test_init_with_default_config(); - test_init_applies_normalized_config(); - test_deinit_is_safe_before_init(); - test_deinit_is_idempotent(); - test_reinit_after_deinit(); - test_stores_logs_up_to_configured_capacity(); - test_sync_callback_receives_buffered_logs(); - test_deinit_flushes_and_clears_callbacks(); - test_live_callback_receives_logs_immediately(); - test_detach_disables_live_callback(); - test_live_and_sync_callbacks_can_be_used_together(); - test_set_log_level_updates_config(); - test_multiple_logger_instances_operate_independently(); - test_get_logs_by_level(); - test_static_helpers_on_snapshot(); - test_destructor_calls_deinit_and_flushes_pending_logs(); - } catch (const std::exception &ex) { - std::cerr << "Test failure: " << ex.what() << '\n'; - return 1; - } - - std::cout << "All tests passed\n"; - return 0; + try { + test_init_with_default_config(); + test_init_applies_normalized_config(); + test_deinit_is_safe_before_init(); + test_deinit_is_idempotent(); + test_reinit_after_deinit(); + test_stores_logs_up_to_configured_capacity(); + test_sync_callback_receives_buffered_logs(); + test_deinit_flushes_and_clears_callbacks(); + test_live_callback_receives_logs_immediately(); + test_detach_disables_live_callback(); + test_live_and_sync_callbacks_can_be_used_together(); + test_set_log_level_updates_config(); + test_multiple_logger_instances_operate_independently(); + test_get_logs_by_level(); + test_static_helpers_on_snapshot(); + test_destructor_calls_deinit_and_flushes_pending_logs(); + } catch (const std::exception &ex) { + std::cerr << "Test failure: " << ex.what() << '\n'; + return 1; + } + + std::cout << "All tests passed\n"; + return 0; } diff --git a/test/stubs/ESPWorker.h b/test/stubs/ESPWorker.h index b1a5a7f..40c964c 100644 --- a/test/stubs/ESPWorker.h +++ b/test/stubs/ESPWorker.h @@ -8,49 +8,57 @@ #include "freertos/task.h" struct WorkerConfig { - size_t stackSizeBytes = 4096; - UBaseType_t priority = 1; - BaseType_t coreId = tskNO_AFFINITY; - std::string name{}; - bool useExternalStack = false; + size_t stackSizeBytes = 4096; + UBaseType_t priority = 1; + BaseType_t coreId = tskNO_AFFINITY; + std::string name{}; + bool useExternalStack = false; }; class WorkerHandler { - public: - bool wait(TickType_t = portMAX_DELAY) { return true; } - bool destroy() { return true; } + public: + bool wait(TickType_t = portMAX_DELAY) { + return true; + } + bool destroy() { + return true; + } }; struct WorkerResult { - int error = 0; - std::shared_ptr handler{}; - const char *message = nullptr; + int error = 0; + std::shared_ptr handler{}; + const char *message = nullptr; - explicit operator bool() const { return error == 0 && static_cast(handler); } + explicit operator bool() const { + return error == 0 && static_cast(handler); + } }; class ESPWorker { - public: - struct Config { - size_t maxWorkers = 1; - size_t stackSizeBytes = 4096; - UBaseType_t priority = 1; - BaseType_t coreId = tskNO_AFFINITY; - bool enableExternalStacks = true; - }; - - using TaskCallback = std::function; - - void init(const Config &) {} - void deinit() {} - - WorkerResult spawn(TaskCallback, const WorkerConfig & = WorkerConfig{}) { - WorkerResult result{}; - result.handler = std::make_shared(); - return result; - } - - WorkerResult spawnExt(TaskCallback cb, const WorkerConfig &cfg = WorkerConfig{}) { - return spawn(std::move(cb), cfg); - } + public: + struct Config { + size_t maxWorkers = 1; + size_t stackSizeBytes = 4096; + UBaseType_t priority = 1; + BaseType_t coreId = tskNO_AFFINITY; + bool enableExternalStacks = true; + }; + + using TaskCallback = std::function; + + void init(const Config &) { + } + void deinit() { + } + + WorkerResult spawn(TaskCallback, const WorkerConfig & = WorkerConfig{}) { + WorkerResult result{}; + result.handler = std::make_shared(); + return result; + } + + WorkerResult spawnExt(TaskCallback cb, const WorkerConfig &cfg = WorkerConfig{}) { + return spawn(std::move(cb), cfg); + } }; diff --git a/test/stubs/freertos/FreeRTOS.h b/test/stubs/freertos/FreeRTOS.h index 3e394b1..caa4cc1 100644 --- a/test/stubs/freertos/FreeRTOS.h +++ b/test/stubs/freertos/FreeRTOS.h @@ -9,13 +9,13 @@ extern "C" { typedef int BaseType_t; typedef unsigned int UBaseType_t; typedef uint32_t TickType_t; -typedef void * TaskHandle_t; -typedef void * SemaphoreHandle_t; +typedef void *TaskHandle_t; +typedef void *SemaphoreHandle_t; typedef uint32_t StackType_t; #define pdPASS 1 #define pdFAIL 0 -#define portMAX_DELAY ((TickType_t)-1) +#define portMAX_DELAY ((TickType_t) - 1) #define tskNO_AFFINITY (-1) #define pdMS_TO_TICKS(ms) ((TickType_t)(ms)) diff --git a/test/stubs/freertos/task.h b/test/stubs/freertos/task.h index fcae2d0..4ce38e0 100644 --- a/test/stubs/freertos/task.h +++ b/test/stubs/freertos/task.h @@ -8,13 +8,15 @@ extern "C" { typedef void (*TaskFunction_t)(void *); -BaseType_t xTaskCreatePinnedToCore(TaskFunction_t task, - const char *name, - uint32_t stackDepth, - void *parameters, - UBaseType_t priority, - TaskHandle_t *createdTask, - BaseType_t coreId); +BaseType_t xTaskCreatePinnedToCore( + TaskFunction_t task, + const char *name, + uint32_t stackDepth, + void *parameters, + UBaseType_t priority, + TaskHandle_t *createdTask, + BaseType_t coreId +); void vTaskDelete(TaskHandle_t task); void vTaskDelay(TickType_t ticks); diff --git a/test/stubs_json/ArduinoJson.h b/test/stubs_json/ArduinoJson.h index 55ab0a4..efcb9a6 100644 --- a/test/stubs_json/ArduinoJson.h +++ b/test/stubs_json/ArduinoJson.h @@ -12,193 +12,201 @@ namespace ArduinoJson { namespace detail { -inline std::string quote(const std::string& value) { - std::string quoted; - quoted.reserve(value.size() + 2); - quoted.push_back('"'); - for (char ch : value) { - if (ch == '"' || ch == '\\') { - quoted.push_back('\\'); - } - quoted.push_back(ch); - } - quoted.push_back('"'); - return quoted; +inline std::string quote(const std::string &value) { + std::string quoted; + quoted.reserve(value.size() + 2); + quoted.push_back('"'); + for (char ch : value) { + if (ch == '"' || ch == '\\') { + quoted.push_back('\\'); + } + quoted.push_back(ch); + } + quoted.push_back('"'); + return quoted; } -inline std::string indentSubsequentLines(const std::string& text, const std::string& indent) { - std::string result; - result.reserve(text.size() + indent.size()); +inline std::string indentSubsequentLines(const std::string &text, const std::string &indent) { + std::string result; + result.reserve(text.size() + indent.size()); - for (char ch : text) { - result.push_back(ch); - if (ch == '\n') { - result += indent; - } - } + for (char ch : text) { + result.push_back(ch); + if (ch == '\n') { + result += indent; + } + } - return result; + return result; } -} // namespace detail +} // namespace detail class JsonVariantConst { public: - JsonVariantConst() = default; - JsonVariantConst(const char* value) : _compact(detail::quote(value != nullptr ? value : "")), _pretty(_compact) {} - JsonVariantConst(int value) : _compact(std::to_string(value)), _pretty(_compact) {} - JsonVariantConst(bool value) : _compact(value ? "true" : "false"), _pretty(_compact) {} - - static JsonVariantConst fromRaw(std::string compact, std::string pretty) { - return JsonVariantConst(std::move(compact), std::move(pretty), RawTag{}); - } - - const std::string& compact() const { return _compact; } - const std::string& pretty() const { return _pretty; } + JsonVariantConst() = default; + JsonVariantConst(const char *value) + : _compact(detail::quote(value != nullptr ? value : "")), _pretty(_compact) { + } + JsonVariantConst(int value) : _compact(std::to_string(value)), _pretty(_compact) { + } + JsonVariantConst(bool value) : _compact(value ? "true" : "false"), _pretty(_compact) { + } + + static JsonVariantConst fromRaw(std::string compact, std::string pretty) { + return JsonVariantConst(std::move(compact), std::move(pretty), RawTag{}); + } + + const std::string &compact() const { + return _compact; + } + const std::string &pretty() const { + return _pretty; + } private: - struct RawTag {}; + struct RawTag {}; - JsonVariantConst(std::string compact, std::string pretty, RawTag) - : _compact(std::move(compact)), _pretty(std::move(pretty)) {} + JsonVariantConst(std::string compact, std::string pretty, RawTag) + : _compact(std::move(compact)), _pretty(std::move(pretty)) { + } - std::string _compact; - std::string _pretty; + std::string _compact; + std::string _pretty; }; class JsonDocument { public: - class MemberProxy { - public: - MemberProxy(JsonDocument& owner, std::string key) : _owner(owner), _key(std::move(key)) {} - - MemberProxy& operator=(const char* value) { - _owner.set(_key, JsonVariantConst(value)); - return *this; - } - - MemberProxy& operator=(int value) { - _owner.set(_key, JsonVariantConst(value)); - return *this; - } - - MemberProxy& operator=(bool value) { - _owner.set(_key, JsonVariantConst(value)); - return *this; - } - - MemberProxy& operator=(JsonVariantConst value) { - _owner.set(_key, std::move(value)); - return *this; - } - - private: - JsonDocument& _owner; - std::string _key; - }; - - MemberProxy operator[](const char* key) { - return MemberProxy(*this, key != nullptr ? key : ""); - } - - template - T as() const; + class MemberProxy { + public: + MemberProxy(JsonDocument &owner, std::string key) : _owner(owner), _key(std::move(key)) { + } + + MemberProxy &operator=(const char *value) { + _owner.set(_key, JsonVariantConst(value)); + return *this; + } + + MemberProxy &operator=(int value) { + _owner.set(_key, JsonVariantConst(value)); + return *this; + } + + MemberProxy &operator=(bool value) { + _owner.set(_key, JsonVariantConst(value)); + return *this; + } + + MemberProxy &operator=(JsonVariantConst value) { + _owner.set(_key, std::move(value)); + return *this; + } + + private: + JsonDocument &_owner; + std::string _key; + }; + + MemberProxy operator[](const char *key) { + return MemberProxy(*this, key != nullptr ? key : ""); + } + + template T as() const; private: - friend class MemberProxy; + friend class MemberProxy; - void set(const std::string& key, JsonVariantConst value) { - for (auto& member : _members) { - if (member.first == key) { - member.second = std::move(value); - return; - } - } + void set(const std::string &key, JsonVariantConst value) { + for (auto &member : _members) { + if (member.first == key) { + member.second = std::move(value); + return; + } + } - _members.emplace_back(key, std::move(value)); - } + _members.emplace_back(key, std::move(value)); + } - JsonVariantConst toVariant() const { - std::string compact = "{"; - std::string pretty = "{"; + JsonVariantConst toVariant() const { + std::string compact = "{"; + std::string pretty = "{"; - if (!_members.empty()) { - pretty.push_back('\n'); - } + if (!_members.empty()) { + pretty.push_back('\n'); + } - for (size_t index = 0; index < _members.size(); ++index) { - const auto& member = _members[index]; - const bool isLast = index + 1 == _members.size(); + for (size_t index = 0; index < _members.size(); ++index) { + const auto &member = _members[index]; + const bool isLast = index + 1 == _members.size(); - compact += detail::quote(member.first); - compact += ':'; - compact += member.second.compact(); + compact += detail::quote(member.first); + compact += ':'; + compact += member.second.compact(); - pretty += " "; - pretty += detail::quote(member.first); - pretty += ": "; - pretty += detail::indentSubsequentLines(member.second.pretty(), " "); + pretty += " "; + pretty += detail::quote(member.first); + pretty += ": "; + pretty += detail::indentSubsequentLines(member.second.pretty(), " "); - if (!isLast) { - compact.push_back(','); - pretty += ",\n"; - } - } + if (!isLast) { + compact.push_back(','); + pretty += ",\n"; + } + } - if (!_members.empty()) { - pretty.push_back('\n'); - } + if (!_members.empty()) { + pretty.push_back('\n'); + } - compact.push_back('}'); - pretty.push_back('}'); + compact.push_back('}'); + pretty.push_back('}'); - return JsonVariantConst::fromRaw(std::move(compact), std::move(pretty)); - } + return JsonVariantConst::fromRaw(std::move(compact), std::move(pretty)); + } - std::vector> _members; + std::vector> _members; }; -template <> -inline JsonVariantConst JsonDocument::as() const { - return toVariant(); +template <> inline JsonVariantConst JsonDocument::as() const { + return toVariant(); } inline size_t measureJson(JsonVariantConst source) { - return source.compact().size(); + return source.compact().size(); } inline size_t measureJsonPretty(JsonVariantConst source) { - return source.pretty().size(); + return source.pretty().size(); } -inline size_t serializeJson(JsonVariantConst source, void* buffer, size_t bufferSize) { - if (buffer == nullptr || bufferSize == 0) { - return 0; - } +inline size_t serializeJson(JsonVariantConst source, void *buffer, size_t bufferSize) { + if (buffer == nullptr || bufferSize == 0) { + return 0; + } - const std::string& text = source.compact(); - if (text.empty() || bufferSize <= text.size()) { - return 0; - } + const std::string &text = source.compact(); + if (text.empty() || bufferSize <= text.size()) { + return 0; + } - std::memcpy(buffer, text.data(), text.size()); - static_cast(buffer)[text.size()] = '\0'; - return text.size(); + std::memcpy(buffer, text.data(), text.size()); + static_cast(buffer)[text.size()] = '\0'; + return text.size(); } -inline size_t serializeJsonPretty(JsonVariantConst source, void* buffer, size_t bufferSize) { - if (buffer == nullptr || bufferSize == 0) { - return 0; - } +inline size_t serializeJsonPretty(JsonVariantConst source, void *buffer, size_t bufferSize) { + if (buffer == nullptr || bufferSize == 0) { + return 0; + } - const std::string& text = source.pretty(); - if (text.empty() || bufferSize <= text.size()) { - return 0; - } + const std::string &text = source.pretty(); + if (text.empty() || bufferSize <= text.size()) { + return 0; + } - std::memcpy(buffer, text.data(), text.size()); - static_cast(buffer)[text.size()] = '\0'; - return text.size(); + std::memcpy(buffer, text.data(), text.size()); + static_cast(buffer)[text.size()] = '\0'; + return text.size(); } -} // namespace ArduinoJson +} // namespace ArduinoJson