1use std::time::Instant;
7use tracing::{info_span, Span};
8
9pub struct CliInitSpanBuilder {
11 project_path: String,
12 exists_before: bool,
13 force_used: bool,
14}
15
16impl CliInitSpanBuilder {
17 pub fn new(project_path: String, exists_before: bool, force_used: bool) -> Self {
18 Self {
19 project_path,
20 exists_before,
21 force_used,
22 }
23 }
24
25 pub fn start(self) -> CliInitSpan {
26 let span = info_span!(
27 "clnrm.cli.init",
28 cli.command = "init",
29 cli.version = env!("CARGO_PKG_VERSION"),
30 project.path = %self.project_path,
31 project.exists_before = self.exists_before,
32 force.used = self.force_used,
33 );
34
35 CliInitSpan {
36 span,
37 start_time: Instant::now(),
38 }
39 }
40}
41
42pub struct CliInitSpan {
43 span: Span,
44 start_time: Instant,
45}
46
47impl CliInitSpan {
48 pub fn finish(
49 self,
50 success: bool,
51 config_generated: bool,
52 config_path: Option<String>,
53 files_created: usize,
54 error: Option<(String, String)>,
55 ) {
56 let duration_ms = self.start_time.elapsed().as_secs_f64() * 1000.0;
57
58 let _enter = self.span.enter();
59
60 self.span.record("operation.success", success);
62 self.span.record("config.generated", config_generated);
63 self.span.record("operation.duration_ms", duration_ms);
64
65 if let Some(path) = config_path {
67 self.span.record("config.path", path.as_str());
68 }
69 self.span.record("files.created", files_created as i64);
70
71 if let Some((error_type, error_message)) = error {
73 self.span.record("error.type", error_type.as_str());
74 self.span.record("error.message", error_message.as_str());
75 }
76 }
77}
78
79pub struct CliPluginsSpanBuilder;
81
82impl Default for CliPluginsSpanBuilder {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88impl CliPluginsSpanBuilder {
89 pub fn new() -> Self {
90 Self
91 }
92
93 pub fn start(self) -> CliPluginsSpan {
94 let span = info_span!(
95 "clnrm.cli.plugins",
96 cli.command = "plugins",
97 cli.version = env!("CARGO_PKG_VERSION"),
98 );
99
100 CliPluginsSpan {
101 span,
102 start_time: Instant::now(),
103 }
104 }
105}
106
107pub struct CliPluginsSpan {
108 span: Span,
109 start_time: Instant,
110}
111
112impl CliPluginsSpan {
113 pub fn finish(
114 self,
115 success: bool,
116 plugins_discovered: usize,
117 plugins_builtin: usize,
118 plugins_custom: usize,
119 plugins_by_type: Option<String>,
120 error: Option<(String, String)>,
121 ) {
122 let duration_ms = self.start_time.elapsed().as_secs_f64() * 1000.0;
123
124 let _enter = self.span.enter();
125
126 self.span.record("operation.success", success);
128 self.span
129 .record("plugins.discovered", plugins_discovered as i64);
130 self.span.record("operation.duration_ms", duration_ms);
131
132 self.span.record("plugins.builtin", plugins_builtin as i64);
134 self.span.record("plugins.custom", plugins_custom as i64);
135
136 if let Some(json) = plugins_by_type {
137 self.span.record("plugins.by_type", json.as_str());
138 }
139
140 if let Some((error_type, error_message)) = error {
142 self.span.record("error.type", error_type.as_str());
143 self.span.record("error.message", error_message.as_str());
144 }
145 }
146}
147
148pub struct CliHealthSpanBuilder {
150 verbose: bool,
151}
152
153impl CliHealthSpanBuilder {
154 pub fn new(verbose: bool) -> Self {
155 Self { verbose }
156 }
157
158 pub fn start(self) -> CliHealthSpan {
159 let span = info_span!(
160 "clnrm.cli.health",
161 cli.command = "health",
162 cli.version = env!("CARGO_PKG_VERSION"),
163 verbose.enabled = self.verbose,
164 );
165
166 CliHealthSpan {
167 span,
168 start_time: Instant::now(),
169 }
170 }
171}
172
173pub struct CliHealthSpan {
174 span: Span,
175 start_time: Instant,
176}
177
178#[derive(Debug, Default)]
180pub struct HealthCheckResult {
181 pub success: bool,
182 pub overall: String,
183 pub checks_total: usize,
184 pub checks_passed: usize,
185 pub checks_failed: usize,
186 pub docker_available: bool,
187 pub docker_version: Option<String>,
188 pub docker_type: Option<String>,
189 pub weaver_available: bool,
190 pub weaver_version: Option<String>,
191 pub error: Option<(String, String)>,
192}
193
194impl HealthCheckResult {
195 pub fn builder() -> HealthCheckResultBuilder {
196 HealthCheckResultBuilder::default()
197 }
198}
199
200#[derive(Debug, Default)]
202pub struct HealthCheckResultBuilder {
203 result: HealthCheckResult,
204}
205
206impl HealthCheckResultBuilder {
207 pub fn success(mut self, success: bool) -> Self {
208 self.result.success = success;
209 self
210 }
211
212 pub fn overall(mut self, overall: impl Into<String>) -> Self {
213 self.result.overall = overall.into();
214 self
215 }
216
217 pub fn checks(mut self, total: usize, passed: usize, failed: usize) -> Self {
218 self.result.checks_total = total;
219 self.result.checks_passed = passed;
220 self.result.checks_failed = failed;
221 self
222 }
223
224 pub fn docker(
225 mut self,
226 available: bool,
227 version: Option<String>,
228 dtype: Option<String>,
229 ) -> Self {
230 self.result.docker_available = available;
231 self.result.docker_version = version;
232 self.result.docker_type = dtype;
233 self
234 }
235
236 pub fn weaver(mut self, available: bool, version: Option<String>) -> Self {
237 self.result.weaver_available = available;
238 self.result.weaver_version = version;
239 self
240 }
241
242 pub fn error(mut self, error_type: String, error_message: String) -> Self {
243 self.result.error = Some((error_type, error_message));
244 self
245 }
246
247 pub fn build(self) -> HealthCheckResult {
248 self.result
249 }
250}
251
252impl CliHealthSpan {
253 pub fn finish(self, result: HealthCheckResult) {
255 let duration_ms = self.start_time.elapsed().as_secs_f64() * 1000.0;
256
257 let _enter = self.span.enter();
258
259 self.span.record("operation.success", result.success);
261 self.span.record("health.overall", result.overall.as_str());
262 self.span
263 .record("health.checks_total", result.checks_total as i64);
264 self.span
265 .record("health.checks_passed", result.checks_passed as i64);
266 self.span
267 .record("health.checks_failed", result.checks_failed as i64);
268 self.span
269 .record("docker.available", result.docker_available);
270 self.span.record("operation.duration_ms", duration_ms);
271
272 if let Some(version) = result.docker_version {
274 self.span.record("docker.version", version.as_str());
275 }
276 if let Some(dtype) = result.docker_type {
277 self.span.record("docker.type", dtype.as_str());
278 }
279 self.span
280 .record("weaver.available", result.weaver_available);
281 if let Some(version) = result.weaver_version {
282 self.span.record("weaver.version", version.as_str());
283 }
284
285 if let Some((error_type, error_message)) = result.error {
287 self.span.record("error.type", error_type.as_str());
288 self.span.record("error.message", error_message.as_str());
289 }
290 }
291}
292
293pub struct CliSelfTestSpanBuilder {
295 suite: Option<String>,
296}
297
298impl CliSelfTestSpanBuilder {
299 pub fn new(suite: Option<String>) -> Self {
300 Self { suite }
301 }
302
303 pub fn start(self) -> CliSelfTestSpan {
304 let suite_name = self.suite.as_deref().unwrap_or("all");
305
306 let span = info_span!(
307 "clnrm.cli.self_test",
308 cli.command = "self-test",
309 cli.version = env!("CARGO_PKG_VERSION"),
310 test.suite = suite_name,
311 );
312
313 CliSelfTestSpan {
314 span,
315 start_time: Instant::now(),
316 }
317 }
318}
319
320pub struct CliSelfTestSpan {
321 span: Span,
322 start_time: Instant,
323}
324
325impl CliSelfTestSpan {
326 pub fn finish(
327 self,
328 success: bool,
329 tests_total: usize,
330 tests_passed: usize,
331 tests_failed: usize,
332 error: Option<(String, String)>,
333 ) {
334 let duration_ms = self.start_time.elapsed().as_secs_f64() * 1000.0;
335
336 let _enter = self.span.enter();
337
338 self.span.record("operation.success", success);
340 self.span.record("test.count", tests_total as i64);
341 self.span.record("test.passed", tests_passed as i64);
342 self.span.record("test.failed", tests_failed as i64);
343 self.span.record("operation.duration_ms", duration_ms);
344
345 if let Some((error_type, error_message)) = error {
347 self.span.record("error.type", error_type.as_str());
348 self.span.record("error.message", error_message.as_str());
349 }
350 }
351}