11//! Subsystem-scoped diagnostics control surface.
22
3+ // TODO: probably rework process name construction to use `SubsystemIdentifier` under the hood,
4+ // and expose that, in addition to process ID, as a task-local we can access so that we can
5+ // make `DiagnosticEmitter::new` require no parameters at all while still doing the right thing
6+
37use snafu:: { OptionExt as _, Snafu } ;
8+ use stringtheory:: MetaString ;
49
510use super :: { DiagnosticCollector , DiagnosticEvent } ;
611use crate :: {
712 runtime:: state:: { DataspaceRegistry , Identifier , IdentifierFilter , Subscription } ,
813 support:: SubsystemIdentifier ,
914} ;
1015
11- /// An error that can occur when creating a [`DiagnosticsEmitter`] from the current context .
16+ /// Errors that can occur when creating a [`DiagnosticsEmitter`].
1217#[ derive( Debug , Snafu ) ]
1318#[ snafu( context( suffix( false ) ) ) ]
1419pub enum DiagnosticsEmitterError {
@@ -22,8 +27,8 @@ pub enum DiagnosticsEmitterError {
2227
2328/// A subsystem-scoped control surface for exposing diagnostics.
2429///
25- /// A `DiagnosticsEmitter` is created for a single subsystem, identified by a [`SubsystemIdentifier`], and attaches to
26- /// the current dataspace. It hides the boilerplate of interacting with the dataspace directly, while still using it
30+ /// A `DiagnosticsEmitter` is created for a single subsystem, identified by a [`SubsystemIdentifier`], and is attached
31+ /// to a specific dataspace. It hides the boilerplate of interacting with the dataspace directly, while still using it
2732/// under the hood so that other subsystems can subscribe to what is exposed in a decoupled, eventually consistent way.
2833///
2934/// It exposes two capabilities:
@@ -49,16 +54,15 @@ pub enum DiagnosticsEmitterError {
4954/// dataspace,
5055/// );
5156///
52- /// // Expose an artifact that is produced on demand.
53- /// emitter.register_collector("state.json", || b"{}".to_vec() );
57+ /// // Expose an artifact that is produced on demand:
58+ /// emitter.register_collector("state.json", || b"{}");
5459///
55- /// // Emit a point-in-time event.
60+ /// // Emit a point-in-time event:
5661/// emitter.emit(DiagnosticEvent::new("credentials rejected", DiagnosticDetails::InvalidApiKey));
5762/// ```
5863#[ derive( Clone ) ]
5964pub struct DiagnosticsEmitter {
60- id : SubsystemIdentifier ,
61- event_id : Identifier ,
65+ base_id : MetaString ,
6266 dataspace : DataspaceRegistry ,
6367}
6468
@@ -67,65 +71,63 @@ impl DiagnosticsEmitter {
6771 ///
6872 /// # Errors
6973 ///
70- /// Returns [`DiagnosticsEmitterError::NoDataspace`] if no dataspace is available in the current context (that is,
71- /// when not running inside a supervision tree).
74+ /// If no dataspace is available, an error is returned.
7275 pub fn from_current ( id : SubsystemIdentifier ) -> Result < Self , DiagnosticsEmitterError > {
7376 let dataspace = DataspaceRegistry :: try_current ( ) . context ( NoDataspace ) ?;
7477 Ok ( Self :: from_dataspace ( id, dataspace) )
7578 }
7679
7780 /// Creates an emitter for the given subsystem from an already-held dataspace handle.
78- ///
79- /// This avoids a second task-local lookup when the caller already holds a [`DataspaceRegistry`]. Holding one
80- /// already proves a dataspace exists, so this is infallible.
8181 pub fn from_dataspace ( id : SubsystemIdentifier , dataspace : DataspaceRegistry ) -> Self {
82- let event_id = Identifier :: named ( id. to_string ( ) ) ;
82+ let base_id = id. to_string ( ) ;
8383 Self {
84- id,
85- event_id,
84+ base_id : base_id. into ( ) ,
8685 dataspace,
8786 }
8887 }
8988
90- /// Registers a named collector whose bytes are gathered into diagnostic artifacts on demand .
89+ /// Registers a collector for a given artifact .
9190 ///
9291 /// The collector is exposed until it is explicitly removed via [`unregister_collector`], or until the owning
9392 /// process exits, whichever comes first. Registering a collector with an artifact name that is already registered
9493 /// by this subsystem replaces the previous one.
9594 ///
96- /// `collect_fn` runs synchronously and must return promptly, as it can delay the collection of artifacts for the
97- /// whole system.
95+ /// Care should be taken when registering a collector:
96+ ///
97+ /// - the given artifact name _should_ be unique within the overall system, and should be generally suitable as a
98+ /// file name when possible (artifact names are sanitized/normalized where necessary, but may lose useful
99+ /// information in the process)
100+ /// - the collection function (`collect_fn`) will be run synchronously and should return promptly, as it can delay
101+ /// the collection of artifacts for the whole system
98102 ///
99103 /// [`unregister_collector`]: Self::unregister_collector
100- pub fn register_collector < F > ( & self , artifact_name : impl Into < String > , collect_fn : F )
104+ pub fn register_collector < F , T > ( & self , artifact_name : impl Into < String > , collect_fn : F )
101105 where
102- F : Fn ( ) -> Vec < u8 > + Send + Sync + ' static ,
106+ F : Fn ( ) -> T + Send + Sync + ' static ,
107+ T : Into < Vec < u8 > > ,
103108 {
104109 let collector = DiagnosticCollector :: new ( artifact_name, collect_fn) ;
105- let id = self . collector_identifier ( collector. artifact_name ( ) ) ;
110+ let id = self . build_collector_identifier ( collector. artifact_name ( ) ) ;
106111 self . dataspace . assert ( collector, id) ;
107112 }
108113
109- /// Removes a previously registered collector by artifact name.
114+ /// Removes a previously registered collector by name
110115 ///
111116 /// Does nothing if no collector with that name is currently registered by this subsystem.
112117 pub fn unregister_collector ( & self , artifact_name : impl AsRef < str > ) {
113- let id = self . collector_identifier ( artifact_name. as_ref ( ) ) ;
118+ let id = self . build_collector_identifier ( artifact_name. as_ref ( ) ) ;
114119 self . dataspace . retract :: < DiagnosticCollector > ( id) ;
115120 }
116121
117122 /// Emits a diagnostic event.
118123 ///
119- /// The event is sent as a transient dataspace message keyed by this subsystem's identifier: it is delivered only to
120- /// subscribers present at the time of emission, is never stored or replayed, and is dropped if there are no
121- /// matching subscribers.
124+ /// Diagnostics events are transient and only delivered to active listeners.
122125 pub fn emit ( & self , event : DiagnosticEvent ) {
123- self . dataspace . send ( event, self . event_id . clone ( ) ) ;
126+ self . dataspace . send ( event, self . base_id . clone ( ) ) ;
124127 }
125128
126- /// Returns the dataspace identifier for a collector with the given artifact name.
127- fn collector_identifier ( & self , artifact_name : & str ) -> Identifier {
128- Identifier :: named ( self . id . clone ( ) . child ( artifact_name) . to_string ( ) )
129+ fn build_collector_identifier ( & self , artifact_name : & str ) -> Identifier {
130+ Identifier :: named ( format ! ( "{}-{}" , self . base_id, artifact_name) )
129131 }
130132}
131133
@@ -136,7 +138,7 @@ impl DiagnosticsEmitter {
136138///
137139/// # Errors
138140///
139- /// Returns [`DiagnosticsEmitterError::NoDataspace`] if no dataspace is available in the current context .
141+ /// If no dataspace is available, an error is returned .
140142pub fn subscribe_events ( filter : IdentifierFilter ) -> Result < Subscription < DiagnosticEvent > , DiagnosticsEmitterError > {
141143 let dataspace = DataspaceRegistry :: try_current ( ) . context ( NoDataspace ) ?;
142144 Ok ( dataspace. subscribe :: < DiagnosticEvent > ( filter) )
@@ -176,7 +178,7 @@ mod tests {
176178 #[ test]
177179 fn register_collector_is_discoverable ( ) {
178180 let registry = DataspaceRegistry :: new ( ) ;
179- emitter ( registry. clone ( ) ) . register_collector ( "state.json" , || b"hello" . to_vec ( ) ) ;
181+ emitter ( registry. clone ( ) ) . register_collector ( "state.json" , || b"hello" ) ;
180182
181183 let collectors = registry. current_values :: < DiagnosticCollector > ( IdentifierFilter :: all ( ) ) ;
182184 assert_eq ! ( collectors. len( ) , 1 ) ;
@@ -204,8 +206,8 @@ mod tests {
204206 fn reregister_same_artifact_updates ( ) {
205207 let registry = DataspaceRegistry :: new ( ) ;
206208 let emitter = emitter ( registry. clone ( ) ) ;
207- emitter. register_collector ( "state.json" , || b"v1" . to_vec ( ) ) ;
208- emitter. register_collector ( "state.json" , || b"v2" . to_vec ( ) ) ;
209+ emitter. register_collector ( "state.json" , || b"v1" ) ;
210+ emitter. register_collector ( "state.json" , || b"v2" ) ;
209211
210212 let collectors = registry. current_values :: < DiagnosticCollector > ( IdentifierFilter :: all ( ) ) ;
211213 assert_eq ! ( collectors. len( ) , 1 ) ;
@@ -225,7 +227,7 @@ mod tests {
225227 let mut recv = test_spawn ( sub. recv ( ) ) ;
226228 match assert_ready ! ( recv. poll( ) ) {
227229 Some ( DataspaceUpdate :: Asserted ( id, collector) ) => {
228- assert_eq ! ( id, Identifier :: named( "sub.state_json " ) ) ;
230+ assert_eq ! ( id, Identifier :: named( "sub-state.json " ) ) ;
229231 assert_eq ! ( collector. artifact_name( ) , "state.json" ) ;
230232 }
231233 _ => panic ! ( "expected an assertion first" ) ,
@@ -235,7 +237,7 @@ mod tests {
235237 // Second update: the retraction.
236238 let mut recv = test_spawn ( sub. recv ( ) ) ;
237239 match assert_ready ! ( recv. poll( ) ) {
238- Some ( DataspaceUpdate :: Retracted ( id) ) => assert_eq ! ( id, Identifier :: named( "sub.state_json " ) ) ,
240+ Some ( DataspaceUpdate :: Retracted ( id) ) => assert_eq ! ( id, Identifier :: named( "sub-state.json " ) ) ,
239241 _ => panic ! ( "expected a retraction second" ) ,
240242 }
241243 }
0 commit comments