Aria 2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
Diagnostics & Debugging

Aria's diagnostics system lets you observe the inner workings of the reactive graph, async commands, bindings, validators, and list mutations — all through a single, zero-overhead trace protocol.

Include: #include "aria/diagnostics.hpp", #include "aria/reactive/inspector.hpp"


Zero-Overhead Contract

When no trace sink is installed, the publish path costs one atomic load + one branch. No strings are built, no allocations happen. This means you can ship production builds with trace instrumentation compiled in and pay nothing until you attach a debugger.


Trace Categories

Every trace event carries a TraceCategory:

Category Enum Value Covers
Reactive 0 Graph flush, push-color, pull-evaluate
Async 1 AsyncCommand / AsyncResource lifecycle
Binding 2 BindingEngine VM↔View dispatch
Command 3 Synchronous Command execution
Validation 4 Validator / FormValidator rule runs
List 5 ObservableList / derived list mutations

Installing a Global Trace Sink

std::cout << "[" << aria::to_string(ev.category) << "] "
<< ev.debug_name << "\n";
});
void install_trace_sink(TraceSink sink)
Install (or replace) the global sink.
Definition diagnostics.hpp:271
std::string_view to_string(TraceCategory c) noexcept
Definition diagnostics.hpp:75
One trace event.
Definition diagnostics.hpp:200
TraceCategory category
Coarse routing label.
Definition diagnostics.hpp:202

The sink is thread-safe — install, replace, or clear from any thread at any time.

Clear the Sink

// Publish path returns to zero-overhead
void clear_trace_sink() noexcept
Tear down the global sink.
Definition diagnostics.hpp:279

TraceEvent Structure

struct TraceEvent {
TraceCategory category;
std::string debug_name; // node name (if set)
std::variant<
trace::Reactive,
trace::Async,
trace::Binding,
trace::Command,
trace::Validation,
trace::List
> payload;
// + timestamp, thread_id, etc.
};

Each payload variant carries category-specific data:

  • trace::Reactive — flush phase, node state transitions
  • trace::Async — command execution start/end/error
  • trace::Binding — bind/unbind events, dispatch direction
  • trace::Command — execute / rejected_can_execute / can_execute_changed
  • trace::Validation — rule_pass / rule_fail / begin_pending / end_pending
  • trace::List — insert/remove/reset/change events

GraphInspector

For deeper reactive graph introspection:

// Dump the graph as DOT (Graphviz)
std::string dot = inspector.to_dot();
// Dump as JSON
std::string json = inspector.to_json();
// Install a flush tracer for detailed step-by-step logging
std::cout << "Phase: " << ev.phase
<< " Node: " << ev.node_name
<< " Changed: " << ev.changed << "\n";
});
Lightweight diagnostics entry point.
Definition inspector.hpp:54
static void install_flush_tracer(FlushTracer tracer)
Install a tracer.
Definition inspector.hpp:201
static std::string to_dot(const std::vector< const Node * > &seeds, std::string_view graph_name="reactive")
Emit the reachable subgraph as a Graphviz DOT document.
Definition inspector.hpp:96
static std::string to_json(const std::vector< const Node * > &seeds)
Emit the reachable subgraph as a minimal JSON document.
Definition inspector.hpp:132
A single event emitted by the Graph during flush.
Definition inspector.hpp:171
bool changed
valid for Phase::Recomputed
Definition inspector.hpp:184
Phase phase
Definition inspector.hpp:181

DOT Output

Generate a visual representation of the reactive DAG:

std::ofstream out("graph.dot");
// Render: dot -Tpng graph.dot -o graph.png

Nodes are labeled with their debug_name (if set via set_debug_name()). Edges show dependency direction.


ScopedTraceSink

Install a trace sink for a limited scope, restoring the previous sink on exit:

void test_reactive_flow() {
std::vector<std::string> log;
aria::ScopedTraceSink scoped([&](const aria::TraceEvent& ev) {
log.push_back(ev.debug_name);
});
// ... exercise reactive code ...
// Sink automatically restored (or cleared) when scoped exits
REQUIRE(log.size() > 0);
}
Installs a sink for the lifetime of the scope, restoring whatever was previously installed (possibly ...
Definition diagnostics.hpp:348

Debug Names

Set human-readable names on reactive nodes for easier debugging:

count.set_debug_name("counter");
aria::Computed<int> doubled{[&] { return count.get() * 2; }};
doubled.set_debug_name("counter×2");
Definition computed.hpp:86
void set_debug_name(std::string name)
Definition node.hpp:158
Definition property.hpp:103
T get() const
Auto-tracked read.
Definition property.hpp:138

These names appear in DOT graphs, JSON dumps, and trace events.


Filtering by Category

// Only process validation events
});
@ Validation
Validator / FormValidator rule runs.
Definition diagnostics.hpp:71

Quick Reference

Function / Type Purpose
install_trace_sink(fn) Set global trace consumer
clear_trace_sink() Remove consumer, restore zero-overhead
has_trace_sink() Check if a consumer is active
ScopedTraceSink RAII: install + auto-restore
GraphInspector::to_dot() Export DAG as DOT
GraphInspector::to_json() Export DAG as JSON
GraphInspector::install_flush_tracer(fn) Per-flush step logging
Node::set_debug_name(str) Human-readable label

See Also