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}