Trace is a logging and diagnostics library for ESP32.
Trace collects structured runtime logs with bounded recent history, bounded realtime delivery, bounded pending flush storage, task-side persistence callbacks, optional Tempo timestamps, and runtime diagnostics. Trace v0.3.0 uses Strata for all movable library-owned memory and FreeRTOS storage.
- Bounded storage - recent history, realtime delivery, pending flush logs, and payload lengths use fixed-capacity storage.
- PSRAM-first defaults - movable Trace-owned allocations and the Trace task stack prefer external memory by default and fall back to internal memory when needed.
- Shared ZekStack memory policy - allocation and task placement use
Strata::MemoryPolicy,Strata::Placement, andStrata::Region. - Structured output - log records keep level, tag, message, formatted text, sequence, and uptime.
- Task-side callbacks - realtime observation and persistence callbacks run from the internal Trace task.
- Production-minded - result-based errors, diagnostics, thread-safe internals, ArduinoJson support, and no exceptions in Trace production sources.
Trace v0.3.0 requires:
- ArduinoJson v7 or newer.
- Strata v0.1.2.
- C++20.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
https://github.com/ZekStack/trace.git#v0.3.0
https://github.com/ZekStack/strata.git#v0.1.2
bblanchon/ArduinoJson@>=7.0.0
build_flags =
-std=gnu++20
build_unflags =
-std=gnu++11Trace and Strata are not published to Arduino Library Manager yet. Install both repositories into your Arduino libraries folder and install ArduinoJson through Library Manager.
Arduino/libraries/Trace
Arduino/libraries/Strata#include <Arduino.h>
#include <Trace.h>
Trace trace;
void setup() {
Serial.begin(115200);
trace.setStream(&Serial);
TraceResult result = trace.init();
if (!result) {
Serial.println(result.message);
return;
}
trace.info("BOOT", "Trace is ready");
}
void loop() {
delay(1000);
}With the default configuration, all movable Trace-owned memory prefers PSRAM:
TraceConfig config;
// These are already the defaults.
config.memory.allocation = Strata::Placement::PreferExternal;
config.memory.taskStack = Strata::Placement::PreferExternal;
// nullopt means: inherit memory.allocation.
config.realtimeAllocation = std::nullopt;PreferExternal is deliberately different from RequireExternal: devices without usable PSRAM still work by falling back to internal memory.
If realtime conversion must stay internal while history, pending storage, flush batches, and the task stack prefer PSRAM:
TraceConfig config;
config.realtimeAllocation = Strata::Placement::Internal;
trace.init(config);TraceConfig::memory.allocation controls movable general Trace-owned allocations, including recent and pending ring storage, public query values, flush batches, formatted strings, and general callback ownership.
TraceConfig::memory.taskStack controls the Trace worker task stack.
TraceConfig::realtimeAllocation optionally overrides the general allocation placement for realtime queue storage, realtime callback ownership, and realtime TraceLog conversion. When it is std::nullopt, realtime memory inherits memory.allocation.
Strata keeps RTOS control structures internal where required by the platform. Trace does not override those safety constraints.
Diagnostics report both what was requested and where memory actually landed:
TraceDiag diag = trace.getDiagnostics();
Serial.printf("allocation requested: %s\n", Strata::toString(diag.requestedAllocationPlacement));
Serial.printf("recent region: %s\n", Strata::toString(diag.recentStorageRegion));
Serial.printf("task stack region: %s\n", Strata::toString(diag.taskStackRegion));The internal queue records remain fixed-size TraceRecord values. After Trace::init(), accepted direct C-string log calls do not allocate on the internal enqueue path while target queues have capacity and no output-boundary conversion is triggered.
Public/output-boundary values use Strata-backed storage:
TraceLogstrings areStrata::String.TraceLogListisStrata::Vector<TraceLog>.TraceLogBatch::logsis aTraceLogList.- query values and flush batches follow
memory.allocation. - realtime conversion follows
realtimeAllocationor the inherited general policy.
Caller-owned allocations, such as captures created before a callback is passed to Trace or caller-created std::string values, remain the caller's responsibility.
Important
info(), debug(), warn(), error(), and fatal() only enqueue logs. onLog() and onFlush() callbacks run later from the internal Trace task.
maxRecentLogscontrols queryable in-RAM history only.maxRealtimeLogscontrols realtime delivery used byonLog()and stream output.maxPendingLogscontrols unsaved logs waiting for flush.maxFlushBatchLogsbounds the number of publicTraceLogobjects converted per flush callback.0keeps flush batches uncapped.- Queue-count
0disables that queue and allocates no ring storage for it. setStream()writes formatted realtime logs to any ArduinoPrintimplementation.- Callbacks should avoid long blocking work and should not recursively call Trace logging methods.
- Trace does not own attached
PrintorTempoinstances. Keep them alive untilTrace::end()completes. - Stack sizes are FreeRTOS byte sizes on ESP32 and must be at least 1024 bytes.
Trace trace;
trace.init();
trace.setStream(&Serial);
trace.onLog([](const TraceLog &log) {});
trace.onFlush([](const TraceLogBatch &batch) {
return TraceFlushResult::Ok;
});
trace.info("WIFI", "connected");
trace.errorf("HTTP", "status=%d", 500);
TraceDiag diag = trace.getDiagnostics();
TraceLogList errors = trace.getLogs(TraceLevel::Error);
trace.flushAndWait(2000);Trace v0.3.0 intentionally adopts the shared ZekStack Strata API instead of retaining library-specific memory enums.
| v0.2.x | v0.3.0 |
|---|---|
TraceStackType::Auto |
Strata::Placement::PreferExternal |
TraceStackType::Internal |
Strata::Placement::Internal |
TraceStackType::Psram |
Strata::Placement::RequireExternal |
TraceStorageMemory::Internal |
Strata::Placement::Internal |
TraceStorageMemory::PreferPsram |
Strata::Placement::PreferExternal |
TraceStorageMemory::RequirePsram |
Strata::Placement::RequireExternal |
config.stackType |
config.memory.taskStack |
config.storageMemory |
config.memory.allocation |
config.realtimeStorageMemory |
config.realtimeAllocation |
std::vector<TraceLog> query results |
TraceLogList |
TraceResult::message as std::string |
const char * |
The default storage policy also changes: v0.3.0 prefers external memory for movable Trace-owned allocations and the task stack.
| Example | Description |
|---|---|
Basic |
Minimal init, realtime log printing, and manual flush. |
JsonPayloads |
Compact and pretty ArduinoJson payload logging. |
PrintfFormatting |
printf-style logging helpers. |
CallbacksAndFlush |
Realtime observation and persistence callback behavior. |
Diagnostics |
Runtime counters, requested placements, and observed regions. |
TempoTimestamps |
Tempo timestamp formatting. |
OverflowPolicies |
Pending queue limits and overflow policy configuration. |
| Item | Support |
|---|---|
| Framework | Arduino ESP32 |
| Platform | espressif32 |
| Language | C++20 |
| PSRAM | Optional; preferred by default for movable Trace-owned memory |
| Dependencies | ArduinoJson >= 7.0.0, Strata v0.1.2 |
| Exceptions in Trace production sources | Not used |
| Status | v0.3.0 |
MIT - see LICENSE.md.
Part of the ZekStack ESP32 library stack.