Skip to main content

saluki_app/
bootstrap.rs

1//! Bootstrap utilities.
2
3use metrics::Level;
4use saluki_config::GenericConfiguration;
5use saluki_core::runtime::Supervisor;
6use saluki_error::{ErrorContext as _, GenericError};
7
8use crate::{
9    logging::{initialize_logging, LoggingConfiguration, LoggingGuard},
10    metrics::initialize_metrics,
11    tls::initialize_tls,
12};
13
14/// The result of running [`AppBootstrapper::bootstrap`].
15///
16/// Bundles together the [`BootstrapGuard`] that must be held for the lifetime of the application with the
17/// [`Supervisor`] that drives the background workers spawned during bootstrap. Callers must arrange for the
18/// supervisor to be run (either directly or by adding it to a parent supervisor) for those workers to make
19/// progress.
20pub struct Bootstrap {
21    /// Supervisor populated with workers for all background async tasks created during bootstrap.
22    pub supervisor: Supervisor,
23
24    /// Drop guard for resources acquired during bootstrap.
25    pub guard: BootstrapGuard,
26}
27
28/// A drop guard for ensuring deferred cleanup of resources acquired during bootstrap.
29pub struct BootstrapGuard {
30    logging_guard: LoggingGuard,
31}
32
33impl BootstrapGuard {
34    /// Returns a reference to the [`LoggingGuard`].
35    ///
36    /// Use this to obtain a [`LoggingOverrideController`][crate::logging::LoggingOverrideController] clone (via
37    /// [`LoggingGuard::controller`]) for downstream callers that drive runtime filter changes.
38    pub fn logging(&self) -> &LoggingGuard {
39        &self.logging_guard
40    }
41
42    /// Returns a mutable reference to the [`LoggingGuard`].
43    ///
44    /// Use this to swap the entire logging configuration (outputs, format, level) via [`LoggingGuard::reload`].
45    pub fn logging_mut(&mut self) -> &mut LoggingGuard {
46        &mut self.logging_guard
47    }
48}
49
50/// Early application initialization.
51///
52/// This helper type is used to configure the various low-level shared resources required by the application, such as
53/// the logging and metrics subsystems.
54pub struct AppBootstrapper {
55    logging_config: LoggingConfiguration,
56    metrics_prefix: String,
57    metrics_default_level: Level,
58}
59
60impl AppBootstrapper {
61    /// Creates a new `AppBootstrapper`.
62    ///
63    /// The bootstrapper is initialized with a [`simple`][LoggingConfiguration::simple] logging configuration. Callers
64    /// that have application-specific logging requirements should follow up with
65    /// [`with_logging_configuration`][Self::with_logging_configuration] to override this default.
66    ///
67    /// # Errors
68    ///
69    /// This currently doesn't fail, but the signature returns `Result` to leave room for future failures.
70    pub fn from_configuration(_config: &GenericConfiguration) -> Result<Self, GenericError> {
71        Ok(Self {
72            logging_config: LoggingConfiguration::simple(),
73            metrics_prefix: "saluki".to_string(),
74            metrics_default_level: Level::INFO,
75        })
76    }
77
78    /// Sets the prefix to use for internal metrics.
79    ///
80    /// Defaults to "saluki".
81    pub fn with_metrics_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
82        self.metrics_prefix = prefix.into();
83        self
84    }
85
86    /// Sets the default filter level for internal metrics.
87    ///
88    /// Metrics whose level is more verbose than this default are filtered out at flush time. The default also drives
89    /// the level that the filter is restored to whenever a runtime override is reset.
90    ///
91    /// Defaults to [`Level::INFO`].
92    pub fn with_metrics_default_level(mut self, level: Level) -> Self {
93        self.metrics_default_level = level;
94        self
95    }
96
97    /// Sets the logging configuration to use during bootstrap.
98    ///
99    /// Replaces the [`simple`][LoggingConfiguration::simple] default that
100    /// [`from_configuration`][Self::from_configuration] installs.
101    pub fn with_logging_configuration(mut self, logging_config: LoggingConfiguration) -> Self {
102        self.logging_config = logging_config;
103        self
104    }
105
106    /// Executes the bootstrap operation, initializing all configured subsystems.
107    ///
108    /// Returns a [`Bootstrap`] containing both a [`BootstrapGuard`] (which must be held until the application is
109    /// ready to shut down) and a [`Supervisor`] populated with workers for all background async tasks created
110    /// during bootstrap. Callers must arrange for the supervisor to run (typically by adding it to a parent
111    /// supervisor or calling [`Supervisor::run_with_shutdown`]) for those workers to make progress.
112    ///
113    /// # Errors
114    ///
115    /// If any of the bootstrap steps fail, an error will be returned.
116    pub async fn bootstrap(self) -> Result<Bootstrap, GenericError> {
117        // Initialize the logging subsystem first, since we want to make it possible to get any logs from the rest of
118        // the bootstrap process.
119        let (logging_guard, logging_override) = initialize_logging(self.logging_config)
120            .await
121            .error_context("Failed to initialize logging subsystem.")?;
122
123        // Initialize everything else.
124        initialize_tls().error_context("Failed to initialize TLS subsystem.")?;
125        let metrics_workers = initialize_metrics(self.metrics_prefix, self.metrics_default_level)
126            .await
127            .error_context("Failed to initialize metrics subsystem.")?;
128
129        // Build the supervisor for all bootstrap-spawned background workers. The default ambient runtime mode is
130        // appropriate here: these are lightweight tasks that share the parent runtime, and the runtime metrics
131        // worker has already eagerly captured the parent's `Handle` so it always describes the right runtime.
132        let mut supervisor =
133            Supervisor::new("app-bootstrap").error_context("Failed to construct app bootstrap supervisor.")?;
134        supervisor.add_worker(logging_override);
135        supervisor.add_worker(metrics_workers.flusher);
136        supervisor.add_worker(metrics_workers.runtime);
137        supervisor.add_worker(metrics_workers.override_processor);
138
139        Ok(Bootstrap {
140            supervisor,
141            guard: BootstrapGuard { logging_guard },
142        })
143    }
144}