Skip to main content

saluki_app/
bootstrap.rs

1//! Bootstrap utilities.
2
3use saluki_config::GenericConfiguration;
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    // TODO: Just prefix at the moment.
56    metrics_config: String,
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    ///
66    /// # Errors
67    ///
68    /// This currently does not fail, but the signature returns `Result` to leave room for future failures.
69    pub fn from_configuration(_config: &GenericConfiguration) -> Result<Self, GenericError> {
70        Ok(Self {
71            logging_config: LoggingConfiguration::simple(),
72            metrics_config: "saluki".to_string(),
73        })
74    }
75
76    /// Sets the prefix to use for internal metrics.
77    ///
78    /// Defaults to "saluki".
79    pub fn with_metrics_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
80        self.metrics_config = prefix.into();
81        self
82    }
83
84    /// Sets the logging configuration to use during bootstrap.
85    ///
86    /// Replaces the [`simple`][LoggingConfiguration::simple] default that
87    /// [`from_configuration`][Self::from_configuration] installs.
88    pub fn with_logging_configuration(mut self, logging_config: LoggingConfiguration) -> Self {
89        self.logging_config = logging_config;
90        self
91    }
92
93    /// Executes the bootstrap operation, initializing all configured subsystems.
94    ///
95    /// Returns a [`Bootstrap`] containing both a [`BootstrapGuard`] (which must be held until the application is
96    /// ready to shut down) and a [`Supervisor`] populated with workers for all background async tasks created
97    /// during bootstrap. Callers must arrange for the supervisor to run (typically by adding it to a parent
98    /// supervisor or calling [`Supervisor::run_with_shutdown`]) for those workers to make progress.
99    ///
100    /// # Errors
101    ///
102    /// If any of the bootstrap steps fail, an error will be returned.
103    pub async fn bootstrap(self) -> Result<Bootstrap, GenericError> {
104        // Initialize the logging subsystem first, since we want to make it possible to get any logs from the rest of
105        // the bootstrap process.
106        let (logging_guard, logging_override) = initialize_logging(self.logging_config)
107            .await
108            .error_context("Failed to initialize logging subsystem.")?;
109
110        // Initialize everything else.
111        initialize_tls().error_context("Failed to initialize TLS subsystem.")?;
112        let metrics_workers = initialize_metrics(self.metrics_config)
113            .await
114            .error_context("Failed to initialize metrics subsystem.")?;
115
116        // Build the supervisor for all bootstrap-spawned background workers. The default ambient runtime mode is
117        // appropriate here: these are lightweight tasks that share the parent runtime, and the runtime metrics
118        // worker has already eagerly captured the parent's `Handle` so it always describes the right runtime.
119        let mut supervisor =
120            Supervisor::new("app-bootstrap").error_context("Failed to construct app bootstrap supervisor.")?;
121        supervisor.add_worker(logging_override);
122        supervisor.add_worker(metrics_workers.flusher);
123        supervisor.add_worker(metrics_workers.runtime);
124        supervisor.add_worker(metrics_workers.override_processor);
125
126        Ok(Bootstrap {
127            supervisor,
128            guard: BootstrapGuard { logging_guard },
129        })
130    }
131}