rhi

Coordinated trade for connected markets
git clone https://radroots.dev/git/rhi.git
Log | Files | Refs | README | LICENSE

diagnostics_v1.rs (5659B)


      1 //! Closed process-result and structured stderr diagnostic contract.
      2 
      3 use crate::{RhiProcessResult, RhiServicePhase};
      4 use core::fmt;
      5 
      6 /// Exact Rhi diagnostics contract version.
      7 pub const RHI_DIAGNOSTICS_CONTRACT_VERSION: u32 = 1;
      8 
      9 /// Hard maximum for one canonical Rhi structured log record, excluding newline.
     10 pub const RHI_LOG_RECORD_MAX_UTF8_BYTES: usize = 512;
     11 
     12 const LOG_SCHEMA: &str = "radroots.rhi.log.v1";
     13 
     14 /// Closed structured-log severity vocabulary admitted by Rhi.
     15 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     16 pub enum RhiLogLevel {
     17     Trace,
     18     Debug,
     19     Info,
     20     Warn,
     21     Error,
     22 }
     23 
     24 impl RhiLogLevel {
     25     #[must_use]
     26     pub const fn as_str(self) -> &'static str {
     27         match self {
     28             Self::Trace => "trace",
     29             Self::Debug => "debug",
     30             Self::Info => "info",
     31             Self::Warn => "warn",
     32             Self::Error => "error",
     33         }
     34     }
     35 }
     36 
     37 /// Closed structured-log event vocabulary required by the current runtime plan.
     38 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     39 pub enum RhiLogEvent {
     40     ProcessResult,
     41     Lifecycle,
     42     CriticalTaskFailed,
     43     ShutdownRequested,
     44     ShutdownForced,
     45 }
     46 
     47 impl RhiLogEvent {
     48     #[must_use]
     49     pub const fn as_str(self) -> &'static str {
     50         match self {
     51             Self::ProcessResult => "process_result",
     52             Self::Lifecycle => "lifecycle",
     53             Self::CriticalTaskFailed => "critical_task_failed",
     54             Self::ShutdownRequested => "shutdown_requested",
     55             Self::ShutdownForced => "shutdown_forced",
     56         }
     57     }
     58 }
     59 
     60 /// One sealed canonical structured log record containing only governed values.
     61 #[derive(Clone, Copy, PartialEq, Eq)]
     62 pub struct RhiLogRecord {
     63     level: RhiLogLevel,
     64     event: RhiLogEvent,
     65     code: &'static str,
     66     process_result: Option<RhiProcessResult>,
     67 }
     68 
     69 impl RhiLogRecord {
     70     /// Builds the exact terminal record for one governed process result.
     71     #[must_use]
     72     pub const fn process_result(result: RhiProcessResult) -> Self {
     73         let level = match result {
     74             RhiProcessResult::Success => RhiLogLevel::Info,
     75             RhiProcessResult::OperationRejectedOrConflict => RhiLogLevel::Warn,
     76             RhiProcessResult::UnexpectedInternal
     77             | RhiProcessResult::InputOrConfiguration
     78             | RhiProcessResult::ServiceOrDependencyUnavailable
     79             | RhiProcessResult::StateOrIdentityUnavailable
     80             | RhiProcessResult::DoctorRequiredCheckFailed => RhiLogLevel::Error,
     81         };
     82         Self {
     83             level,
     84             event: RhiLogEvent::ProcessResult,
     85             code: result.code(),
     86             process_result: Some(result),
     87         }
     88     }
     89 
     90     /// Builds the exact phase record from an already-published lifecycle value.
     91     #[must_use]
     92     pub const fn lifecycle(phase: RhiServicePhase) -> Self {
     93         let (level, code) = match phase {
     94             RhiServicePhase::Starting => (RhiLogLevel::Info, "starting"),
     95             RhiServicePhase::Ready => (RhiLogLevel::Info, "ready"),
     96             RhiServicePhase::Degraded => (RhiLogLevel::Warn, "degraded"),
     97             RhiServicePhase::Unready => (RhiLogLevel::Warn, "unready"),
     98             RhiServicePhase::Stopping => (RhiLogLevel::Info, "stopping"),
     99             RhiServicePhase::Failed => (RhiLogLevel::Error, "failed"),
    100         };
    101         Self {
    102             level,
    103             event: RhiLogEvent::Lifecycle,
    104             code,
    105             process_result: None,
    106         }
    107     }
    108 
    109     #[must_use]
    110     pub const fn critical_task_failed() -> Self {
    111         Self {
    112             level: RhiLogLevel::Error,
    113             event: RhiLogEvent::CriticalTaskFailed,
    114             code: "critical_task_failed",
    115             process_result: None,
    116         }
    117     }
    118 
    119     #[must_use]
    120     pub const fn shutdown_requested() -> Self {
    121         Self {
    122             level: RhiLogLevel::Info,
    123             event: RhiLogEvent::ShutdownRequested,
    124             code: "first_signal",
    125             process_result: None,
    126         }
    127     }
    128 
    129     #[must_use]
    130     pub const fn shutdown_forced() -> Self {
    131         Self {
    132             level: RhiLogLevel::Error,
    133             event: RhiLogEvent::ShutdownForced,
    134             code: "second_signal",
    135             process_result: None,
    136         }
    137     }
    138 
    139     #[must_use]
    140     pub const fn level(&self) -> RhiLogLevel {
    141         self.level
    142     }
    143 
    144     #[must_use]
    145     pub const fn event(&self) -> RhiLogEvent {
    146         self.event
    147     }
    148 
    149     #[must_use]
    150     pub const fn code(&self) -> &'static str {
    151         self.code
    152     }
    153 
    154     #[must_use]
    155     pub const fn process_exit(&self) -> Option<RhiProcessResult> {
    156         self.process_result
    157     }
    158 }
    159 
    160 impl fmt::Debug for RhiLogRecord {
    161     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    162         formatter
    163             .debug_struct("RhiLogRecord")
    164             .field("level", &self.level)
    165             .field("event", &self.event)
    166             .field("code", &self.code)
    167             .field(
    168                 "exit_code",
    169                 &self.process_result.map(RhiProcessResult::exit_code_u8),
    170             )
    171             .finish()
    172     }
    173 }
    174 
    175 impl fmt::Display for RhiLogRecord {
    176     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    177         write!(
    178             formatter,
    179             "{{\"schema\":\"{LOG_SCHEMA}\",\"contract_version\":{RHI_DIAGNOSTICS_CONTRACT_VERSION},\"service\":\"rhi\",\"level\":\"{}\",\"event\":\"{}\",\"code\":\"{}\"",
    180             self.level.as_str(),
    181             self.event.as_str(),
    182             self.code,
    183         )?;
    184         if let Some(result) = self.process_result {
    185             write!(formatter, ",\"exit_code\":{}", result.exit_code_u8())?;
    186         }
    187         formatter.write_str("}")
    188     }
    189 }