Skip to main content

tailtriage_core/
config.rs

1use serde::{Deserialize, Serialize};
2use std::path::Path;
3use std::sync::Arc;
4
5use crate::{LocalJsonSink, RunSink};
6
7/// Capture mode used during a run.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum CaptureMode {
11    /// Low-overhead mode.
12    Light,
13    /// Higher-detail mode for incident investigation.
14    Investigation,
15}
16
17/// Limits that bound in-memory capture growth for each run section.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct CaptureLimits {
20    /// Maximum number of request events retained in-memory for the run.
21    pub max_requests: usize,
22    /// Maximum number of stage events retained in-memory for the run.
23    pub max_stages: usize,
24    /// Maximum number of queue events retained in-memory for the run.
25    pub max_queues: usize,
26    /// Maximum number of in-flight snapshots retained in-memory for the run.
27    pub max_inflight_snapshots: usize,
28    /// Maximum number of runtime snapshots retained in-memory for the run.
29    pub max_runtime_snapshots: usize,
30}
31
32impl Default for CaptureLimits {
33    fn default() -> Self {
34        Self {
35            max_requests: 100_000,
36            max_stages: 200_000,
37            max_queues: 200_000,
38            max_inflight_snapshots: 200_000,
39            max_runtime_snapshots: 100_000,
40        }
41    }
42}
43
44#[derive(Clone)]
45pub(crate) struct Config {
46    pub service_name: String,
47    pub service_version: Option<String>,
48    pub run_id: Option<String>,
49    pub mode: CaptureMode,
50    pub sink: Arc<dyn RunSink + Send + Sync>,
51    pub capture_limits: CaptureLimits,
52    pub strict_lifecycle: bool,
53}
54
55impl Config {
56    pub(crate) fn from_builder(builder: &TailtriageBuilder) -> Self {
57        Self {
58            service_name: builder.service_name.clone(),
59            service_version: builder.service_version.clone(),
60            run_id: builder.run_id.clone(),
61            mode: builder.mode,
62            sink: Arc::clone(&builder.sink),
63            capture_limits: builder.capture_limits,
64            strict_lifecycle: builder.strict_lifecycle,
65        }
66    }
67}
68
69/// Errors emitted while building one tailtriage capture instance.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum BuildError {
72    /// Service name was empty.
73    EmptyServiceName,
74}
75
76impl std::fmt::Display for BuildError {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            Self::EmptyServiceName => write!(f, "service_name cannot be empty"),
80        }
81    }
82}
83
84impl std::error::Error for BuildError {}
85
86/// Builder for constructing a [`crate::Tailtriage`] run.
87#[derive(Clone)]
88pub struct TailtriageBuilder {
89    pub(crate) service_name: String,
90    pub(crate) service_version: Option<String>,
91    pub(crate) run_id: Option<String>,
92    pub(crate) mode: CaptureMode,
93    pub(crate) sink: Arc<dyn RunSink + Send + Sync>,
94    pub(crate) capture_limits: CaptureLimits,
95    pub(crate) strict_lifecycle: bool,
96}
97
98impl TailtriageBuilder {
99    pub(crate) fn new(service_name: impl Into<String>) -> Self {
100        Self {
101            service_name: service_name.into(),
102            service_version: None,
103            run_id: None,
104            mode: CaptureMode::Light,
105            sink: Arc::new(LocalJsonSink::new("tailtriage-run.json")),
106            capture_limits: CaptureLimits::default(),
107            strict_lifecycle: false,
108        }
109    }
110
111    /// Sets capture mode to [`CaptureMode::Light`].
112    ///
113    /// Light mode is the default and favors low overhead with enough signal for
114    /// first-pass triage.
115    #[must_use]
116    pub fn light(mut self) -> Self {
117        self.mode = CaptureMode::Light;
118        self
119    }
120
121    /// Sets capture mode to [`CaptureMode::Investigation`].
122    ///
123    /// Use this mode when you need more detailed evidence during an incident.
124    #[must_use]
125    pub fn investigation(mut self) -> Self {
126        self.mode = CaptureMode::Investigation;
127        self
128    }
129
130    /// Writes run output to a local JSON file sink at `output_path`.
131    ///
132    /// The default output path is `tailtriage-run.json`.
133    #[must_use]
134    pub fn output(mut self, output_path: impl AsRef<Path>) -> Self {
135        self.sink = Arc::new(LocalJsonSink::new(output_path));
136        self
137    }
138
139    /// Uses a custom run sink implementation.
140    #[must_use]
141    pub fn sink<S>(mut self, sink: S) -> Self
142    where
143        S: RunSink + Send + Sync + 'static,
144    {
145        self.sink = Arc::new(sink);
146        self
147    }
148
149    /// Sets an optional service version recorded in run metadata.
150    #[must_use]
151    pub fn service_version(mut self, service_version: impl Into<String>) -> Self {
152        self.service_version = Some(service_version.into());
153        self
154    }
155
156    /// Sets an explicit run identifier for metadata.
157    ///
158    /// If not set, `tailtriage` generates a run ID automatically.
159    #[must_use]
160    pub fn run_id(mut self, run_id: impl Into<String>) -> Self {
161        self.run_id = Some(run_id.into());
162        self
163    }
164
165    /// Overrides default capture limits for bounded in-memory collection.
166    #[must_use]
167    pub fn capture_limits(mut self, limits: CaptureLimits) -> Self {
168        self.capture_limits = limits;
169        self
170    }
171
172    /// Enables strict lifecycle validation on shutdown.
173    ///
174    /// When enabled, [`crate::Tailtriage::shutdown`] returns an error if unfinished
175    /// requests remain pending.
176    #[must_use]
177    pub fn strict_lifecycle(mut self, strict_lifecycle: bool) -> Self {
178        self.strict_lifecycle = strict_lifecycle;
179        self
180    }
181
182    /// Builds one [`crate::Tailtriage`] collector for the configured service.
183    ///
184    /// # Errors
185    ///
186    /// Returns [`BuildError::EmptyServiceName`] when the configured service name is blank.
187    pub fn build(self) -> Result<crate::Tailtriage, BuildError> {
188        crate::Tailtriage::from_config(Config::from_builder(&self))
189    }
190}
191
192/// Optional request start settings used by [`crate::Tailtriage::begin_request_with`].
193///
194/// When `request_id` is not provided, a request ID is generated automatically.
195#[derive(Debug, Clone, PartialEq, Eq, Default)]
196pub struct RequestOptions {
197    /// Optional caller-provided request ID used for request correlation.
198    pub request_id: Option<String>,
199    /// Optional semantic request kind (for example `http` or `job`).
200    pub kind: Option<String>,
201}
202
203impl RequestOptions {
204    /// Creates default request options with autogenerated request IDs.
205    #[must_use]
206    pub fn new() -> Self {
207        Self::default()
208    }
209
210    /// Sets an explicit request ID for the next request context.
211    #[must_use]
212    pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
213        self.request_id = Some(request_id.into());
214        self
215    }
216
217    /// Sets an optional semantic kind recorded on completion.
218    #[must_use]
219    pub fn kind(mut self, kind: impl Into<String>) -> Self {
220        self.kind = Some(kind.into());
221        self
222    }
223}