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}