Skip to main content

ferroui_splat/application_performance_monitoring/
mod.rs

1//! Feature usage tracking (`Splat/ApplicationPerformanceMonitoring`): the interfaces that the
2//! adapters of monitoring services implement, and the default implementation, which logs.
3
4mod default_feature_usage_tracking;
5mod guid;
6
7pub use default_feature_usage_tracking::{
8    DefaultFeatureUsageTrackingManager, DefaultFeatureUsageTrackingSession,
9    FuncFeatureUsageTrackingManager,
10};
11pub use guid::Guid;
12
13use crate::service_location::{AppLocator, ReadonlyDependencyResolverExt};
14use ferroui_system::IDisposable;
15use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
16use std::rc::Rc;
17
18/// A session of the usage of a feature (`IFeatureUsageTrackingSession`); disposing it ends the
19/// session.
20pub trait IFeatureUsageTrackingSession: IDisposable {
21    /// The name of the feature (`FeatureName`).
22    fn feature_name(&self) -> &str;
23
24    /// A session of a sub-feature of this one (`SubFeature`).
25    fn sub_feature(&self, description: &str) -> Rc<dyn IFeatureUsageTrackingSession>;
26
27    /// Records a failure of the feature (`OnException`).
28    fn on_exception(&self, exception: &dyn std::error::Error);
29}
30
31/// A session with references of the type `R` to itself and to its parent
32/// (`IFeatureUsageTrackingSession<TReferenceType>`).
33pub trait IFeatureUsageTrackingSessionOf<R>: IFeatureUsageTrackingSession {
34    /// The reference of the session (`FeatureReference`).
35    fn feature_reference(&self) -> R;
36
37    /// The reference of the parent session (`ParentReference`).
38    fn parent_reference(&self) -> R;
39}
40
41/// Starts the sessions of features (`IFeatureUsageTrackingManager`).
42pub trait IFeatureUsageTrackingManager {
43    /// A session for the feature `feature_name` (`GetFeatureUsageTrackingSession`).
44    fn get_feature_usage_tracking_session(&self, feature_name: &str) -> Rc<dyn IFeatureUsageTrackingSession>;
45}
46
47/// Tracks the navigation between views (`IViewTracking`).
48pub trait IViewTracking {
49    /// Records the navigation to the view `name` (`OnViewNavigation`).
50    fn on_view_navigation(&self, name: &str);
51}
52
53/// A type that tracks the usage of its features (`IEnableFeatureUsageTracking`), through the
54/// methods of [`EnableFeatureUsageTrackingExt`].
55pub trait IEnableFeatureUsageTracking {}
56
57/// The failure of a feature, for [`IFeatureUsageTrackingSession::on_exception`]: the message of
58/// the panic of the action.
59#[derive(Debug)]
60struct ActionPanicked(String);
61
62impl std::fmt::Display for ActionPanicked {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        f.write_str(&self.0)
65    }
66}
67
68impl std::error::Error for ActionPanicked {}
69
70fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
71    if let Some(message) = payload.downcast_ref::<&str>() {
72        (*message).to_owned()
73    } else if let Some(message) = payload.downcast_ref::<String>() {
74        message.clone()
75    } else {
76        "panic".to_owned()
77    }
78}
79
80/// Runs `action` in `session`; a panic of the action is recorded with `on_exception` and
81/// propagates; the session is disposed in either case.
82fn run_in_session(
83    session: Rc<dyn IFeatureUsageTrackingSession>,
84    action: impl FnOnce(&dyn IFeatureUsageTrackingSession),
85) {
86    let result = catch_unwind(AssertUnwindSafe(|| action(&*session)));
87    if let Err(payload) = result {
88        session.on_exception(&ActionPanicked(panic_message(payload.as_ref())));
89        session.dispose();
90        resume_unwind(payload);
91    }
92    session.dispose();
93}
94
95/// The feature usage tracking of a type (`EnableFeatureUsageTrackingExtensions`).
96// Deviation (DEVIATIONS.md, Splat): an exception of the action is a panic; it is recorded with the
97// message of the panic and propagates.
98pub trait EnableFeatureUsageTrackingExt: IEnableFeatureUsageTracking {
99    /// A session for the feature `feature_name`, from the `dyn IFeatureUsageTrackingManager` of
100    /// the current resolver (`FeatureUsageTrackingSession`).
101    ///
102    /// Panics when the resolver has no manager (`InvalidOperationException`).
103    fn feature_usage_tracking_session(&self, feature_name: &str) -> Rc<dyn IFeatureUsageTrackingSession> {
104        match AppLocator::current().get_service::<dyn IFeatureUsageTrackingManager>() {
105            None => panic!(
106                "Feature Usage Tracking Manager is null. This should never happen, your dependency resolver is broken"
107            ),
108            Some(manager) => manager.get_feature_usage_tracking_session(feature_name),
109        }
110    }
111
112    /// Runs `action` in a session for the feature `feature_name`
113    /// (`WithFeatureUsageTrackingSession`).
114    fn with_feature_usage_tracking_session(
115        &self,
116        feature_name: &str,
117        action: impl FnOnce(&dyn IFeatureUsageTrackingSession),
118    ) {
119        run_in_session(self.feature_usage_tracking_session(feature_name), action);
120    }
121}
122
123impl<T: IEnableFeatureUsageTracking + ?Sized> EnableFeatureUsageTrackingExt for T {}
124
125/// The sub-features of a session (`EnableFeatureUsageTrackingExtensions`).
126pub trait FeatureUsageTrackingSessionExt: IFeatureUsageTrackingSession {
127    /// Runs `action` in a session of the sub-feature `feature_name`
128    /// (`WithSubFeatureUsageTrackingSession`).
129    fn with_sub_feature_usage_tracking_session(
130        &self,
131        feature_name: &str,
132        action: impl FnOnce(&dyn IFeatureUsageTrackingSession),
133    ) {
134        run_in_session(self.sub_feature(feature_name), action);
135    }
136}
137
138impl<T: IFeatureUsageTrackingSession + ?Sized> FeatureUsageTrackingSessionExt for T {}