///|
/// Errors raised while recording local diagnostics.
pub(all) suberror DiagnosticsError {
InvalidLogLimit
EmptyComponent
EmptyMessage
InvalidTimestamp
} derive(Debug, Eq)
///|
/// Severity for one structured local diagnostic event.
pub(all) enum LogLevel {
Debug
Info
Warn
Error
} derive(Debug, Eq)
///|
/// One structured event retained by the local diagnostics collector.
pub(all) struct DiagnosticEvent {
at : Timestamp
level : LogLevel
component : String
message : String
} derive(Debug, Eq)
///|
/// A bounded collector for local events and sync/policy status.
pub(all) struct DiagnosticsCollector {
max_logs : Int32
mut logs : Array[DiagnosticEvent]
mut sync_failures : Int32
mut last_sync_error : String?
mut last_policy_decision : PolicyDecision
} derive(Debug, Eq)
///|
/// Creates an empty diagnostics collector with a bounded log buffer.
pub fn DiagnosticsCollector::new(
max_logs~ : Int32,
) -> DiagnosticsCollector raise DiagnosticsError {
if max_logs <= 0 {
raise DiagnosticsError::InvalidLogLimit
}
{
max_logs,
logs: [],
sync_failures: 0,
last_sync_error: None,
last_policy_decision: PolicyDecision::SafeDefault,
}
}
///|
/// Records one structured event, dropping the oldest event at capacity.
pub fn DiagnosticsCollector::record_event(
self : DiagnosticsCollector,
at~ : Timestamp,
level~ : LogLevel,
component~ : String,
message~ : String,
) -> Unit raise DiagnosticsError {
ensure_diagnostic_timestamp(at)
if component == "" {
raise DiagnosticsError::EmptyComponent
}
if message == "" {
raise DiagnosticsError::EmptyMessage
}
if self.logs.length() >= self.max_logs {
let retained : Array[DiagnosticEvent] = []
for index in 1.. Unit raise DiagnosticsError {
ensure_diagnostic_timestamp(at)
if message == "" {
raise DiagnosticsError::EmptyMessage
}
self.sync_failures = self.sync_failures + 1
self.last_sync_error = Some(message)
self.record_event(at~, level=LogLevel::Error, component="sync", message~)
}
///|
/// Records the latest policy decision for diagnostics.
pub fn DiagnosticsCollector::record_policy_decision(
self : DiagnosticsCollector,
at~ : Timestamp,
decision~ : PolicyDecision,
) -> Unit raise DiagnosticsError {
ensure_diagnostic_timestamp(at)
self.last_policy_decision = decision
self.record_event(
at~,
level=LogLevel::Info,
component="policy",
message="policy decision recorded",
)
}
///|
/// Returns a copy of the retained structured events.
pub fn DiagnosticsCollector::events(
self : DiagnosticsCollector,
) -> Array[DiagnosticEvent] {
copy_diagnostic_events(self.logs)
}
///|
/// Captures a cross-component local health snapshot.
pub fn DiagnosticsCollector::snapshot(
self : DiagnosticsCollector,
node~ : Node,
store~ : LocalStore,
transport~ : TransportManager,
executor~ : EdgeExecutor,
at~ : Timestamp,
) -> LocalDiagnostics raise DiagnosticsError {
ensure_diagnostic_timestamp(at)
let negotiated_capabilities = match transport.negotiated() {
Some(link) => copy_transport_capabilities(link.capabilities)
None => []
}
let mut uncertain_executions = 0
let mut failed_executions = 0
let mut succeeded_executions = 0
for record in executor.records() {
match record.status {
ExecutionStatus::InProgress =>
uncertain_executions = uncertain_executions + 1
ExecutionStatus::Failed => failed_executions = failed_executions + 1
ExecutionStatus::Succeeded =>
succeeded_executions = succeeded_executions + 1
}
}
{
node_id: node.id,
lifecycle: node.lifecycle,
health: node.health,
captured_at: at,
pending_events: store.pending_count(),
dropped_events: store.dropped_count(),
sync_failures: self.sync_failures,
last_sync_error: self.last_sync_error,
sync_cursor: transport.sync_cursor(),
connection_state: transport.state(),
last_connected_at: transport.last_connected_at(),
last_error_category: match self.last_sync_error {
Some(_) => Some("sync")
None => None
},
negotiated_capabilities,
next_retry_at: transport.next_retry_at(),
policy_decision: self.last_policy_decision,
execution_records: executor.records().length(),
uncertain_executions,
failed_executions,
succeeded_executions,
events: copy_diagnostic_events(self.logs),
}
}
///|
/// Cross-component status exposed to local operators.
pub(all) struct LocalDiagnostics {
node_id : NodeId
lifecycle : NodeLifecycle
health : NodeHealth
captured_at : Timestamp
pending_events : Int32
dropped_events : Int32
sync_failures : Int32
last_sync_error : String?
sync_cursor : SyncCursor
connection_state : ConnectionState
last_connected_at : Timestamp?
last_error_category : String?
negotiated_capabilities : Array[TransportCapability]
next_retry_at : Timestamp?
policy_decision : PolicyDecision
execution_records : Int32
uncertain_executions : Int32
failed_executions : Int32
succeeded_executions : Int32
events : Array[DiagnosticEvent]
} derive(Debug, Eq)
///|
fn ensure_diagnostic_timestamp(at : Timestamp) -> Unit raise DiagnosticsError {
if at.unix_seconds < 0 {
raise DiagnosticsError::InvalidTimestamp
}
}
///|
fn copy_diagnostic_events(
events : Array[DiagnosticEvent],
) -> Array[DiagnosticEvent] {
let copy : Array[DiagnosticEvent] = []
for event in events {
copy.push({
at: event.at,
level: event.level,
component: event.component,
message: event.message,
})
}
copy
}
///|
fn copy_transport_capabilities(
capabilities : Array[TransportCapability],
) -> Array[TransportCapability] {
let copy : Array[TransportCapability] = []
for capability in capabilities {
copy.push(capability)
}
copy
}