Aria 2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
diagnostics.hpp
Go to the documentation of this file.
1#pragma once
2
3// ============================================================================
4// aria/diagnostics.hpp
5// ----------------------------------------------------------------------------
6// Unified diagnostic protocol for the Aria framework. Per
7// docs/diagnostics.md, every observable subsystem publishes
8// `TraceEvent`s through a single, optional, thread-safe sink:
9//
10// - reactive graph flush -> Category::Reactive
11// - async command -> Category::Async
12// - async resource -> Category::Async
13// - binding engine -> Category::Binding
14// - command (sync) -> Category::Command
15// - validator -> Category::Validation
16// - observable list -> Category::List
17//
18// Design pillars
19// --------------
20// 1. A cheap disabled path: the has_trace_sink gate costs
21// one atomic load + one branch. No string is built, no allocation
22// is done.
23//
24// 2. Single value type (`TraceEvent`) so tooling consumes ONE shape;
25// heterogeneous payloads are boxed into a small `std::variant`.
26//
27// 3. Thread-safe sink registration: a sink may be installed /
28// replaced / cleared from any thread at any time. Concurrent
29// publishers see a consistent snapshot via `std::shared_ptr`.
30//
31// 4. Aria's own subsystems are ALLOWED to keep their own focused
32// tracers (e.g. `GraphInspector::install_flush_tracer`); the
33// unified sink is an additional fanout, NOT a replacement.
34//
35// Per docs/api-style.md S-1 these names live in `aria::`.
36// ============================================================================
37
38#include "aria/abi/export.hpp"
39#include "aria/error.hpp"
41
42#include <atomic>
43#include <chrono>
44#include <concepts>
45#include <cstdint>
46#include <functional>
47#include <memory>
48#include <mutex>
49#include <optional>
50#include <ostream>
51#include <string>
52#include <string_view>
53#include <utility>
54#include <variant>
55#include <vector>
56
57namespace aria {
58
59// ---------------------------------------------------------------------------
60// TraceCategory
61// ---------------------------------------------------------------------------
62
66enum class TraceCategory : std::uint8_t {
68 Async = 1,
69 Binding = 2,
70 Command = 3,
72 List = 5,
73};
74
75[[nodiscard]] inline std::string_view to_string(TraceCategory c) noexcept {
76 switch (c) {
77 case TraceCategory::Reactive: return "Reactive";
78 case TraceCategory::Async: return "Async";
79 case TraceCategory::Binding: return "Binding";
80 case TraceCategory::Command: return "Command";
81 case TraceCategory::Validation: return "Validation";
82 case TraceCategory::List: return "List";
83 }
84 return "TraceCategory?";
85}
86
87// ---------------------------------------------------------------------------
88// Per-category event payloads
89// ---------------------------------------------------------------------------
90//
91// Each category carries a small, focused payload. Adding a new field
92// is a backwards-compatible change (consumers ignore unknown fields);
93// removing a field is breaking and must surface in CHANGELOG.
94
95namespace trace {
96
101enum class ReactivePhase : std::uint8_t {
104 Pull = 2,
109};
110
124
128struct Async {
130 std::string source;
134 std::string op;
137 std::uint64_t generation = 0;
138};
139
142struct Binding {
144 std::string platform;
146 std::string target;
148 std::string op;
149};
150
153struct Command {
154 std::string op;
155};
156
163 std::string op;
166 std::string message;
167};
168
173struct List {
174 std::string op;
175 std::size_t index = 0;
176 std::size_t from_index = 0;
177 std::size_t size_after = 0;
178};
179
180} // namespace trace
181
183using TracePayload = std::variant<
190
191// ---------------------------------------------------------------------------
192// TraceEvent
193// ---------------------------------------------------------------------------
194
208 std::chrono::steady_clock::time_point time =
209 std::chrono::steady_clock::now();
214 std::optional<::aria::Error> error;
215
216 [[nodiscard]] std::string_view category_name() const noexcept {
217 return to_string(category);
218 }
219};
220
221// ---------------------------------------------------------------------------
222// TraceSink
223// ---------------------------------------------------------------------------
224
230using TraceSink = std::function<void(const TraceEvent&)>;
231
232namespace detail {
233
234// Shared trace storage lives in aria_abi, which is linked by core-only
235// consumers and platform modules. See modules/abi/src/diagnostics.cpp.
236ARIA_ABI_API std::shared_ptr<TraceSink>& global_sink_storage_() noexcept;
237ARIA_ABI_API std::mutex& global_sink_mutex_() noexcept;
238ARIA_ABI_API std::atomic<bool>& trace_sink_present_() noexcept;
239ARIA_ABI_API void dispatch_trace_(const std::shared_ptr<TraceSink>& sink,
240 const TraceEvent& event) noexcept;
241
245inline std::shared_ptr<TraceSink>
246swap_global_sink_(std::shared_ptr<TraceSink> next) noexcept {
247 std::lock_guard lk(global_sink_mutex_());
248 auto& slot = global_sink_storage_();
249 auto prev = std::move(slot);
250 slot = std::move(next);
251 trace_sink_present_().store(static_cast<bool>(slot), std::memory_order_release);
252 return prev;
253}
254
258inline std::shared_ptr<TraceSink> snapshot_global_sink_() noexcept {
259 std::lock_guard lk(global_sink_mutex_());
260 return global_sink_storage_();
261}
262
263} // namespace detail
264
265// ---------------------------------------------------------------------------
266// Public API: install / clear / publish
267// ---------------------------------------------------------------------------
268
271inline void install_trace_sink(TraceSink sink) {
272 detail::swap_global_sink_(
273 sink ? std::make_shared<TraceSink>(std::move(sink))
274 : std::shared_ptr<TraceSink>{});
275}
276
279inline void clear_trace_sink() noexcept {
280 detail::swap_global_sink_({});
281}
282
286[[nodiscard]] inline bool has_trace_sink() noexcept {
287 return detail::trace_sink_present_().load(std::memory_order_acquire);
288}
289
293inline void publish_trace_unchecked(const TraceEvent& event) noexcept {
294 auto sink = detail::snapshot_global_sink_();
295 if (sink && *sink) detail::dispatch_trace_(sink, event);
296}
297
302inline void publish_trace(const TraceEvent& event) noexcept {
304}
305
306namespace detail {
307template<class Payload>
308concept TracePayloadType =
309 std::same_as<Payload, trace::Reactive> || std::same_as<Payload, trace::Async> ||
310 std::same_as<Payload, trace::Binding> || std::same_as<Payload, trace::Command> ||
311 std::same_as<Payload, trace::Validation> || std::same_as<Payload, trace::List>;
312} // namespace detail
313
316template<detail::TracePayloadType Payload>
317inline void publish_trace_unchecked(TraceCategory category, Payload payload,
318 std::optional<::aria::Error> error = std::nullopt) {
319 auto sink = detail::snapshot_global_sink_();
320 if (!sink || !*sink) return;
321 const TraceEvent event{category, TracePayload{std::move(payload)},
322 std::chrono::steady_clock::now(), std::move(error)};
323 detail::dispatch_trace_(sink, event);
324}
325
328template<detail::TracePayloadType Payload>
329inline void publish_trace(TraceCategory category, Payload payload,
330 std::optional<::aria::Error> error = std::nullopt) {
331 if (has_trace_sink())
332 publish_trace_unchecked(category, std::move(payload), std::move(error));
333}
334
335// ---------------------------------------------------------------------------
336// ScopedTraceSink -- RAII install/restore for tests
337// ---------------------------------------------------------------------------
338
349public:
351 : previous_(detail::swap_global_sink_(
352 sink ? std::make_shared<TraceSink>(std::move(sink))
353 : std::shared_ptr<TraceSink>{})) {}
354
355 ~ScopedTraceSink() noexcept {
356 detail::swap_global_sink_(std::move(previous_));
357 }
358
363
364private:
365 std::shared_ptr<TraceSink> previous_;
366};
367
368} // namespace aria
Encapsulated user action with an optional CanExecute predicate.
Definition command.hpp:58
ScopedTraceSink(TraceSink sink)
Definition diagnostics.hpp:350
ScopedTraceSink(ScopedTraceSink &&)=delete
ScopedTraceSink & operator=(const ScopedTraceSink &)=delete
~ScopedTraceSink() noexcept
Definition diagnostics.hpp:355
ScopedTraceSink & operator=(ScopedTraceSink &&)=delete
ScopedTraceSink(const ScopedTraceSink &)=delete
#define ARIA_ABI_API
Definition export.hpp:21
Definition diagnostics.hpp:95
ReactivePhase
Reactive flush phases.
Definition diagnostics.hpp:101
@ SkipClean
Definition diagnostics.hpp:105
@ RoundBegin
Definition diagnostics.hpp:103
@ FlushBegin
Definition diagnostics.hpp:102
@ Recomputed
Definition diagnostics.hpp:106
@ Pull
Definition diagnostics.hpp:104
@ RoundEnd
Definition diagnostics.hpp:107
@ FlushEnd
Definition diagnostics.hpp:108
Definition signal.hpp:12
void publish_trace_unchecked(const TraceEvent &event) noexcept
Publish an already-built event using one owning sink snapshot.
Definition diagnostics.hpp:293
void clear_trace_sink() noexcept
Tear down the global sink.
Definition diagnostics.hpp:279
void install_trace_sink(TraceSink sink)
Install (or replace) the global sink.
Definition diagnostics.hpp:271
void publish_trace(const TraceEvent &event) noexcept
Publish an already-built event.
Definition diagnostics.hpp:302
std::variant< trace::Reactive, trace::Async, trace::Binding, trace::Command, trace::Validation, trace::List > TracePayload
Heterogeneous payload. Order matches TraceCategory.
Definition diagnostics.hpp:183
std::function< void(const TraceEvent &)> TraceSink
User-facing sink shape: a callable that receives every published event.
Definition diagnostics.hpp:230
bool has_trace_sink() noexcept
True iff a sink is currently installed.
Definition diagnostics.hpp:286
TraceCategory
Coarse subsystem label.
Definition diagnostics.hpp:66
@ Validation
Validator / FormValidator rule runs.
Definition diagnostics.hpp:71
@ Async
AsyncCommand / AsyncResource lifecycle.
Definition diagnostics.hpp:68
@ List
ObservableList / FilteredList / SortedList / MappedList.
Definition diagnostics.hpp:72
@ Binding
BindingEngine VM<->View dispatch.
Definition diagnostics.hpp:69
@ Reactive
Graph flush, push-color, pull-evaluate.
Definition diagnostics.hpp:67
@ Command
Synchronous Command<Args...> execution.
Definition diagnostics.hpp:70
std::string_view to_string(TraceCategory c) noexcept
Definition diagnostics.hpp:75
Definition validation_key.hpp:110
One trace event.
Definition diagnostics.hpp:200
std::string_view category_name() const noexcept
Definition diagnostics.hpp:216
std::chrono::steady_clock::time_point time
Monotonic timestamp at publish time.
Definition diagnostics.hpp:208
TracePayload payload
Per-category payload.
Definition diagnostics.hpp:204
std::optional<::aria::Error > error
Optional Error snapshot (for *_fail / *_error events).
Definition diagnostics.hpp:214
TraceCategory category
Coarse routing label.
Definition diagnostics.hpp:202
(field_path, rule_id) locator for a validation message.
Definition validation_key.hpp:52
Async lifecycle.
Definition diagnostics.hpp:128
std::uint64_t generation
Optional invocation generation counter (AsyncResource gen, AsyncCommand inflight id).
Definition diagnostics.hpp:137
std::string source
Subsystem tag, e.g. "AsyncCommand" or "AsyncResource".
Definition diagnostics.hpp:130
std::string op
Operation, e.g.
Definition diagnostics.hpp:134
Binding events.
Definition diagnostics.hpp:142
std::string op
What happened.
Definition diagnostics.hpp:148
std::string platform
Adapter platform name ("qt6", "appkit", "uikit", "fake", ...).
Definition diagnostics.hpp:144
std::string target
Bind target description, free-form ("text", "bool", "int", ...).
Definition diagnostics.hpp:146
Synchronous Command<Args...> events.
Definition diagnostics.hpp:153
std::string op
Definition diagnostics.hpp:154
Observable-list mutation.
Definition diagnostics.hpp:173
std::size_t size_after
Definition diagnostics.hpp:177
std::string op
Definition diagnostics.hpp:174
std::size_t index
Definition diagnostics.hpp:175
std::size_t from_index
Move-only; 0 otherwise.
Definition diagnostics.hpp:176
Definition diagnostics.hpp:111
std::string node_name
Optional debug name of the node being processed (Pull / SkipClean / Recomputed).
Definition diagnostics.hpp:115
ReactivePhase phase
Definition diagnostics.hpp:112
int round
Round counter (1-based).
Definition diagnostics.hpp:118
bool changed
Only meaningful for Recomputed: did the cached value actually move (true) or did the upstream-version...
Definition diagnostics.hpp:122
Validation events.
Definition diagnostics.hpp:160
std::string op
Operation: "rule_pass" / "rule_fail" / "warning_pass" / "warning_fail" / "begin_pending" / "end_pendi...
Definition diagnostics.hpp:163
std::string message
Carried only for the *_fail variants; empty otherwise.
Definition diagnostics.hpp:166
ValidationKey key
Definition diagnostics.hpp:164