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 InvalidRunTimeBounds {
213 started_at_unix_ms: u64,
215 finished_at_unix_ms: u64,
217 },
218 InvalidFinalizationTime {
220 finished_at_unix_ms: u64,
222 finalized_at_unix_ms: u64,
224 },
225}
226
227impl std::fmt::Display for BuildError {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 match self {
230 Self::EmptyServiceName => write!(f, "service_name cannot be empty"),
231 Self::InvalidRunTimeBounds {
232 started_at_unix_ms,
233 finished_at_unix_ms,
234 } => write!(
235 f,
236 "invalid run timestamps: finished_at_unix_ms ({finished_at_unix_ms}) must be >= started_at_unix_ms ({started_at_unix_ms})"
237 ),
238 Self::InvalidFinalizationTime {
239 finished_at_unix_ms,
240 finalized_at_unix_ms,
241 } => write!(
242 f,
243 "invalid finalization timestamp: finalized_at_unix_ms ({finalized_at_unix_ms}) must be >= finished_at_unix_ms ({finished_at_unix_ms})"
244 ),
245 }
246 }
247}
248
249impl std::error::Error for BuildError {}
250
251#[derive(Clone)]
253pub struct TailtriageBuilder {
254 pub(crate) service_name: String,
255 pub(crate) service_version: Option<String>,
256 pub(crate) run_id: Option<String>,
257 pub(crate) mode: CaptureMode,
258 pub(crate) sink: Arc<dyn RunSink + Send + Sync>,
259 pub(crate) capture_limits: Option<CaptureLimits>,
260 pub(crate) capture_limits_override: CaptureLimitsOverride,
261 pub(crate) strict_lifecycle: bool,
262}
263
264impl TailtriageBuilder {
265 pub(crate) fn new(service_name: impl Into<String>) -> Self {
266 Self {
267 service_name: service_name.into(),
268 service_version: None,
269 run_id: None,
270 mode: CaptureMode::Light,
271 sink: Arc::new(LocalJsonSink::new("tailtriage-run.json")),
272 capture_limits: None,
273 capture_limits_override: CaptureLimitsOverride::default(),
274 strict_lifecycle: false,
275 }
276 }
277
278 #[must_use]
282 pub fn light(mut self) -> Self {
283 self.mode = CaptureMode::Light;
284 self
285 }
286
287 #[must_use]
291 pub fn investigation(mut self) -> Self {
292 self.mode = CaptureMode::Investigation;
293 self
294 }
295
296 #[must_use]
300 pub fn output(mut self, output_path: impl AsRef<Path>) -> Self {
301 self.sink = Arc::new(LocalJsonSink::new(output_path));
302 self
303 }
304
305 #[must_use]
307 pub fn sink<S>(mut self, sink: S) -> Self
308 where
309 S: RunSink + Send + Sync + 'static,
310 {
311 self.sink = Arc::new(sink);
312 self
313 }
314
315 #[must_use]
317 pub fn service_version(mut self, service_version: impl Into<String>) -> Self {
318 self.service_version = Some(service_version.into());
319 self
320 }
321
322 #[must_use]
326 pub fn run_id(mut self, run_id: impl Into<String>) -> Self {
327 self.run_id = Some(run_id.into());
328 self
329 }
330
331 #[must_use]
333 pub fn capture_limits(mut self, limits: CaptureLimits) -> Self {
334 self.capture_limits = Some(limits);
335 self
336 }
337
338 #[must_use]
344 pub fn capture_limits_override(mut self, overrides: CaptureLimitsOverride) -> Self {
345 self.capture_limits_override = self.capture_limits_override.merge(overrides);
346 self
347 }
348
349 #[must_use]
354 pub fn strict_lifecycle(mut self, strict_lifecycle: bool) -> Self {
355 self.strict_lifecycle = strict_lifecycle;
356 self
357 }
358
359 pub fn build(self) -> Result<crate::Tailtriage, BuildError> {
365 crate::Tailtriage::from_config(Config::from_builder(&self))
366 }
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, Default)]
376pub struct RequestOptions {
377 pub request_id: Option<String>,
386 pub kind: Option<String>,
388}
389
390impl RequestOptions {
391 #[must_use]
393 pub fn new() -> Self {
394 Self::default()
395 }
396
397 #[must_use]
403 pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
404 self.request_id = Some(request_id.into());
405 self
406 }
407
408 #[must_use]
410 pub fn kind(mut self, kind: impl Into<String>) -> Self {
411 self.kind = Some(kind.into());
412 self
413 }
414}