A lightweight, configurable logging utility for ESP32 projects. ESPLogger combines formatted log APIs, in-RAM batching, and optional background syncing so you can integrate storage or telemetry pipelines without blocking application code.
- Familiar
debug/info/warn/errorhelpers withprintfformatting semantics. - Optional ArduinoJson v7+ overloads for logging
JsonDocumentandJsonVariantpayloads directly. - Configurable behavior: batching thresholds, FreeRTOS core/stack/priority, and console log level.
- Optional background sync task (native FreeRTOS task) plus manual
sync()for deterministic flushes. - Optional PSRAM-backed internal buffers via
LoggerConfig::usePSRAMBufferswith automatic fallback to normal heap. - Live callback support via
attachso each emitted log entry can be streamed in real time. onSynccallback hands over a vector of structuredLogentries for custom persistence.- Helpers to fetch every buffered log or just the most recent entries whenever you need diagnostics.
- Filter helpers to count or retrieve buffered logs at a specific level without flushing them.
- Static helpers to count or filter the snapshot passed into
onSyncwithout relying on internal buffers. - Console output defaults to a minimal
printfbackend; opt into ESP-IDFESP_LOGxmacros with a single build flag when you want IDF-style logs.
Minimal setup:
#include <ESPLogger.h>
ESPLogger logger;
void setup() {
Serial.begin(115200);
LoggerConfig cfg;
cfg.syncIntervalMS = 5000;
cfg.maxLogInRam = 100;
cfg.consoleLogLevel = LogLevel::Info;
cfg.usePrettyJson = true;
cfg.usePSRAMBuffers = true; // Safe on non-PSRAM boards (falls back automatically)
logger.init(cfg);
logger.onSync([](const std::vector<Log>& logs) {
for (const auto& entry : logs) {
// Persist logs to flash, send over Wi-Fi, etc.
}
});
logger.attach([](const Log& entry) {
// Called immediately for every log entry.
// Forward to websockets, telemetry, etc.
});
logger.info("INIT", "ESPLogger initialised with %u entries", cfg.maxLogInRam);
}
void loop() {
logger.debug("LOOP", "Loop iteration at %lu ms", static_cast<unsigned long>(millis()));
delay(1000);
}Log ArduinoJson payloads directly when ArduinoJson v7+ is available at compile time. PlatformIO installs it automatically through library.json; for other build systems, install ArduinoJson v7+ yourself:
#include <ArduinoJson.h>
void logJsonPayload() {
LoggerConfig cfg;
cfg.enableSyncTask = false;
cfg.usePrettyJson = false; // Switch to compact output
logger.init(cfg);
JsonDocument doc;
doc["hello"] = "world";
logger.debug("JSON", doc);
logger.info("JSON", doc);
logger.warn("JSON", doc);
logger.error("JSON", doc);
}Teardown explicitly when your component shuts down or reconfigures:
void stopLogging() {
logger.deinit();
}Scope a logger inside your own class:
class SensorModule {
public:
explicit SensorModule(ESPLogger& logger) : _logger(logger) {}
void update() {
const float reading = analogRead(A0) / 1023.0f;
_logger.info("SENSOR", "Latest reading: %.2f", reading);
}
private:
ESPLogger& _logger;
};Example sketches:
examples/basic_usage– minimal configuration + periodic logging.examples/custom_sync– manual syncing with a custom persistence callback.examples/json_logging– log ArduinoJson v7JsonDocumentandJsonVariantConstpayloads with pretty and compact output.
ESPLogger prints with a minimal printf backend by default, producing lines like:
[I] [NETWORK] ~ Connected to Wi-Fi
Prefer the ESP-IDF logging macros? Define ESPLOGGER_USE_ESP_LOG=1 in your build flags to switch the console bridge:
- PlatformIO:
build_flags = -DESPLOGGER_USE_ESP_LOG=1 - Arduino CLI:
--build-property build.extra_flags=-DESPLOGGER_USE_ESP_LOG=1
- Keep
ESPLoggerinstances alive for as long as their sync worker may run; destroying the object stops the worker. - Call
logger.deinit()during shutdown/reconfiguration so pending buffered logs are flushed and callbacks are detached deterministically. - When
enableSyncTaskisfalse, remember to calllogger.sync()yourself or logs will stay buffered forever. setLogLevelonly affects console output; all logs remain available inside the RAM buffer until purged.- ArduinoJson overloads are enabled only when
ArduinoJson.his visible at compile time andARDUINOJSON_VERSION_MAJOR >= 7. - Inside
onSync, the internal buffer has already been cleared—use the static helper overloads that take thelogssnapshot to count or filter entries. attachcallbacks run in the caller context ofdebug/info/warn/error; keep handlers fast and non-blocking.- The
onSynccallback runs inside the sync task context—avoid blocking operations.
bool init(const LoggerConfig& cfg = {})– configure sync cadence, stack size, priorities, and thresholds.void deinit()/bool isInitialized() const– tear down runtime resources and inspect lifecycle state.void debug/info/warn/error(const char* tag, const char* fmt, ...)– emit formatted logs.void debug/info/warn/error(const char* tag, const JsonDocument& json)andvoid debug/info/warn/error(const char* tag, JsonVariantConst json)– available when ArduinoJson v7+ is installed and included by the build.void attach(LiveCallback cb)/void detach()– register or remove a per-entry live callback invoked on every emitted log entry.void setLogLevel(LogLevel level)/LogLevel logLevel() const– adjust console verbosity at runtime.void onSync(ESPLogger::SyncCallback cb)– receive batches ofLogentries whenever the buffer flushes.void sync()– force a flush (useful when the background task is disabled).std::vector<Log> getAllLogs()/std::vector<Log> getLastLogs(size_t count)– snapshot buffered entries.int getLogCount(LogLevel level)/std::vector<Log> getLogs(LogLevel level)– inspect buffered logs at a particular level.static int getLogCount(const std::vector<Log>& logs, LogLevel level)/static std::vector<Log> getLogs(const std::vector<Log>& logs, LogLevel level)– filter the snapshot passed toonSync.LoggerConfig currentConfig() const– inspect the live settings.
LoggerConfig knobs:
| Field | Default | Description |
|---|---|---|
syncIntervalMS |
5000 |
Period between automatic flushes (ignored when enableSyncTask is false). |
stackSize |
16384 |
Stack size for the sync task. |
coreId |
LoggerConfig::any |
CPU core affinity for the sync task. |
maxLogInRam |
100 |
Maximum entries retained in RAM; oldest entries are discarded when the buffer is full. |
priority |
1 |
FreeRTOS priority for the sync task. |
consoleLogLevel |
LogLevel::Debug |
Minimum level printed to the console. |
enableSyncTask |
true |
Disable to opt out of the background task and call logger.sync() manually. |
usePrettyJson |
true |
Use serializeJsonPretty() for ArduinoJson payloads; set to false to use compact serializeJson(). |
usePSRAMBuffers |
false |
Prefer PSRAM for logger-owned buffers (_logs, format temp buffers, sync staging) when available; falls back to normal heap if not. |
Stack sizes are expressed in bytes.
- Built for ESP32 + FreeRTOS (Arduino or ESP-IDF) with C++17 enabled.
- Uses dynamic allocation for the RAM buffer; size
maxLogInRamaccording to your heap budget. - ArduinoJson logging is optional; older ArduinoJson versions are ignored and the JSON overloads are not exposed.
- Console output uses
printfby default; defineESPLOGGER_USE_ESP_LOG=1if you want ESP-IDF log colors/levels managed via menuconfig.
A host-driven test suite lives under test/ and is wired into CTest. Run it with:
cmake -S . -B build
cmake --build build
ctest --test-dir buildThe suite exercises buffering, log level filtering, and sync behavior. Hardware smoke tests reside in examples/.
This repository follows the firmware formatting baseline from esptoolkit-template:
.clang-formatis the source of truth for C/C++/INO layout..editorconfigenforces tabs (tab_width = 4), LF endings, and final newline.- Format all tracked firmware sources with
bash scripts/format_cpp.sh.
MIT — see LICENSE.md.
- Check out other libraries: https://github.com/orgs/ESPToolKit/repositories
- Hang out on Discord: https://discord.gg/WG8sSqAy
- Support the project: https://ko-fi.com/esptoolkit
- Visit the website: https://www.esptoolkit.hu/