blueprint_qos/service_builder.rs
1use std::sync::Arc;
2
3use crate::QoSConfig;
4use crate::error::Result;
5use crate::heartbeat::{HeartbeatConfig, HeartbeatConsumer};
6use crate::logging::{GrafanaConfig, LokiConfig};
7use crate::metrics::opentelemetry::OpenTelemetryConfig;
8use crate::metrics::types::MetricsConfig;
9use crate::servers::{
10 grafana::GrafanaServerConfig, loki::LokiServerConfig, prometheus::PrometheusServerConfig,
11};
12use crate::unified_service::QoSService;
13
14/// Fluent builder for assembling a `QoSService`.
15///
16/// Use this builder to enable and configure optional sub-systems such as heartbeat
17/// reporting, metrics (Prometheus/OpenTelemetry), log aggregation (Loki) and
18/// Grafana dashboards. All features are off by default and can be enabled with the
19/// corresponding `with_*` methods.
20pub struct QoSServiceBuilder<C: HeartbeatConsumer + Send + Sync + 'static> {
21 config: QoSConfig,
22 heartbeat_consumer: Option<Arc<C>>,
23 _phantom_c: std::marker::PhantomData<C>,
24 otel_config: Option<OpenTelemetryConfig>,
25 prometheus_datasource: Option<String>,
26 loki_datasource: Option<String>,
27 create_dashboard: bool,
28 http_rpc_endpoint: Option<String>,
29 ws_rpc_endpoint: Option<String>,
30 keystore_uri: Option<String>,
31}
32
33impl<C: HeartbeatConsumer + Send + Sync + 'static> Default for QoSServiceBuilder<C> {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl<C: HeartbeatConsumer + Send + Sync + 'static> QoSServiceBuilder<C> {
40 /// Creates a new `QoS` service builder with default settings.
41 ///
42 /// Initializes a builder with an empty configuration. All components (heartbeat, metrics,
43 /// logging, etc.) are disabled by default and must be explicitly configured using the
44 /// builder methods. This provides a clean starting point for building a custom `QoS`
45 /// observability setup.
46 #[must_use]
47 pub fn new() -> Self {
48 Self {
49 config: QoSConfig::default(),
50 heartbeat_consumer: None,
51 _phantom_c: std::marker::PhantomData,
52 otel_config: None,
53 prometheus_datasource: None,
54 loki_datasource: None,
55 create_dashboard: false,
56 http_rpc_endpoint: None,
57 ws_rpc_endpoint: None,
58 keystore_uri: None,
59 }
60 }
61
62 /// Sets the complete `QoS` configuration at once.
63 ///
64 /// This method allows you to provide a pre-configured `QoSConfig` instance,
65 /// which can be useful when loading configuration from external sources or
66 /// when you want to reuse an existing configuration. This will override any
67 /// previous component-specific configurations that were set on the builder.
68 ///
69 /// # Parameters
70 /// * `config` - A complete `QoSConfig` containing settings for all `QoS` components
71 #[must_use]
72 pub fn with_config(mut self, config: QoSConfig) -> Self {
73 self.config = config;
74 self
75 }
76
77 /// Configures the heartbeat service component.
78 ///
79 /// The heartbeat service sends periodic signals to the Tangle blockchain to indicate that
80 /// the service is alive and functioning properly. This helps prevent slashing of staked
81 /// tokens and provides operational visibility.
82 ///
83 /// # Parameters
84 /// * `config` - Configuration for the heartbeat service including service ID, blueprint ID,
85 /// and heartbeat parameters such as interval and jitter
86 #[must_use]
87 pub fn with_heartbeat_config(mut self, config: HeartbeatConfig) -> Self {
88 self.config.heartbeat = Some(config);
89 self
90 }
91
92 /// Configures the metrics collection component.
93 ///
94 /// Metrics collection captures system resource usage (CPU, memory, disk, network) and
95 /// application-specific metrics (job execution statistics, custom metrics). These metrics
96 /// can be exported to Prometheus and visualized in Grafana dashboards.
97 ///
98 /// # Parameters
99 /// * `config` - Configuration for metrics collection including retention settings,
100 /// collection intervals, and export options
101 #[must_use]
102 pub fn with_metrics_config(mut self, config: MetricsConfig) -> Self {
103 self.config.metrics = Some(config);
104 self
105 }
106
107 /// Configures the Loki logging integration.
108 ///
109 /// Loki is a log aggregation system that works well with Grafana for visualization.
110 /// This integration sends application logs to a Loki server, allowing centralized
111 /// log storage, querying, and correlation with metrics.
112 ///
113 /// # Parameters
114 /// * `config` - Configuration for Loki integration including server URL, authentication,
115 /// labels, and batch settings
116 #[must_use]
117 pub fn with_loki_config(mut self, config: LokiConfig) -> Self {
118 self.config.loki = Some(config);
119 self
120 }
121
122 /// Configures the Grafana integration for dashboard visualization.
123 ///
124 /// Grafana provides powerful visualization capabilities for metrics and logs. This
125 /// configuration allows the `QoS` service to automatically create and update dashboards
126 /// that display service health, resource usage, and operational metrics.
127 ///
128 /// # Parameters
129 /// * `config` - Configuration for Grafana integration including server URL, authentication,
130 /// and organization settings
131 #[must_use]
132 pub fn with_grafana_config(mut self, config: GrafanaConfig) -> Self {
133 self.config.grafana = Some(config);
134 self
135 }
136
137 /// Sets the heartbeat consumer implementation.
138 ///
139 /// The heartbeat consumer is responsible for processing and submitting heartbeat messages
140 /// to the Tangle blockchain. It handles the cryptographic signing of heartbeat messages
141 /// and submits them to the appropriate chain endpoint.
142 ///
143 /// This is required if heartbeat functionality is enabled.
144 ///
145 /// # Parameters
146 /// * `consumer` - Implementation of the `HeartbeatConsumer` trait that will process and
147 /// submit heartbeats to the blockchain
148 #[must_use]
149 pub fn with_heartbeat_consumer(mut self, consumer: Arc<C>) -> Self {
150 self.heartbeat_consumer = Some(consumer);
151 self
152 }
153
154 /// Configures OpenTelemetry integration for distributed tracing and advanced metrics.
155 ///
156 /// OpenTelemetry provides a standardized way to collect and export telemetry data
157 /// (traces, metrics, logs) across services. This integration enables correlation of
158 /// traces with logs and metrics for comprehensive observability.
159 ///
160 /// # Parameters
161 /// * `config` - OpenTelemetry configuration including exporter settings, sampling,
162 /// and resource attribution
163 #[must_use]
164 pub fn with_otel_config(mut self, config: OpenTelemetryConfig) -> Self {
165 self.otel_config = Some(config);
166 self
167 }
168
169 /// Configures automatic Grafana dashboard creation with a specific Prometheus datasource.
170 ///
171 /// This method enables the automatic creation or updating of a Grafana dashboard
172 /// during `QoS` service initialization. The dashboard will include panels for system
173 /// metrics, resource usage, and application-specific metrics sourced from the
174 /// specified Prometheus datasource.
175 ///
176 /// # Parameters
177 /// * `datasource_uid` - The Grafana UID of the Prometheus datasource to use for metrics visualization
178 #[must_use]
179 pub fn with_prometheus_datasource(mut self, datasource_uid: &str) -> Self {
180 self.prometheus_datasource = Some(datasource_uid.to_string());
181 self.create_dashboard = true;
182 self
183 }
184
185 /// Configures Grafana dashboard to include logs from a specific Loki datasource.
186 ///
187 /// When used in combination with `with_prometheus_datasource`, this enables the creation
188 /// of comprehensive dashboards that include both metrics and logs. The dashboard will
189 /// include panels that allow for correlation between metrics and logs using the same
190 /// timestamps.
191 ///
192 /// # Parameters
193 /// * `datasource_uid` - The Grafana UID of the Loki datasource to use for log visualization
194 #[must_use]
195 pub fn with_loki_datasource(mut self, datasource_uid: &str) -> Self {
196 self.loki_datasource = Some(datasource_uid.to_string());
197 self
198 }
199
200 /// Configures the managed Grafana server instance.
201 ///
202 /// If server management is enabled, this configuration will be used to start and
203 /// manage a Grafana server instance automatically. The server can be run as a
204 /// Docker container or embedded server depending on the configuration.
205 ///
206 /// # Parameters
207 /// * `config` - Configuration for the Grafana server including host, port, and authentication settings
208 #[must_use]
209 pub fn with_grafana_server_config(mut self, config: GrafanaServerConfig) -> Self {
210 self.config.grafana_server = Some(config);
211 self
212 }
213
214 /// Configures the managed Loki log aggregation server instance.
215 ///
216 /// If server management is enabled, this configuration will be used to start and
217 /// manage a Loki server instance automatically. This server will collect and store
218 /// logs from the application for later querying and visualization in Grafana.
219 ///
220 /// # Parameters
221 /// * `config` - Configuration for the Loki server including host, port, retention, and storage settings
222 #[must_use]
223 pub fn with_loki_server_config(mut self, config: LokiServerConfig) -> Self {
224 self.config.loki_server = Some(config);
225 self
226 }
227
228 /// Set the Prometheus server configuration
229 #[must_use]
230 pub fn with_prometheus_server_config(mut self, config: PrometheusServerConfig) -> Self {
231 self.config.prometheus_server = Some(config);
232 self
233 }
234
235 /// Enable or disable server management
236 #[must_use]
237 pub fn manage_servers(mut self, manage: bool) -> Self {
238 self.config.manage_servers = manage;
239 self
240 }
241
242 /// Build the `QoS` service
243 ///
244 /// # Errors
245 /// Returns an error if the heartbeat consumer is not provided or if the service initialization fails
246 /// Set the HTTP RPC endpoint for `HeartbeatService`
247 #[must_use]
248 pub fn with_http_rpc_endpoint(mut self, endpoint: String) -> Self {
249 self.http_rpc_endpoint = Some(endpoint);
250 self
251 }
252
253 /// Set the WebSocket RPC endpoint for `HeartbeatService`
254 #[must_use]
255 pub fn with_ws_rpc_endpoint(mut self, endpoint: String) -> Self {
256 self.ws_rpc_endpoint = Some(endpoint);
257 self
258 }
259
260 /// Set the Keystore URI for `HeartbeatService`
261 #[must_use]
262 pub fn with_keystore_uri(mut self, uri: String) -> Self {
263 self.keystore_uri = Some(uri);
264 self
265 }
266
267 /// Build the `QoS` service
268 ///
269 /// # Errors
270 /// Returns an error if the heartbeat consumer is not provided or if the service initialization fails
271 pub async fn build(self) -> Result<QoSService<C>> {
272 let heartbeat_consumer = self.heartbeat_consumer.ok_or_else(|| {
273 crate::error::Error::Other("Heartbeat consumer is required".to_string())
274 })?;
275
276 let ws_rpc = self.ws_rpc_endpoint.unwrap_or_default();
277 let keystore = self.keystore_uri.unwrap_or_default();
278
279 if let Some(otel_config) = self.otel_config {
280 QoSService::with_otel_config(
281 self.config,
282 heartbeat_consumer,
283 ws_rpc,
284 keystore,
285 otel_config,
286 )
287 .await
288 } else {
289 QoSService::new(self.config, heartbeat_consumer, ws_rpc, keystore).await
290 }
291 }
292}