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}