diff --git a/README.md b/README.md index 69b7fff..0505917 100644 --- a/README.md +++ b/README.md @@ -60,16 +60,15 @@ Saving should be quite straightforward: just copy any message that is sent to th 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. +This project has no external dependencies, and uses only the standard libraries. ## What's next? -- [ ] **Customizable paths**: allow to set a custom path when creating the AdvancedLogger object. -- [ ] **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. +- [x] **Customizable paths**: allow to set a custom path when creating the AdvancedLogger object. +- [x] **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. - [x] **Dump to serial**: implement a function 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. +- [x] **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. +- [ ] **Test other microcontrollers**: the library is currently tested only on the ESP32, but it should be tested on other microcontrollers to ensure compatibility. - [ ] **MQTT integration**: the ability to send logs to an MQTT server would be a great addition, as it would allow to monitor the device remotely. -- [ ] ~~**Consistent spacing**: the spacing between the different parts of the log should be consistent, to make it easier to read.~~ Not needed, as the format is already quite clear. \ No newline at end of file +- [x] ~~**Consistent spacing**: the spacing between the different parts of the log should be consistent, to make it easier to read.~~ Not needed, as the format is already quite clear. \ No newline at end of file diff --git a/examples/basicServer/basicServer.cpp b/examples/basicServer/basicServer.cpp index bd5fd95..e2cf028 100644 --- a/examples/basicServer/basicServer.cpp +++ b/examples/basicServer/basicServer.cpp @@ -4,7 +4,7 @@ * This file provides a simple example to show how to use the AdvancedLogger library. * * Author: Jibril Sharafi, @jibrilsharafi - * Date: 11/05/2024 + * Date: 07/04/2024 * GitHub repository: https://github.com/jibrilsharafi/AdvancedLogger * * This library is licensed under the MIT License. See the LICENSE file for more information. @@ -34,7 +34,10 @@ #include "advancedLogger.h" -AdvancedLogger logger; +String customLogPath = "/customPath/log.txt"; +String customConfigPath = "/customPath/config.txt"; + +AdvancedLogger logger(customLogPath.c_str(), customConfigPath.c_str()); // Leave empty for default paths AsyncWebServer server(80); @@ -47,6 +50,8 @@ const long intervalLogDump = 10000; long lastMillisLogClear = 0; const long intervalLogClear = 30000; +int maxLogLines = 10; // Low value for testing purposes + // **** CHANGE THESE TO YOUR SSID AND PASSWORD **** const char *ssid = "YOUR_SSID"; const char *password = "YOUR_PASSWORD"; @@ -70,6 +75,9 @@ void setup() // levels are used (DEBUG for print and INFO for save). logger.setPrintLevel(ADVANCEDLOGGER_DEBUG); logger.setSaveLevel(ADVANCEDLOGGER_INFO); + // Set the maximum number of log lines before the log is cleared + // If you don't set this, the default is used + logger.setMaxLogLines(maxLogLines); logger.log("AdvancedLogger setup done!", "basicServer::setup", ADVANCEDLOGGER_INFO); // Connect to WiFi @@ -86,8 +94,8 @@ void setup() // -------------------- server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(200, "text/html", "

"); }); - server.serveStatic("/log", SPIFFS, "/AdvancedLogger/log.txt"); - server.serveStatic("/config", SPIFFS, "/AdvancedLogger/config.json"); + server.serveStatic("/log", SPIFFS, customLogPath.c_str()); + server.serveStatic("/config", SPIFFS, customConfigPath.c_str()); server.onNotFound([](AsyncWebServerRequest *request) { request->send(404, "text/plain", "Not found"); }); server.begin(); @@ -101,12 +109,17 @@ void setup() void loop() { logger.log("This is an debug message!", "basicServer::loop", ADVANCEDLOGGER_DEBUG); + delay(500); logger.log("This is an info message!!", "basicServer::loop", ADVANCEDLOGGER_INFO); + delay(500); logger.log("This is an warning message!!!", "basicServer::loop", ADVANCEDLOGGER_WARNING); + delay(500); logger.log("This is an error message!!!!", "basicServer::loop", ADVANCEDLOGGER_ERROR); + delay(500); logger.log("This is an fatal message!!!!!", "basicServer::loop", ADVANCEDLOGGER_FATAL); - delay(1000); + delay(500); logger.logOnly("This is an info message (logOnly)!!", "basicServer::loop", ADVANCEDLOGGER_INFO); + delay(1000); printLevel = logger.getPrintLevel(); saveLevel = logger.getSaveLevel(); @@ -120,7 +133,11 @@ void loop() if (millis() - lastMillisLogClear > intervalLogClear) { - logger.dumpToSerial(); + logger.log( + ("Current number of log lines: " + String(logger.getLogLines())).c_str(), + "basicServer::loop", + ADVANCEDLOGGER_INFO + ); logger.clearLog(); logger.setDefaultLogLevels(); logger.log("Log cleared!", "basicServer::loop", ADVANCEDLOGGER_WARNING); diff --git a/examples/basicUsage/basicUsage.cpp b/examples/basicUsage/basicUsage.cpp index b8e9ce2..7918b8c 100644 --- a/examples/basicUsage/basicUsage.cpp +++ b/examples/basicUsage/basicUsage.cpp @@ -27,7 +27,10 @@ #include "advancedLogger.h" -AdvancedLogger logger; +String customLogPath = "/customPath/log.txt"; +String customConfigPath = "/customPath/config.txt"; + +AdvancedLogger logger(customLogPath.c_str(), customConfigPath.c_str()); // Leave empty for default paths String printLevel; String saveLevel; @@ -38,6 +41,8 @@ const long intervalLogDump = 10000; long lastMillisLogClear = 0; const long intervalLogClear = 30000; +int maxLogLines = 10; // Low value for testing purposes + void setup() { // Initialize Serial and SPIFFS (mandatory for the AdvancedLogger library) @@ -57,6 +62,10 @@ void setup() // levels are used (DEBUG for print and INFO for save). logger.setPrintLevel(ADVANCEDLOGGER_DEBUG); logger.setSaveLevel(ADVANCEDLOGGER_INFO); + // Set the maximum number of log lines before the log is cleared + // If you don't set this, the default is used + logger.setMaxLogLines(maxLogLines); + logger.log("AdvancedLogger setup done!", "basicUsage::setup", ADVANCEDLOGGER_INFO); lastMillisLogDump = millis(); lastMillisLogClear = millis(); @@ -65,13 +74,18 @@ void setup() void loop() { - logger.log("This is an debug message!", "basicUsage::loop", ADVANCEDLOGGER_DEBUG); - logger.log("This is an info message!!", "basicUsage::loop", ADVANCEDLOGGER_INFO); - logger.log("This is an warning message!!!", "basicUsage::loop", ADVANCEDLOGGER_WARNING); - logger.log("This is an error message!!!!", "basicUsage::loop", ADVANCEDLOGGER_ERROR); - logger.log("This is an fatal message!!!!!", "basicUsage::loop", ADVANCEDLOGGER_FATAL); + logger.log("This is an debug message!", "basicServer::loop", ADVANCEDLOGGER_DEBUG); + delay(500); + logger.log("This is an info message!!", "basicServer::loop", ADVANCEDLOGGER_INFO); + delay(500); + logger.log("This is an warning message!!!", "basicServer::loop", ADVANCEDLOGGER_WARNING); + delay(500); + logger.log("This is an error message!!!!", "basicServer::loop", ADVANCEDLOGGER_ERROR); + delay(500); + logger.log("This is an fatal message!!!!!", "basicServer::loop", ADVANCEDLOGGER_FATAL); + delay(500); + logger.logOnly("This is an info message (logOnly)!!", "basicServer::loop", ADVANCEDLOGGER_INFO); delay(1000); - logger.logOnly("This is an info message (logOnly)!!", "basicUsage::loop", ADVANCEDLOGGER_INFO); printLevel = logger.getPrintLevel(); saveLevel = logger.getSaveLevel(); @@ -85,7 +99,11 @@ void loop() if (millis() - lastMillisLogClear > intervalLogClear) { - logger.dumpToSerial(); + logger.log( + ("Current number of log lines: " + String(logger.getLogLines())).c_str(), + "basicServer::loop", + ADVANCEDLOGGER_INFO + ); logger.clearLog(); logger.setDefaultLogLevels(); logger.log("Log cleared!", "basicServer::loop", ADVANCEDLOGGER_WARNING); diff --git a/library.json b/library.json index 5a7e5c5..0e9c5e5 100644 --- a/library.json +++ b/library.json @@ -1,29 +1,21 @@ { - "name": "AdvancedLogger", - "keywords": "logger, log, logging, memory, format", - "description": "Library for simple logging to memory with comprehensive format.", - "repository": + "name": "AdvancedLogger", + "keywords": "logger, log, logging, memory, format", + "description": "Library for simple logging to memory with comprehensive format.", + "repository": { + "type": "git", + "url": "https://github.com/jibrilsharafi/AdvancedLogger.git" + }, + "authors": [ { - "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.1.0", - "frameworks": "arduino", - "platforms": "*" - } - \ No newline at end of file + "name": "Jibril Sharafi", + "email": "jibril.sharafi@gmail.com", + "url": "https://github.com/jibrilsharafi", + "maintainer": true + } + ], + "dependencies": {}, + "version": "1.1.0", + "frameworks": "arduino", + "platforms": "*" +} \ No newline at end of file diff --git a/library.properties b/library.properties index a7f5f72..a5fb2cc 100644 --- a/library.properties +++ b/library.properties @@ -7,4 +7,4 @@ paragraph=Easy to use, logs to memory using SPIFFS and the format contains all t category=Communication url=https://github.com/jibrilsharafi/AdvancedLogger architectures=* -depends=ArduinoJson \ No newline at end of file +depends= \ No newline at end of file diff --git a/src/advancedLogger.cpp b/src/advancedLogger.cpp index b343453..5138774 100644 --- a/src/advancedLogger.cpp +++ b/src/advancedLogger.cpp @@ -1,17 +1,22 @@ #include "advancedLogger.h" -AdvancedLogger::AdvancedLogger() +AdvancedLogger::AdvancedLogger(const char *logFilePath, const char *configFilePath) + : _logFilePath(logFilePath), _configFilePath(configFilePath) { _printLevel = ADVANCEDLOGGER_DEFAULT_PRINT_LEVEL; _saveLevel = ADVANCEDLOGGER_DEFAULT_SAVE_LEVEL; + _maxLogLines = ADVANCEDLOGGER_DEFAULT_MAX_LOG_LINES; } void AdvancedLogger::begin() { - if (!setLogLevelsFromSpiffs()) + log("Initializing AdvancedLogger...", "advancedLogger::begin", ADVANCEDLOGGER_DEBUG); + + if (!_setConfigFromSpiffs()) { setDefaultLogLevels(); } + log("AdvancedLogger initialized", "advancedLogger::begin", ADVANCEDLOGGER_DEBUG); } @@ -44,6 +49,14 @@ void AdvancedLogger::log(const char *message, const char *function, int logLevel if (logLevel >= _saveLevel) { _save(_message_formatted); + if (getLogLines() > _maxLogLines) + { + clearLog(); + log( + ("Log cleared due to max log lines (" + String(_maxLogLines) + ") reached").c_str(), + "advancedLogger::log", + ADVANCEDLOGGER_WARNING); + } } } @@ -81,7 +94,7 @@ void AdvancedLogger::setPrintLevel(int level) "advancedLogger::setPrintLevel", ADVANCEDLOGGER_INFO); _printLevel = _saturateLogLevel(level); - _saveLogLevelsToSpiffs(); + _saveConfigToSpiffs(); } void AdvancedLogger::setSaveLevel(int level) @@ -91,7 +104,7 @@ void AdvancedLogger::setSaveLevel(int level) "advancedLogger::setSaveLevel", ADVANCEDLOGGER_INFO); _saveLevel = _saturateLogLevel(level); - _saveLogLevelsToSpiffs(); + _saveConfigToSpiffs(); } String AdvancedLogger::getPrintLevel() @@ -108,69 +121,98 @@ void AdvancedLogger::setDefaultLogLevels() { setPrintLevel(ADVANCEDLOGGER_DEFAULT_PRINT_LEVEL); setSaveLevel(ADVANCEDLOGGER_DEFAULT_SAVE_LEVEL); - log("Log levels set to default", "advancedLogger::setDefaultLogLevels", ADVANCEDLOGGER_DEBUG); + setMaxLogLines(ADVANCEDLOGGER_DEFAULT_MAX_LOG_LINES); + + log("Log levels set to default", "advancedLogger::setDefaultLogLevels", ADVANCEDLOGGER_INFO); } -bool AdvancedLogger::setLogLevelsFromSpiffs() +bool AdvancedLogger::_setConfigFromSpiffs() { - log("Deserializing JSON from SPIFFS", "utils::deserializeJsonFromSpiffs", ADVANCEDLOGGER_DEBUG); - - File _file = SPIFFS.open(ADVANCEDLOGGER_CONFIG_PATH, "r"); - if (!_file) + File file = SPIFFS.open(_configFilePath, "r"); + if (!file) { - log( - ("Failed to open file " + String(ADVANCEDLOGGER_CONFIG_PATH)).c_str(), - "utils::deserializeJsonFromSpiffs", - ADVANCEDLOGGER_ERROR); + log("Failed to open config file for reading", "advancedLogger::_setConfigFromSpiffs", ADVANCEDLOGGER_ERROR); return false; } - JsonDocument _jsonDocument; - DeserializationError _error = deserializeJson(_jsonDocument, _file); - _file.close(); - if (_error) + while (file.available()) { - log( - ("Failed to deserialize file " + String(ADVANCEDLOGGER_CONFIG_PATH) + ". Error: " + String(_error.c_str())).c_str(), - "utils::deserializeJsonFromSpiffs", - ADVANCEDLOGGER_ERROR); - return false; + String line = file.readStringUntil('\n'); + int separatorPosition = line.indexOf('='); + String key = line.substring(0, separatorPosition); + String value = line.substring(separatorPosition + 1); + + if (key == "printLevel") + { + setPrintLevel(value.toInt()); + } + else if (key == "saveLevel") + { + setSaveLevel(value.toInt()); + } + else if (key == "maxLogLines") + { + setMaxLogLines(value.toInt()); + } } - log("JSON deserialized from SPIFFS correctly", "utils::deserializeJsonFromSpiffs", ADVANCEDLOGGER_DEBUG); + file.close(); + log("Log levels set from SPIFFS", "advancedLogger::_setConfigFromSpiffs", ADVANCEDLOGGER_DEBUG); + return true; +} - if (_jsonDocument.isNull()) +void AdvancedLogger::_saveConfigToSpiffs() +{ + File file = SPIFFS.open(_configFilePath, "w"); + if (!file) { - return false; + log("Failed to open config file for writing", "advancedLogger::_saveConfigToSpiffs", ADVANCEDLOGGER_ERROR); + return; } - setPrintLevel(_jsonDocument["level"]["print"].as()); - setSaveLevel(_jsonDocument["level"]["save"].as()); - log("Log levels set from SPIFFS", "advancedLogger::setLogLevelsFromSpiffs", ADVANCEDLOGGER_DEBUG); - return true; + file.println(String("printLevel=") + String(_printLevel)); + file.println(String("saveLevel=") + String(_saveLevel)); + file.println(String("maxLogLines=") + String(_maxLogLines)); + file.close(); + log("Log levels saved to SPIFFS", "advancedLogger::_saveConfigToSpiffs", ADVANCEDLOGGER_DEBUG); } -void AdvancedLogger::_saveLogLevelsToSpiffs() +void AdvancedLogger::setMaxLogLines(int maxLines) { - JsonDocument _jsonDocument; - _jsonDocument["level"]["print"] = _printLevel; - _jsonDocument["level"]["save"] = _saveLevel; - File _file = SPIFFS.open(ADVANCEDLOGGER_CONFIG_PATH, "w"); - if (!_file) + log( + ("Setting max log lines to " + String(maxLines)).c_str(), + "advancedLogger::setMaxLogLines", + ADVANCEDLOGGER_INFO); + _maxLogLines = maxLines; + _saveConfigToSpiffs(); +} + +int AdvancedLogger::getLogLines() +{ + File file = SPIFFS.open(_logFilePath, "r"); + if (!file) { - log("Failed to open logger.json", "advancedLogger::_saveLogLevelsToSpiffs", ADVANCEDLOGGER_ERROR); - return; + logOnly("Failed to open log file", "advancedLogger::getLogLines", ADVANCEDLOGGER_ERROR); + return -1; } - serializeJson(_jsonDocument, _file); - _file.close(); - log("Log levels saved to SPIFFS", "advancedLogger::_saveLogLevelsToSpiffs", ADVANCEDLOGGER_DEBUG); + + int lines = 0; + while (file.available()) + { + if (file.read() == '\n') + { + lines++; + } + } + file.close(); + return lines; } void AdvancedLogger::clearLog() { logOnly("Clearing log", "advancedLogger::clearLog", ADVANCEDLOGGER_WARNING); - SPIFFS.remove(ADVANCEDLOGGER_LOG_PATH); - File _file = SPIFFS.open(ADVANCEDLOGGER_LOG_PATH, "w"); + SPIFFS.remove(_logFilePath); + File _file = SPIFFS.open(_logFilePath, "w"); if (!_file) { logOnly("Failed to open log file", "advancedLogger::clearLog", ADVANCEDLOGGER_ERROR); @@ -182,7 +224,7 @@ void AdvancedLogger::clearLog() void AdvancedLogger::_save(const char *messageFormatted) { - File file = SPIFFS.open(ADVANCEDLOGGER_LOG_PATH, "a"); + File file = SPIFFS.open(_logFilePath, "a"); if (file) { file.println(messageFormatted); @@ -197,15 +239,15 @@ void AdvancedLogger::_save(const char *messageFormatted) void AdvancedLogger::dumpToSerial() { logOnly( - "Dumping log to Serial", - "advancedLogger::dumpToSerial", - ADVANCEDLOGGER_INFO - ); + "Dumping log to Serial", + "advancedLogger::dumpToSerial", + ADVANCEDLOGGER_INFO); - for(int i = 0; i < 2*50; i++) Serial.print("_"); + for (int i = 0; i < 2 * 50; i++) + Serial.print("_"); Serial.println(); - File file = SPIFFS.open(ADVANCEDLOGGER_LOG_PATH, "r"); + File file = SPIFFS.open(_logFilePath, "r"); if (!file) { logOnly("Failed to open log file", "advancedLogger::dumpToSerial", ADVANCEDLOGGER_ERROR); @@ -218,14 +260,14 @@ void AdvancedLogger::dumpToSerial() } file.close(); - for(int i = 0; i < 2*50; i++) Serial.print("_"); + for (int i = 0; i < 2 * 50; i++) + Serial.print("_"); Serial.println(); logOnly( - "Log dumped to Serial", - "advancedLogger::dumpToSerial", - ADVANCEDLOGGER_INFO - ); + "Log dumped to Serial", + "advancedLogger::dumpToSerial", + ADVANCEDLOGGER_INFO); } String AdvancedLogger::_logLevelToString(int logLevel) diff --git a/src/advancedLogger.h b/src/advancedLogger.h index 3393110..2928864 100644 --- a/src/advancedLogger.h +++ b/src/advancedLogger.h @@ -28,22 +28,22 @@ #define ADVANCEDLOGGER_DEFAULT_PRINT_LEVEL 2 // 2 = INFO #define ADVANCEDLOGGER_DEFAULT_SAVE_LEVEL 3 // 3 = WARNING +#define ADVANCEDLOGGER_DEFAULT_MAX_LOG_LINES 1000 // 1000 lines before the log is cleared #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" +#define ADVANCEDLOGGER_CONFIG_PATH "/AdvancedLogger/config.txt" #include #include -#include class AdvancedLogger { public: - AdvancedLogger(); + AdvancedLogger(const char *logFilePath = ADVANCEDLOGGER_LOG_PATH, const char *configFilePath = ADVANCEDLOGGER_CONFIG_PATH); void begin(); @@ -56,18 +56,25 @@ class AdvancedLogger String getSaveLevel(); void setDefaultLogLevels(); - bool setLogLevelsFromSpiffs(); + void setMaxLogLines(int maxLines); + int getLogLines(); void clearLog(); void dumpToSerial(); private: + String _logFilePath; + String _configFilePath; + int _printLevel; int _saveLevel; + + int _maxLogLines; void _save(const char *messageFormatted); - void _saveLogLevelsToSpiffs(); + bool _setConfigFromSpiffs(); + void _saveConfigToSpiffs(); String _logLevelToString(int logLevel); int _saturateLogLevel(int logLevel);