1use serde::{Deserialize, Serialize};
2use std::path::Path;
3use std::sync::Arc;
4
5use crate::{LocalJsonSink, RunSink};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum CaptureMode {
11 Light,
21 Investigation,
31}
32
33impl CaptureMode {
34 #[must_use]
40 pub const fn core_defaults(self) -> CaptureLimits {
41 match self {
42 Self::Light => CaptureLimits {
43 max_requests: 100_000,
44 max_stages: 200_000,
45 max_queues: 200_000,
46 max_inflight_snapshots: 200_000,
47 max_runtime_snapshots: 100_000,
50 },
51 Self::Investigation => CaptureLimits {
52 max_requests: 300_000,
53 max_stages: 600_000,
54 max_queues: 600_000,
55 max_inflight_snapshots: 600_000,
56 max_runtime_snapshots: 300_000,
57 },
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68pub struct CaptureLimits {
69 pub max_requests: usize,
71 pub max_stages: usize,
73 pub max_queues: usize,
75 pub max_inflight_snapshots: usize,
77 pub max_runtime_snapshots: usize,
79}
80
81impl Default for CaptureLimits {
82 fn default() -> Self {
83 CaptureMode::Light.core_defaults()
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
92pub struct CaptureLimitsOverride {
93 pub max_requests: Option<usize>,
95 pub max_stages: Option<usize>,
97 pub max_queues: Option<usize>,
99 pub max_inflight_snapshots: Option<usize>,
101 pub max_runtime_snapshots: Option<usize>,
103}
104
105impl CaptureLimitsOverride {
106 #[must_use]
108 pub const fn apply(self, base: CaptureLimits) -> CaptureLimits {
109 CaptureLimits {
110 max_requests: match self.max_requests {
111 Some(value) => value,
112 None => base.max_requests,
113 },
114 max_stages: match self.max_stages {
115 Some(value) => value,
116 None => base.max_stages,
117 },
118 max_queues: match self.max_queues {
119 Some(value) => value,
120 None => base.max_queues,
121 },
122 max_inflight_snapshots: match self.max_inflight_snapshots {
123 Some(value) => value,
124 None => base.max_inflight_snapshots,
125 },
126 max_runtime_snapshots: match self.max_runtime_snapshots {
127 Some(value) => value,
128 None => base.max_runtime_snapshots,
129 },
130 }
131 }
132
133 const fn merge(self, newer: Self) -> Self {
134 Self {
135 max_requests: match newer.max_requests {
136 Some(value) => Some(value),
137 None => self.max_requests,
138 },
139 max_stages: match newer.max_stages {
140 Some(value) => Some(value),
141 None => self.max_stages,
142 },
143 max_queues: match newer.max_queues {
144 Some(value) => Some(value),
145 None => self.max_queues,
146 },
147 max_inflight_snapshots: match newer.max_inflight_snapshots {
148 Some(value) => Some(value),
149 None => self.max_inflight_snapshots,
150 },
151 max_runtime_snapshots: match newer.max_runtime_snapshots {
152 Some(value) => Some(value),
153 None => self.max_runtime_snapshots,
154 },
155 }
156 }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
161pub struct EffectiveCoreConfig {
162 pub mode: CaptureMode,
164 pub capture_limits: CaptureLimits,
166 pub strict_lifecycle: bool,
168}
169
170#[derive(Clone)]
171pub(crate) struct Config {
172 pub service_name: String,
173 pub service_version: Option<String>,
174 pub run_id: Option<String>,
175 pub mode: CaptureMode,
176 pub sink: Arc<dyn RunSink + Send + Sync>,
177 pub effective_core: EffectiveCoreConfig,
178 pub strict_lifecycle: bool,
179}
180
181impl Config {
182 pub(crate) fn from_builder(builder: &TailtriageBuilder) -> Self {
183 let mode_defaults = builder.mode.core_defaults();
184 let effective_limits = match builder.capture_limits {
185 Some(full_override) => full_override,
186 None => builder.capture_limits_override.apply(mode_defaults),
187 };
188 let effective_core = EffectiveCoreConfig {
189 mode: builder.mode,
190 capture_limits: effective_limits,
191 strict_lifecycle: builder.strict_lifecycle,
192 };
193
194 Self {
195 service_name: builder.service_name.clone(),
196 service_version: builder.service_version.clone(),
197 run_id: builder.run_id.clone(),
198 mode: builder.mode,
199 sink: Arc::clone(&builder.sink),
200 effective_core,
201 strict_lifecycle: builder.strict_lifecycle,
202 }
203 }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
208pub enum BuildError {
209 EmptyServiceName,
211}
212
213impl std::fmt::Display for BuildError {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 match self {
216 Self::EmptyServiceName => write!(f, "service_name cannot be empty"),
217 }
218 }
219}
220
221impl std::error::Error for BuildError {}
222
223#[derive(Clone)]
225pub struct TailtriageBuilder {
226 pub(crate) service_name: String,
227 pub(crate) service_version: Option<String>,
228 pub(crate) run_id: Option<String>,
229 pub(crate) mode: CaptureMode,
230 pub(crate) sink: Arc<dyn RunSink + Send + Sync>,
231 pub(crate) capture_limits: Option<CaptureLimits>,
232 pub(crate) capture_limits_override: CaptureLimitsOverride,
233 pub(crate) strict_lifecycle: bool,
234}
235
236impl TailtriageBuilder {
237 pub(crate) fn new(service_name: impl Into<String>) -> Self {
238 Self {
239 service_name: service_name.into(),
240 service_version: None,
241 run_id: None,
242 mode: CaptureMode::Light,
243 sink: Arc::new(LocalJsonSink::new("tailtriage-run.json")),
244 capture_limits: None,
245 capture_limits_override: CaptureLimitsOverride::default(),
246 strict_lifecycle: false,
247 }
248 }
249
250 #[must_use]
254 pub fn light(mut self) -> Self {
255 self.mode = CaptureMode::Light;
256 self
257 }
258
259 #[must_use]
263 pub fn investigation(mut self) -> Self {
264 self.mode = CaptureMode::Investigation;
265 self
266 }
267
268 #[must_use]
272 pub fn output(mut self, output_path: impl AsRef<Path>) -> Self {
273 self.sink = Arc::new(LocalJsonSink::new(output_path));
274 self
275 }
276
277 #[must_use]
279 pub fn sink<S>(mut self, sink: S) -> Self
280 where
281 S: RunSink + Send + Sync + 'static,
282 {
283 self.sink = Arc::new(sink);
284 self
285 }
286
287 #[must_use]
289 pub fn service_version(mut self, service_version: impl Into<String>) -> Self {
290 self.service_version = Some(service_version.into());
291 self
292 }
293
294 #[must_use]
298 pub fn run_id(mut self, run_id: impl Into<String>) -> Self {
299 self.run_id = Some(run_id.into());
300 self
301 }
302
303 #[must_use]
305 pub fn capture_limits(mut self, limits: CaptureLimits) -> Self {
306 self.capture_limits = Some(limits);
307 self
308 }
309
310 #[must_use]
316 pub fn capture_limits_override(mut self, overrides: CaptureLimitsOverride) -> Self {
317 self.capture_limits_override = self.capture_limits_override.merge(overrides);
318 self
319 }
320
321 #[must_use]
326 pub fn strict_lifecycle(mut self, strict_lifecycle: bool) -> Self {
327 self.strict_lifecycle = strict_lifecycle;
328 self
329 }
330
331 pub fn build(self) -> Result<crate::Tailtriage, BuildError> {
337 crate::Tailtriage::from_config(Config::from_builder(&self))
338 }
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, Default)]
348pub struct RequestOptions {
349 pub request_id: Option<String>,
351 pub kind: Option<String>,
353}
354
355impl RequestOptions {
356 #[must_use]
358 pub fn new() -> Self {
359 Self::default()
360 }
361
362 #[must_use]
364 pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
365 self.request_id = Some(request_id.into());
366 self
367 }
368
369 #[must_use]
371 pub fn kind(mut self, kind: impl Into<String>) -> Self {
372 self.kind = Some(kind.into());
373 self
374 }
375}