saluki_app/
bootstrap.rs

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