ursula_config/config.rs
1use std::path::PathBuf;
2
3use serde::Deserialize;
4use serde::Serialize;
5
6use crate::human::HumanDuration;
7use crate::human::HumanSize;
8
9/// Cold-storage backend selector.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
11#[serde(rename_all = "lowercase")]
12pub enum ColdBackend {
13 #[default]
14 #[serde(alias = "disabled", alias = "off")]
15 None,
16 #[serde(alias = "mem", alias = "inmem")]
17 Memory,
18 S3,
19}
20
21/// Raft WAL persistence backend selector.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
23#[serde(rename_all = "lowercase")]
24pub enum WalBackend {
25 #[default]
26 Memory,
27 Disk,
28}
29
30/// Raft snapshot store backend selector.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
32#[serde(rename_all = "lowercase")]
33pub enum RaftSnapshotBackend {
34 #[default]
35 #[serde(alias = "default", alias = "")]
36 Inline,
37 S3,
38}
39
40/// Top-level Ursula server configuration.
41///
42/// Populated from a TOML config file, an optional preset, and CLI overrides.
43#[derive(Debug, Clone, Default, Deserialize, Serialize)]
44#[serde(default, deny_unknown_fields)]
45pub struct UrsulaConfig {
46 pub server: ServerConfig,
47 pub runtime: RuntimeConfig,
48 pub raft: RaftConfig,
49 pub storage: StorageConfig,
50 pub governance: GovernanceConfig,
51 pub observability: ObservabilityConfig,
52}
53
54/// HTTP server binding and admission settings.
55#[derive(Debug, Clone, Deserialize, Serialize)]
56#[serde(default, deny_unknown_fields)]
57pub struct ServerConfig {
58 /// Public HTTP client API bind address.
59 pub listen: String,
60 /// Optional separate bind for the cluster / Raft gRPC plane.
61 /// When omitted, both planes share `listen`.
62 pub cluster_listen: Option<String>,
63 /// Admin-plane bind for mutating operator endpoints (raft ops,
64 /// maintenance drain, cold-flush trigger). Loopback by default so nodes
65 /// expose no cluster-mutation surface on the network; operator tooling
66 /// reaches it through an SSH/SSM/port-forward tunnel.
67 pub admin_listen: String,
68 /// Process-wide cap on accepted write body bytes held by the HTTP layer.
69 pub http_inflight_body_size: HumanSize,
70}
71
72impl Default for ServerConfig {
73 fn default() -> Self {
74 Self {
75 listen: "127.0.0.1:4437".to_string(),
76 cluster_listen: None,
77 admin_listen: "127.0.0.1:4438".to_string(),
78 http_inflight_body_size: HumanSize::mib(256),
79 }
80 }
81}
82
83/// Per-core runtime sizing and admission controls.
84#[derive(Debug, Clone, Deserialize, Serialize)]
85#[serde(default, deny_unknown_fields)]
86pub struct RuntimeConfig {
87 /// Number of CPU cores / tokio worker threads to use.
88 pub core_count: usize,
89 /// Soft RSS cap. When the process RSS exceeds this value, new writes are
90 /// rejected with HTTP 503. `None` disables the monitor.
91 pub node_memory_abort_cap_size: Option<HumanSize>,
92 /// Minimum payload size that triggers external cold-store staging instead
93 /// of inline hot-ring storage. `None` uses the default (1 MiB).
94 pub external_payload_min_size: Option<HumanSize>,
95 /// Max live-read waiters per core. `None` or `0` disables the limit.
96 pub live_read_max_waiters_per_core: Option<usize>,
97}
98
99impl Default for RuntimeConfig {
100 fn default() -> Self {
101 Self {
102 core_count: std::thread::available_parallelism()
103 .map(|n| n.get())
104 .unwrap_or(4),
105 node_memory_abort_cap_size: None,
106 external_payload_min_size: None,
107 live_read_max_waiters_per_core: Some(65_536),
108 }
109 }
110}
111
112/// Raft consensus and static-cluster networking configuration.
113#[derive(Debug, Clone, Deserialize, Serialize)]
114#[serde(default, deny_unknown_fields)]
115pub struct RaftConfig {
116 /// Unique node ID within the static gRPC Raft cluster.
117 /// Must be present in `peers` and must be non-zero.
118 pub node_id: u64,
119 /// Number of Raft groups (shards). Defaults to `core_count * 16`.
120 pub group_count: usize,
121 /// Per-group cap on raft-submitted-but-not-yet-applied payload bytes.
122 /// `None` or `0` disables the admission. Catches raft replication lag before
123 /// in-memory queues grow unbounded.
124 pub max_uncommitted_size_per_group: Option<HumanSize>,
125 /// Bootstrap the initial Raft membership once on startup.
126 pub init_membership: bool,
127 /// Bootstrap per-group Raft membership on startup.
128 pub init_membership_per_group: bool,
129 /// Raft WAL configuration.
130 pub wal: WalConfig,
131 /// Static gRPC Raft peers. Each entry maps a `node_id` to its gRPC URL.
132 pub peers: Vec<RaftPeerConfig>,
133 /// Optional per-group voter assignments.
134 ///
135 /// When empty (the default), every Raft group uses all peers as voters.
136 /// When supplied, every group in `0..group_count` must have an entry and
137 /// each entry's voters must be a non-empty subset of `peers`.
138 #[serde(default)]
139 pub groups: Vec<RaftGroupConfig>,
140 /// How long a restarting node waits to observe an already-established
141 /// (or freshly re-elected) leader before deciding the group is truly new
142 /// and bootstrapping it. Must exceed the election window.
143 pub rejoin_probe: HumanDuration,
144 /// Timeout for probing static peers during bootstrap before logging a
145 /// warning. Continues retrying indefinitely.
146 pub bootstrap_peer_probe: HumanDuration,
147 /// Interval between static-peer reachability probes during bootstrap.
148 pub bootstrap_peer_probe_interval: HumanDuration,
149 /// gRPC connect timeout when probing static peers.
150 pub bootstrap_peer_connect: HumanDuration,
151 /// OpenRaft's `install_snapshot_timeout` covers the whole FullSnapshot RPC.
152 /// The receiver downloads and installs the referenced object before
153 /// replying, so this must be comfortably above the S3 per-attempt timeout
154 /// plus retries.
155 pub install_snapshot_timeout: HumanDuration,
156 /// Directory for memory-bootstrap marker files. When set, each group
157 /// writes a marker after successful membership initialization. On restart,
158 /// a marked memory group rejoins an observed leader or reinitializes
159 /// volatile membership if no leader exists.
160 pub memory_bootstrap_marker_dir: Option<PathBuf>,
161 /// Consecutive gRPC RPC failures before forcing a transport reconnect.
162 pub grpc_reconnect_after_failures: usize,
163 /// Max concurrent snapshot builds across all groups on this node.
164 pub snapshot_build_max_concurrency: usize,
165 /// Max concurrent snapshot installs across all groups on this node.
166 pub snapshot_install_max_concurrency: usize,
167 /// Committed Raft log entries per group between automatic snapshots.
168 /// Larger values reduce full-state snapshot CPU and tail-latency spikes at
169 /// the cost of retaining more log entries for recovery.
170 pub snapshot_logs_since_last: u64,
171 /// Aggregate unpurged Raft log entries on one node that trigger a
172 /// pressure snapshot pass. This bounds memory-WAL growth when traffic is
173 /// spread across many groups and no individual group reaches
174 /// `snapshot_logs_since_last`.
175 pub snapshot_pressure_unpurged_logs: u64,
176 /// Maximum groups snapshotted by one pressure pass.
177 pub snapshot_pressure_max_groups_per_tick: usize,
178 /// Maximum number of payload-bearing Raft log entries retained per group
179 /// after they are covered by a snapshot.
180 pub max_in_snapshot_log_to_keep: u64,
181}
182
183impl Default for RaftConfig {
184 fn default() -> Self {
185 Self {
186 node_id: 0,
187 group_count: std::thread::available_parallelism()
188 .map(|n| n.get().saturating_mul(16).max(1))
189 .unwrap_or(16),
190 max_uncommitted_size_per_group: None,
191 init_membership: false,
192 init_membership_per_group: false,
193 wal: WalConfig::default(),
194 peers: Vec::new(),
195 groups: Vec::new(),
196 rejoin_probe: HumanDuration::sec(6),
197 bootstrap_peer_probe: HumanDuration::sec(60),
198 bootstrap_peer_probe_interval: HumanDuration::milli(250),
199 bootstrap_peer_connect: HumanDuration::milli(500),
200 install_snapshot_timeout: HumanDuration::sec(120),
201 memory_bootstrap_marker_dir: None,
202 grpc_reconnect_after_failures: 8,
203 snapshot_build_max_concurrency: 1,
204 snapshot_install_max_concurrency: 1,
205 snapshot_logs_since_last: 5_000,
206 snapshot_pressure_unpurged_logs: 65_536,
207 snapshot_pressure_max_groups_per_tick: 16,
208 max_in_snapshot_log_to_keep: 64,
209 }
210 }
211}
212
213/// Raft write-ahead log configuration.
214#[derive(Debug, Clone, Deserialize, Serialize)]
215#[serde(default, deny_unknown_fields)]
216pub struct WalConfig {
217 /// WAL persistence backend.
218 pub backend: WalBackend,
219 /// Directory for on-disk WAL files. Required when `backend` is `Disk`.
220 pub path: Option<PathBuf>,
221}
222
223impl WalConfig {
224 /// Resolved on-disk log directory for the Raft WAL.
225 ///
226 /// When `backend` is `Disk` and `path` is set, appends the legacy
227 /// `raft-log` subdirectory so that existing data directories continue
228 /// to work after the config refactor.
229 pub fn resolved_path(&self) -> Option<PathBuf> {
230 match self.backend {
231 WalBackend::Memory => None,
232 WalBackend::Disk => self.path.as_ref().map(|p| p.join("raft-log")),
233 }
234 }
235}
236
237impl Default for WalConfig {
238 fn default() -> Self {
239 Self {
240 backend: WalBackend::Memory,
241 path: None,
242 }
243 }
244}
245
246/// A single static gRPC Raft peer.
247#[derive(Debug, Clone, Deserialize, Serialize)]
248#[serde(deny_unknown_fields)]
249pub struct RaftPeerConfig {
250 /// Peer node ID.
251 pub node_id: u64,
252 /// Peer gRPC URL.
253 pub url: String,
254}
255
256/// Per-group voter assignment for heterogeneous static clusters.
257///
258/// When omitted (the default), every group uses all peers as voters.
259#[derive(Debug, Clone, Deserialize, Serialize)]
260#[serde(deny_unknown_fields)]
261pub struct RaftGroupConfig {
262 /// Raft group ID.
263 pub raft_group_id: u32,
264 /// Node IDs that are voters for this group.
265 pub voters: Vec<u64>,
266}
267
268/// Storage tier configuration.
269#[derive(Debug, Clone, Default, Deserialize, Serialize)]
270#[serde(default, deny_unknown_fields)]
271pub struct StorageConfig {
272 /// Cold-tier (opendal-backed object store) configuration.
273 pub cold: ColdConfig,
274 /// Raft snapshot store configuration.
275 pub snapshot: RaftSnapshotConfig,
276}
277
278/// Cold-tier flush, GC, and cache configuration.
279#[derive(Debug, Clone, Deserialize, Serialize)]
280#[serde(default, deny_unknown_fields)]
281pub struct ColdConfig {
282 /// Cold-storage backend.
283 pub backend: ColdBackend,
284 /// Root prefix for cold-storage objects (e.g. S3 prefix or local dir).
285 pub root: Option<String>,
286 /// S3-specific connection and credential settings.
287 /// Required when `backend` is `S3`.
288 pub s3: Option<S3Config>,
289 /// Optional cold-read cache.
290 pub cache: Option<ColdCacheConfig>,
291 /// Interval between periodic cold-flush passes. Must be non-zero.
292 pub flush_interval: HumanDuration,
293 /// Target number of hot bytes to flush per group per pass.
294 pub flush_size: HumanSize,
295 /// Minimum hot bytes a group must have before it is eligible for flush.
296 /// Falls back to [`flush_size`](Self::flush_size) when unset.
297 pub flush_min_hot_size: Option<HumanSize>,
298 /// Aggregate hot bytes across locally led groups that activate a pressure
299 /// flush. A pressure pass allows undersized groups to flush without
300 /// changing their independent state ownership. `0` disables the fallback.
301 pub flush_pressure_hot_size: HumanSize,
302 /// Upper bound on bytes flushed per group per pass.
303 /// Falls back to [`flush_size`](Self::flush_size) when unset.
304 pub flush_max_size: Option<HumanSize>,
305 /// Max groups flushed concurrently.
306 pub flush_max_concurrency: usize,
307 /// Enable background same-stream cold chunk compaction.
308 pub compaction_enabled: bool,
309 /// Interval between cold chunk compaction discovery passes.
310 pub compaction_interval: HumanDuration,
311 /// Preferred compacted object size.
312 pub compaction_target_size: HumanSize,
313 /// Hard maximum compacted object size.
314 pub compaction_max_size: HumanSize,
315 /// Maximum streams compacted per pass.
316 pub compaction_max_streams_per_pass: usize,
317 /// Grace period before compacted input objects are physically deleted.
318 pub compaction_gc_grace: HumanDuration,
319 /// Per-group hot-size cap. When a group's hot bytes exceed this, new
320 /// writes are rejected with HTTP 503. `None` or `0` disables the admission.
321 pub max_hot_size_per_group: Option<HumanSize>,
322 /// Interval between periodic cold-gc passes. Must be non-zero.
323 pub gc_interval: HumanDuration,
324 /// Max GC entries to process per group per pass.
325 pub gc_max_entries: usize,
326}
327
328impl ColdConfig {
329 /// Minimum hot bytes a group must have before it is eligible for flush.
330 ///
331 /// Falls back to [`flush_size`](Self::flush_size) when the user does not
332 /// supply an explicit value.
333 pub fn flush_min_hot_size(&self) -> HumanSize {
334 self.flush_min_hot_size.unwrap_or(self.flush_size)
335 }
336
337 /// Upper bound on bytes flushed per group per pass.
338 ///
339 /// Falls back to [`flush_size`](Self::flush_size) when the user does not
340 /// supply an explicit value.
341 pub fn flush_max_size(&self) -> HumanSize {
342 self.flush_max_size.unwrap_or(self.flush_size)
343 }
344}
345
346impl Default for ColdConfig {
347 fn default() -> Self {
348 Self {
349 backend: ColdBackend::None,
350 root: None,
351 s3: None,
352 cache: None,
353 flush_interval: HumanDuration::sec(1),
354 flush_size: HumanSize::mib(8),
355 flush_min_hot_size: None,
356 flush_pressure_hot_size: HumanSize::mib(128),
357 flush_max_size: None,
358 flush_max_concurrency: 4,
359 compaction_enabled: false,
360 compaction_interval: HumanDuration::sec(30),
361 compaction_target_size: HumanSize::mib(8),
362 compaction_max_size: HumanSize::mib(16),
363 compaction_max_streams_per_pass: 16,
364 compaction_gc_grace: HumanDuration::sec(300),
365 max_hot_size_per_group: Some(HumanSize::mib(64)),
366 gc_interval: HumanDuration::sec(5),
367 gc_max_entries: 256,
368 }
369 }
370}
371
372/// S3 connection and credential settings.
373#[derive(Debug, Clone, Deserialize, Serialize)]
374#[serde(default, deny_unknown_fields)]
375pub struct S3Config {
376 /// S3 bucket name.
377 pub bucket: Option<String>,
378 /// S3 region.
379 pub region: Option<String>,
380 /// Custom S3 endpoint (for MinIO, etc.).
381 pub endpoint: Option<String>,
382 /// S3 access key ID.
383 pub access_key_id: Option<String>,
384 /// S3 secret access key.
385 pub secret_access_key: Option<String>,
386 /// Optional S3 session token.
387 pub session_token: Option<String>,
388 /// Server-side encryption requested on every cold-tier object write.
389 ///
390 /// Defaults to `aes256` (SSE-S3): free on AWS S3 and the baseline for any
391 /// shared deployment. MinIO only honors it when a KMS/KES is configured —
392 /// set `none` explicitly for a MinIO deployment without one. `aws-kms`
393 /// uses the AWS-managed KMS key unless `kms_key_id` names a customer
394 /// managed key.
395 pub server_side_encryption: S3ServerSideEncryption,
396 /// Customer managed KMS key for `server_side_encryption = "aws-kms"`.
397 pub kms_key_id: Option<String>,
398 /// Per-S3-operation timeout.
399 pub timeout: HumanDuration,
400 /// Max retries per S3 operation.
401 pub max_retries: usize,
402 /// Timeout for S3 health probes.
403 pub probe_timeout: HumanDuration,
404 /// Consecutive probe failures before marking S3 unhealthy.
405 pub unhealthy_ticks: usize,
406 /// Consecutive probe successes before marking S3 healthy again.
407 pub heal_ticks: usize,
408}
409
410impl Default for S3Config {
411 fn default() -> Self {
412 Self {
413 bucket: None,
414 region: None,
415 endpoint: None,
416 access_key_id: None,
417 secret_access_key: None,
418 session_token: None,
419 server_side_encryption: S3ServerSideEncryption::default(),
420 kms_key_id: None,
421 timeout: HumanDuration::sec(10),
422 max_retries: 3,
423 probe_timeout: HumanDuration::sec(2),
424 unhealthy_ticks: 1,
425 heal_ticks: 2,
426 }
427 }
428}
429
430/// Server-side encryption mode for cold-tier S3 writes.
431#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
432#[serde(rename_all = "kebab-case")]
433pub enum S3ServerSideEncryption {
434 /// SSE-S3 (`x-amz-server-side-encryption: AES256`). The default.
435 #[default]
436 #[serde(rename = "aes256")]
437 Aes256,
438 /// SSE-KMS; uses the AWS managed key unless `kms_key_id` is set.
439 AwsKms,
440 /// No server-side encryption header. Required for object stores that
441 /// reject the header (for example MinIO without a configured KMS).
442 None,
443}
444
445/// Cold-read cache sizing.
446#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
447#[serde(default, deny_unknown_fields)]
448pub struct ColdCacheConfig {
449 /// Max cache size in bytes.
450 pub max_size: HumanSize,
451 /// Cache block size in bytes.
452 pub block_size: HumanSize,
453 /// Number of blocks to read ahead on cache miss.
454 pub readahead_blocks: usize,
455}
456
457impl Default for ColdCacheConfig {
458 fn default() -> Self {
459 Self {
460 max_size: HumanSize::mib(256),
461 block_size: HumanSize::mib(1),
462 readahead_blocks: 4,
463 }
464 }
465}
466
467/// Raft snapshot store configuration.
468#[derive(Debug, Clone, Deserialize, Serialize)]
469#[serde(default, deny_unknown_fields)]
470pub struct RaftSnapshotConfig {
471 /// Snapshot store backend.
472 pub backend: RaftSnapshotBackend,
473 /// S3 namespace for snapshot objects, relative to the cold-storage root.
474 /// Used only when `backend` is `S3`.
475 pub s3_prefix: Option<String>,
476 /// Interval for the manual snapshot driver.
477 ///
478 /// When omitted, inline snapshot stores keep the manual driver disabled and
479 /// external snapshot stores use a 5s manual-driver default. Explicit `0s`
480 /// disables the manual driver and keeps openraft's default auto-policy.
481 pub drive_interval: Option<HumanDuration>,
482 /// Retained for configuration compatibility. Snapshot driving no longer
483 /// forces cold flushes; the cold worker owns flush concurrency.
484 pub drive_flush_concurrency: usize,
485}
486
487impl Default for RaftSnapshotConfig {
488 fn default() -> Self {
489 Self {
490 backend: RaftSnapshotBackend::Inline,
491 s3_prefix: None,
492 drive_interval: None,
493 drive_flush_concurrency: 4,
494 }
495 }
496}
497
498/// Cluster governance and health-gate configuration.
499#[derive(Debug, Clone, Default, Deserialize, Serialize)]
500#[serde(default, deny_unknown_fields)]
501pub struct GovernanceConfig {
502 /// Leadership balancing configuration.
503 pub leadership_balance: LeadershipBalanceConfig,
504 /// Cluster egress probe configuration.
505 pub cluster_probe: ClusterProbeConfig,
506 /// Commit-stall watchdog configuration.
507 pub commit_stall: CommitStallConfig,
508 /// Cold-storage health gate configuration.
509 pub cold_health: ColdHealthConfig,
510}
511
512/// Leadership balancer tuning.
513#[derive(Debug, Clone, Deserialize, Serialize)]
514#[serde(default, deny_unknown_fields)]
515pub struct LeadershipBalanceConfig {
516 /// Tick interval for the leadership balancer.
517 pub interval: HumanDuration,
518 /// Max leader handoffs to attempt per tick.
519 pub max_per_tick: usize,
520 /// Timeout when querying peer shed state.
521 pub peer_timeout: HumanDuration,
522}
523
524impl Default for LeadershipBalanceConfig {
525 fn default() -> Self {
526 Self {
527 interval: HumanDuration::sec(5),
528 max_per_tick: 4,
529 peer_timeout: HumanDuration::milli(500),
530 }
531 }
532}
533
534/// Cluster egress probe tuning.
535#[derive(Debug, Clone, Deserialize, Serialize)]
536#[serde(default, deny_unknown_fields)]
537pub struct ClusterProbeConfig {
538 /// Tick interval for egress probes.
539 pub interval: HumanDuration,
540 /// Payload size for egress probe messages.
541 pub probe_size: HumanSize,
542 /// Timeout for individual egress probes.
543 pub timeout: HumanDuration,
544 /// Consecutive failed ticks before marking egress unhealthy.
545 pub unhealthy_ticks: usize,
546 /// Consecutive healthy ticks before clearing egress unhealthy.
547 pub heal_ticks: usize,
548}
549
550impl Default for ClusterProbeConfig {
551 fn default() -> Self {
552 Self {
553 interval: HumanDuration::milli(500),
554 probe_size: HumanSize::kib(64),
555 timeout: HumanDuration::milli(200),
556 unhealthy_ticks: 2,
557 heal_ticks: 6,
558 }
559 }
560}
561
562/// Commit-stall watchdog tuning.
563#[derive(Debug, Clone, Deserialize, Serialize)]
564#[serde(default, deny_unknown_fields)]
565pub struct CommitStallConfig {
566 /// Tick interval for the commit-stall watchdog.
567 pub interval: HumanDuration,
568 /// Duration a group must be stalled (`last_log_index > committed_index`)
569 /// before triggering a leader transfer.
570 pub threshold: HumanDuration,
571}
572
573impl Default for CommitStallConfig {
574 fn default() -> Self {
575 Self {
576 interval: HumanDuration::sec(2),
577 threshold: HumanDuration::sec(15),
578 }
579 }
580}
581
582/// Cold-storage health gate tuning.
583#[derive(Debug, Clone, Deserialize, Serialize)]
584#[serde(default, deny_unknown_fields)]
585pub struct ColdHealthConfig {
586 /// Tick interval for the cold-health gate.
587 pub interval: HumanDuration,
588 /// Consecutive unhealthy ticks before shedding leadership.
589 pub unhealthy_ticks: usize,
590 /// Consecutive healthy ticks before re-allowing leadership.
591 pub heal_ticks: usize,
592 /// High watermark for per-group hot bytes. Exceeding this contributes to
593 /// unhealthy.
594 pub hot_size_high: HumanSize,
595 /// Low watermark for per-group hot bytes. Dropping below this contributes
596 /// to healthy.
597 pub hot_size_low: HumanSize,
598 /// Error-count threshold per tick that marks cold as unhealthy.
599 pub errors_per_tick_high: usize,
600}
601
602impl Default for ColdHealthConfig {
603 fn default() -> Self {
604 Self {
605 interval: HumanDuration::sec(2),
606 unhealthy_ticks: 3,
607 heal_ticks: 5,
608 // Leave the normal 8 MiB cold-flush threshold enough room to run.
609 // The old 7 MiB watermark forced leadership shedding before a
610 // group became eligible for its first flush.
611 hot_size_high: HumanSize::mib(48),
612 hot_size_low: HumanSize::mib(32),
613 errors_per_tick_high: 1,
614 }
615 }
616}
617
618/// Observability and debugging features.
619#[derive(Debug, Clone, Default, Deserialize, Serialize)]
620#[serde(default, deny_unknown_fields)]
621pub struct ObservabilityConfig {
622 /// Enable tokio-console integration.
623 pub tokio_console: bool,
624}