From 0503389d7b93fd5665fe51cbc404eafa60af5d45 Mon Sep 17 00:00:00 2001 From: Jibril Sharafi <83494924+jibrilsharafi@users.noreply.github.com> Date: Fri, 22 Mar 2024 07:26:25 +0100 Subject: [PATCH] First release (#1) * First full commit * Renaming to AdvancedLogger * Update README to include TLDR Other minor changes * Changed private variables to camelCase * Improved example with more functions and comments * Moved header file to src * Added library properties --- .gitignore | 3 + README.md | 72 ++++++++- examples/basicUsage/basicUsage.cpp | 65 ++++++++ keywords.txt | 31 ++++ library.json | 29 ++++ library.properties | 10 ++ src/advancedLogger.cpp | 231 +++++++++++++++++++++++++++++ src/advancedLogger.h | 76 ++++++++++ 8 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 examples/basicUsage/basicUsage.cpp create mode 100644 keywords.txt create mode 100644 library.json create mode 100644 library.properties create mode 100644 src/advancedLogger.cpp create mode 100644 src/advancedLogger.h diff --git a/.gitignore b/.gitignore index 259148f..1bdbc66 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ *.exe *.out *.app + +# Visual Studio Code +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 0a43c4d..d34a95b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,72 @@ # AdvancedLogger -Comprehensive C++ logging module providing formatted logging, memory storage, and ease of use. + +A **simple** logging library capable of **saving logs to memory** and with a **comprehensive format** capable of including all the accessory information to each message. + +Usage: +```cpp +AdvancedLogger logger; + +... + +logger.log("This is an info message!", "main::loop", ADVANCEDLOGGER_INFO); +delay(1000); +logger.log("This is an error message!!", "main::loop", ADVANCEDLOGGER_ERROR); +``` +Output (both in the Serial and in the log file in memory): +```cpp +[2024-03-23 09:44:10] [1450 ms] [INFO] [Core 1] [main::loop] This is an info message! +[2024-03-23 09:44:11] [1550 ms] [ERROR] [Core 1] [main::loop] This is an error message!! +``` +For a detailed example, see the [basicExample](examples/basicUsage/basicUsage.cpp) in the examples folder. + +## Why? +There is no way of sugar-coat it: *every well-developed project will eventually need a proper logging* in place to make sure that not only everything is fully monitored, but also that everything monitored is later accessible for debugging. +The development of the [[EnergyMe]] (my open-source energy monitoring device) quickly led to the necessity of having such feature. Nonetheless, I quickly found out that no repositories on GitHub where available capable of fully covering all of my [[#Requirements]]. +### Requirements +Does it exist a C++ module for the ESP32 (the projects I am currently working on use this microcontroller) that allows logging with the following features: +- **Format**: a comprehensive format capable of including all the accessory information to each print message. Something like: `[2024-03-13 09:44:10] [1313 ms] [INFO] [Core 1] [main::setup] Setting up ADE7953...`, which indeed explains very clearly the nature and context of the message. This type of logging is the norm in more complex systems and environments. +- **Saving to memory**: developing a complex logging system is useless if it can only be accessed in real time. The ability of saving each message in chronological order in a long time storage medium is crucial to unlock the full potential of it. +- **Ease of use**: the module should comprise of only a few functions, such as the core one `log(...)` and the accessory ones needed to set the print and save levels to adapt to each use case. +### Don't reinvent the wheel +Some existing libraries are: + +| Name | Owner | Description | Format | Saving | Ease of use | +| :------------------------------------------------------------------------------------------------: | :-----------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------: | :----: | :----: | :---------: | +| **[elog](https://github.com/x821938/elog)** | [x821938](https://github.com/x821938) | Fast and efficient logging to multiple outputs, with many configuration settings. | ❕ | ✔ | ✔ | +| **[ESP.multiLogger](https://github.com/voelkerb/ESP.multiLogger)** | [voelkerb](https://github.com/voelkerb) | Simple logging to multiple outputs. | ❌ | ✔ | ✔ | +| **[logging](https://github.com/esp32m/logging)** | [esp32m](https://github.com/esp32m) | Context-based logging, multiple appenders, buffers and queues. | ❌ | ✔ | ❕ | +| **[MycilaLogger](https://github.com/mathieucarbou/MycilaLogger)** | [mathieucarbou](https://github.com/mathieucarbou) | Simple and efficient. A wrapper around the standard `buffer.print()`. | ❌ | ❌ | ✔ | +| **[esp_log.h](https://github.com/espressif/esp-idf/blob/master/components/log/include/esp_log.h)** | [espressif](https://github.com/espressif) | Native method implemented by espressif. [Documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/log.html). | ❌ | ❌ | ❌ | + +Many other similar libraries exists, but it is clear that **none of the reported libraries** (and of the others available on *GitHub*) **fully match my requirements**. +## How? +The rule is one: *keep it simple*. +I am in no way a advanced coder, and as such I tend to structure and write code to be as streamlined and simple as possible. So, no memory optimization or any of the pointer-magic stuff in C++. *Not my kink*. +Let's start from the [[#Requirements]]. +### Format +**The format** of the message should **include any information** that could store **any value**. As such, everything related to the time should be logged, along with information about the location of the code in which the log takes place, as well as having different logging levels. +We can set a custom format by using the function `sprintf` and passing the following as third argument: +- `[%s] [%lu ms] [%s] [Core %d] [%s] %s` → `[2024-03-13 09:44:10] [1313 ms] [INFO] [Core 1] [main::setup] Setting up ADE7953...` +which will represent the following parts: +- `[%s]` → `[2024-03-13 09:44:10]`: human-readable ISO8601 format in the format `%Y-%m-%d %H:%M:%S`. Indeed, the time must be set externally via other libraries. +- `[%lu ms]` → `[1313 ms]`: time passed since device boot, in *ms*. Crucial to precisely evaluate the time taken to perform any action. +- `[%s]` → `[INFO]`: the log level, which could be one of the following 5 levels: *DEBUG, INFO, WARNING, ERROR, FATAL*. +- `[Core %d]` → `[Core 1]`: core in which the operation takes place. Useful for multi-core devices (such as many ESP32 variations). +- `[%s]` → `[main::setup]`: the current function, or in general any name to identify a particular section of the code. +- `%s` → `Setting up ADE7953...`: the actual message that contains all the info needed to be logged. +### Saving to memory +Saving should be quite straightforward: just copy any message that is sent to the serial to a log file, appending it to the end with a newline. It has to be noted that a memory clearing has to be set in place in order to avoid filling all the SPIFFS memory of the device, making it slow and unresponsive. +### Ease of use +Last but not least, the use should be as simple as possible: you just need to create `AdvancedLogger advancedLogger;` and simply use `advancedLogger.log("Setting up ADE7953...", "main::setup", INFO);` + +## Dependencies +This project depends on the following libraries: +- [ArduinoJson](https://github.com/bblanchon/ArduinoJson), version 7.0.0 or later. + +## What's next? +- [ ] **Automatic log clearing**: if the free memory is less than a certain threshold, the oldest logs should be deleted, keeping the memory usage under control. +- [ ] **Log to SD card**: the ability to log to an external SD card would be a great addition, as it would allow to store a much larger amount of logs. +- [ ] **Dump to serial**: implement a functino that dumps the entire log to the serial, so that it can be accessed in real time. +- [ ] **Remove ArduinoJson dependency**: the library is used only for the configuration file, and as such it could be removed by implementing a simpler configuration in .txt format. +- [ ] **Upgrade to LittleFS**: the SPIFFS library is deprecated, and as such it should be replaced with the LittleFS library. +- [ ] **Test other microcontrollers**: the library is currently tested only on the ESP32S3, but it should be tested on other microcontrollers to ensure compatibility. \ No newline at end of file diff --git a/examples/basicUsage/basicUsage.cpp b/examples/basicUsage/basicUsage.cpp new file mode 100644 index 0000000..c084d9a --- /dev/null +++ b/examples/basicUsage/basicUsage.cpp @@ -0,0 +1,65 @@ +/* + * File: basicUsage.cpp + * -------------------- + * This file provides a simple example to show how to use the AdvancedLogger library. + * + * Author: Jibril Sharafi, @jibrilsharafi + * Date: 21/03/2024 + * GitHub repository: https://github.com/jibrilsharafi/AdvancedLogger + * + * This library is licensed under the MIT License. See the LICENSE file for more information. + * + * The AdvancedLogger library provides advanced logging for the ESP32. + * It allows you to log messages to the console and to a file on the SPIFFS. + * You can set the print level and save level, and the library will only log + * messages accordingly. + * + * Possible logging levels: + * - ADVANCEDLOGGER_VERBOSE + * - ADVANCEDLOGGER_DEBUG + * - ADVANCEDLOGGER_INFO + * - ADVANCEDLOGGER_WARNING + * - ADVANCEDLOGGER_ERROR + */ +#include +#include + +#include "advancedLogger.h" + +AdvancedLogger logger; + +String printLevel; +String saveLevel; + +void setup() { + Serial.begin(9600); + + if (!SPIFFS.begin()) { + Serial.println("An Error has occurred while mounting SPIFFS"); + } + + logger.begin(); + + logger.setPrintLevel(ADVANCEDLOGGER_VERBOSE); + logger.setSaveLevel(ADVANCEDLOGGER_INFO); + + logger.log("Setup done!", "simpleExample::setup", ADVANCEDLOGGER_INFO); +} + +void loop() { + logger.log("This is an debug message!", "simpleExample::loop", ADVANCEDLOGGER_VERBOSE); + logger.log("This is an info message!!", "simpleExample::loop", ADVANCEDLOGGER_INFO); + logger.log("This is an warning message!!!", "simpleExample::loop", ADVANCEDLOGGER_WARNING); + logger.log("This is an error message!!!!", "simpleExample::loop", ADVANCEDLOGGER_ERROR); + logger.log("This is an fatal message!!!!!", "simpleExample::loop", ADVANCEDLOGGER_FATAL); + delay(1000); + logger.logOnly("This is an info message (logOnly)!!", "simpleExample::loop", ADVANCEDLOGGER_INFO); + + printLevel = logger.getPrintLevel(); + saveLevel = logger.getSaveLevel(); + + if (millis() > 60000) { + logger.clearLog(); + logger.setDefaultLogLevels(); + } +} \ No newline at end of file diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..fa802c3 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,31 @@ +#################################################################################################### +# Syntax coloring for the AdvancedLogger library +#################################################################################################### + +#################################################################################################### +# AdvancedLogger keywords +#################################################################################################### +AdvancedLogger KEYWORD1 + +#################################################################################################### +# AdvancedLogger functions and methods +#################################################################################################### +log KEYWORD2 +logOnly KEYWORD2 +setPrintLevel KEYWORD2 +setSaveLevel KEYWORD2 +getPrintLevel KEYWORD2 +getSaveLevel KEYWORD2 +setDefaultLogLevels KEYWORD2 +setLogLevelsFromSpiffs KEYWORD2 +clearLog KEYWORD2 + +#################################################################################################### +# AdvancedLogger constants +#################################################################################################### +ADVANCEDLOGGER_VERBOSE LITERAL1 +ADVANCEDLOGGER_DEBUG LITERAL1 +ADVANCEDLOGGER_INFO LITERAL1 +ADVANCEDLOGGER_WARNING LITERAL1 +ADVANCEDLOGGER_ERROR LITERAL1 +ADVANCEDLOGGER_FATAL LITERAL1 \ No newline at end of file diff --git a/library.json b/library.json new file mode 100644 index 0000000..8176025 --- /dev/null +++ b/library.json @@ -0,0 +1,29 @@ +{ + "name": "AdvancedLogger", + "keywords": "onewire, 1-wire, bus, sensor, temperature", + "description": "Library for simple logging to memory with comprehensive format.", + "repository": + { + "type": "git", + "url": "https://github.com/jibrilsharafi/AdvancedLogger.git" + }, + "authors": + [ + { + "name": "Jibril Sharafi", + "email": "jibril.sharafi@gmail.com", + "url": "https://github.com/jibrilsharafi", + "maintainer": true + } + ], + "dependencies": + { + "name": "ArduinoJson", + "authors": "bblanchon", + "frameworks": "arduino" + }, + "version": "1.0.0", + "frameworks": "arduino", + "platforms": "*" + } + \ No newline at end of file diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..7690704 --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=AdvancedLogger +version=1.0.0 +author=Jibril Sharafi +maintainer=Jibril Sharafi +sentence=Library for simple logging to memory with comprehensive format. +paragraph=Easy to use, logs to memory using SPIFFS and the format contains all the information you need. +category=Communication +url=https://github.com/jibrilsharafi/AdvancedLogger +architectures=* +depends=ArduinoJson \ No newline at end of file diff --git a/src/advancedLogger.cpp b/src/advancedLogger.cpp new file mode 100644 index 0000000..f683706 --- /dev/null +++ b/src/advancedLogger.cpp @@ -0,0 +1,231 @@ +#include "advancedLogger.h" + +AdvancedLogger::AdvancedLogger() +{ + _printLevel = ADVANCEDLOGGER_DEFAULT_PRINT_LEVEL; + _saveLevel = ADVANCEDLOGGER_DEFAULT_SAVE_LEVEL; +} + +void AdvancedLogger::begin() +{ + if (!setLogLevelsFromSpiffs()) + { + setDefaultLogLevels(); + } + log("AdvancedLogger initialized", "AdvancedLogger::begin", ADVANCEDLOGGER_DEBUG); +} + +void AdvancedLogger::log(const char *message, const char *function, int logLevel) +{ + logLevel = _saturateLogLevel(logLevel); + if (logLevel < _printLevel && logLevel < _saveLevel) + { + return; + } + + char _message_formatted[26 + strlen(message) + strlen(function) + 70]; // 26 = length of timestamp, 70 = length of log level + + snprintf( + _message_formatted, + sizeof(_message_formatted), + ADVANCEDLOGGER_FORMAT, + _getTimestamp().c_str(), + millis(), + _logLevelToString(logLevel).c_str(), + xPortGetCoreID(), + function, + message); + + if (logLevel >= _printLevel) + { + Serial.println(_message_formatted); + } + + if (logLevel >= _saveLevel) + { + _save(_message_formatted); + } +} + +void AdvancedLogger::logOnly(const char *message, const char *function, int logLevel) +{ + logLevel = _saturateLogLevel(logLevel); + if (logLevel < _printLevel) + { + return; + } + + char _message_formatted[26 + strlen(message) + strlen(function) + 70]; // 26 = length of timestamp, 70 = length of log level + + snprintf( + _message_formatted, + sizeof(_message_formatted), + ADVANCEDLOGGER_FORMAT, + _getTimestamp().c_str(), + millis(), + _logLevelToString(logLevel).c_str(), + xPortGetCoreID(), + function, + message); + + if (logLevel >= _printLevel) + { + Serial.println(_message_formatted); + } +} + +void AdvancedLogger::setPrintLevel(int level) +{ + char _buffer[50]; + snprintf(_buffer, sizeof(_buffer), "Setting print level to %d", level); + log(_buffer, "AdvancedLogger::setPrintLevel", ADVANCEDLOGGER_WARNING); + _printLevel = _saturateLogLevel(level); + _saveLogLevelsToSpiffs(); +} + +void AdvancedLogger::setSaveLevel(int level) +{ + char _buffer[50]; + snprintf(_buffer, sizeof(_buffer), "Setting save level to %d", level); + log(_buffer, "AdvancedLogger::setSaveLevel", ADVANCEDLOGGER_WARNING); + _saveLevel = _saturateLogLevel(level); + _saveLogLevelsToSpiffs(); +} + +String AdvancedLogger::getPrintLevel() +{ + return _logLevelToString(_printLevel); +} + +String AdvancedLogger::getSaveLevel() +{ + return _logLevelToString(_saveLevel); +} + +void AdvancedLogger::setDefaultLogLevels() +{ + setPrintLevel(ADVANCEDLOGGER_DEFAULT_PRINT_LEVEL); + setSaveLevel(ADVANCEDLOGGER_DEFAULT_SAVE_LEVEL); + log("Log levels set to default", "AdvancedLogger::setDefaultLogLevels", ADVANCEDLOGGER_DEBUG); +} + +bool AdvancedLogger::setLogLevelsFromSpiffs() +{ + log("Deserializing JSON from SPIFFS", "utils::deserializeJsonFromSpiffs", ADVANCEDLOGGER_DEBUG); + + File _file = SPIFFS.open(ADVANCEDLOGGER_CONFIG_PATH, "r"); + if (!_file) + { + char _buffer[50 + strlen(ADVANCEDLOGGER_CONFIG_PATH)]; + snprintf(_buffer, sizeof(_buffer), "Failed to open file %s", ADVANCEDLOGGER_CONFIG_PATH); + log(_buffer, "utils::deserializeJsonFromSpiffs", ADVANCEDLOGGER_ERROR); + return false; + } + JsonDocument _jsonDocument; + + DeserializationError _error = deserializeJson(_jsonDocument, _file); + _file.close(); + if (_error) + { + char _buffer[50 + strlen(ADVANCEDLOGGER_CONFIG_PATH) + strlen(_error.c_str())]; + snprintf(_buffer, sizeof(_buffer), "Failed to deserialize file %s. Error: %s", ADVANCEDLOGGER_CONFIG_PATH, _error.c_str()); + log(_buffer, "utils::deserializeJsonFromSpiffs", ADVANCEDLOGGER_ERROR); + return false; + } + + log("JSON deserialized from SPIFFS correctly", "utils::deserializeJsonFromSpiffs", ADVANCEDLOGGER_DEBUG); + serializeJson(_jsonDocument, Serial); + Serial.println(); + + if (_jsonDocument.isNull()) + { + return false; + } + setPrintLevel(_jsonDocument["level"]["print"].as()); + setSaveLevel(_jsonDocument["level"]["save"].as()); + log("Log levels set from SPIFFS", "AdvancedLogger::setLogLevelsFromSpiffs", ADVANCEDLOGGER_DEBUG); + + return true; +} + +void AdvancedLogger::clearLog() +{ + logOnly("Clearing log", "AdvancedLogger::clearLog", ADVANCEDLOGGER_WARNING); + SPIFFS.remove(ADVANCEDLOGGER_LOG_PATH); + File _file = SPIFFS.open(ADVANCEDLOGGER_LOG_PATH, "w"); + if (!_file) + { + logOnly("Failed to open log file", "AdvancedLogger::clearLog", ADVANCEDLOGGER_ERROR); + return; + } + _file.close(); + log("Log cleared", "AdvancedLogger::clearLog", ADVANCEDLOGGER_WARNING); +} + +void AdvancedLogger::_save(const char *messageFormatted) +{ + File file = SPIFFS.open(ADVANCEDLOGGER_LOG_PATH, "a"); + if (file) + { + + file.println(messageFormatted); + file.close(); + } + else + { + logOnly("Failed to open log file", "AdvancedLogger::_save", ADVANCEDLOGGER_ERROR); + } +} + +void AdvancedLogger::_saveLogLevelsToSpiffs() +{ + JsonDocument _jsonDocument; + _jsonDocument["level"]["print"] = _printLevel; + _jsonDocument["level"]["save"] = _saveLevel; + File _file = SPIFFS.open(ADVANCEDLOGGER_CONFIG_PATH, "w"); + if (!_file) + { + log("Failed to open logger.json", "AdvancedLogger::_saveLogLevelsToSpiffs", ADVANCEDLOGGER_ERROR); + return; + } + serializeJson(_jsonDocument, _file); + _file.close(); + log("Log levels saved to SPIFFS", "AdvancedLogger::_saveLogLevelsToSpiffs", ADVANCEDLOGGER_DEBUG); +} + +String AdvancedLogger::_logLevelToString(int logLevel) +{ + switch (logLevel) + { + case ADVANCEDLOGGER_VERBOSE: + return "VERBOSE"; + case ADVANCEDLOGGER_DEBUG: + return "DEBUG"; + case ADVANCEDLOGGER_INFO: + return "INFO"; + case ADVANCEDLOGGER_WARNING: + return "WARNING"; + case ADVANCEDLOGGER_ERROR: + return "ERROR"; + case ADVANCEDLOGGER_FATAL: + return "FATAL"; + default: + return "UNKNOWN"; + } +} + +int AdvancedLogger::_saturateLogLevel(int logLevel) +{ + return min(max(logLevel, ADVANCEDLOGGER_VERBOSE), ADVANCEDLOGGER_FATAL); +} + +String AdvancedLogger::_getTimestamp() +{ + struct tm *_timeinfo; + char _timestamp[26]; + + long _time = static_cast(time(nullptr)); + _timeinfo = localtime(&_time); + strftime(_timestamp, sizeof(_timestamp), ADVANCEDLOGGER_TIMESTAMP_FORMAT, _timeinfo); + return String(_timestamp); +} diff --git a/src/advancedLogger.h b/src/advancedLogger.h new file mode 100644 index 0000000..18b3590 --- /dev/null +++ b/src/advancedLogger.h @@ -0,0 +1,76 @@ +/* + * File: AdvancedLogger.h + * ---------------------- + * This file exports the class AdvancedLogger, which provides + * advanced logging for the ESP32. + * + * Author: Jibril Sharafi, @jibrilsharafi + * Date: 21/03/2024 + * GitHub repository: https://github.com/jibrilsharafi/AdvancedLogger + * + * This library is licensed under the MIT License. See the LICENSE file for more information. + * + * This library provides advanced logging for the ESP32. + * It allows you to log messages to the console and to a file on the SPIFFS. + * You can set the print level and save level, and the library will only log + * messages accordingly. +*/ + +#ifndef ADVANCEDLOGGER_H +#define ADVANCEDLOGGER_H + +#define ADVANCEDLOGGER_VERBOSE 0 +#define ADVANCEDLOGGER_DEBUG 1 +#define ADVANCEDLOGGER_INFO 2 +#define ADVANCEDLOGGER_WARNING 3 +#define ADVANCEDLOGGER_ERROR 4 +#define ADVANCEDLOGGER_FATAL 5 + +#define ADVANCEDLOGGER_DEFAULT_PRINT_LEVEL 2 // 2 = INFO +#define ADVANCEDLOGGER_DEFAULT_SAVE_LEVEL 3 // 3 = WARNING + +#define ADVANCEDLOGGER_TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S" +#define ADVANCEDLOGGER_FORMAT "[%s] [%lu ms] [%s] [Core %d] [%s] %s" // [TIME] [MICROS us] [LOG_LEVEL] [Core CORE] [FUNCTION] MESSAGE + +#define ADVANCEDLOGGER_LOG_PATH "/AdvancedLogger/log.txt" +#define ADVANCEDLOGGER_CONFIG_PATH "/AdvancedLogger/config.json" + +#include + +#include +#include + +class AdvancedLogger +{ +public: + AdvancedLogger(); + + void begin(); + + void log(const char *message, const char *function, int logLevel); + void logOnly(const char *message, const char *function, int logLevel); + void setPrintLevel(int printLevel); + void setSaveLevel(int saveLevel); + + String getPrintLevel(); + String getSaveLevel(); + + void setDefaultLogLevels(); + bool setLogLevelsFromSpiffs(); + + void clearLog(); + +private: + int _printLevel; + int _saveLevel; + + void _save(const char *messageFormatted); + void _saveLogLevelsToSpiffs(); + + String _logLevelToString(int logLevel); + int _saturateLogLevel(int logLevel); + + String _getTimestamp(); +}; + +#endif \ No newline at end of file