Diagnostics

Picard has optional Cargo features for observing a running system. They are off by default and add no code when disabled. The runtime console provides authenticated WebSocket inspection and management; tracing and stats are described below.

[dependencies]
picard = { version = "0.4", features = ["tracing", "stats"] }

Tracing

The tracing feature emits tracing spans and events. Install any subscriber, for example tracing-subscriber, to collect them:

#![allow(unused)]
fn main() {
tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).init();
let sys = ActorSystem::new().unwrap();
}

Events have separate targets so subscriber filters can silence frequent lifecycle and message diagnostics independently of supervision events. For example, with an EnvFilter-enabled subscriber, RUST_LOG=picard::actor::lifecycle=warn,picard::messaging=warn,picard::actor::supervision=info keeps supervision warnings while reducing routine event volume.

LevelTargetKindName / messageFields
INFOpicard::systemeventactor system started, actor system shutting downsystem, id
DEBUGpicard::actor::lifecycleeventactor createdactor, actor_type
DEBUGpicard::actor::lifecycleeventactor terminatedactor
INFOpicard::actor::lifecycleeventactor restartedactor
WARNpicard::actor::lifecycleeventactor failed to restartactor
ERRORpicard::actor::supervisioneventactor panicked; reporting failure to parentactor
WARNpicard::actor::supervisioneventsupervising failed childsupervisor, actor, strategy
DEBUGpicard::messagingeventmessage could not be delivered...actor, msg_type (typed sends)
TRACEpicard::messagingspanrecv, entered while an actor handles a messageactor, msg_type

Events raised inside an actor's recv (including your own tracing calls) are nested in its recv span. The recv span is at TRACE level, so filter it out in production if its per-message cost matters.

The tracing feature is independent of Picard's slog-based logger; both can be used at the same time.

Runtime statistics

The stats feature keeps cumulative counters and exposes them through ActorSystem::stats():

#![allow(unused)]
fn main() {
let stats = sys.stats();
println!(
    "live actors: {}, messages sent: {}, failed: {}",
    stats.live_actors(),
    stats.messages_sent,
    stats.messages_failed,
);
}

RuntimeStats contains:

FieldCounts
actors_createdActors whose instance was produced and kernel started
actors_terminatedActors that were stopped and removed
actors_restartedRestarts after a failure (not counted as creations)
messages_sentMessages enqueued in a user mailbox
messages_failedMessages (user or system) that could not be enqueued, e.g. the recipient had stopped
system_messages_sentLifecycle commands, events and failure notifications enqueued

live_actors() returns actors_created - actors_terminated.

Counts include Picard's own actors (root, guardians, system channels and the dead letter logger) and messages sent to them, such as channel publishes. Each field is read separately, so a snapshot of a busy system is not atomic across fields. Counters are relaxed atomics on separate cache lines; each send costs one extra atomic increment.