1use relux_core::diagnostics::IrSpan;
15
16use super::StructuredLogBuilder;
17use crate::observe::progress::ProgressEvent;
18use crate::observe::structured::event::CancelReasonRecord;
19use crate::observe::structured::event::EventKind;
20use crate::observe::structured::failure::CancellationRecord;
21use crate::observe::structured::failure::FailureRecord;
22use crate::observe::structured::span::SpanId;
23
24impl StructuredLogBuilder {
25 pub fn emit_annotate(
26 &self,
27 span: SpanId,
28 shell: &str,
29 marker: &str,
30 text: &str,
31 location: Option<&IrSpan>,
32 ) {
33 self.push_event(
34 span,
35 Some(shell),
36 Some(marker),
37 location,
38 EventKind::Annotate {
39 text: text.to_string(),
40 },
41 );
42 self.push_progress(ProgressEvent::Annotation(text.to_string()));
43 }
44
45 pub fn emit_log(
46 &self,
47 span: SpanId,
48 shell: &str,
49 marker: &str,
50 message: &str,
51 location: Option<&IrSpan>,
52 ) {
53 self.push_event(
54 span,
55 Some(shell),
56 Some(marker),
57 location,
58 EventKind::Log {
59 message: message.to_string(),
60 },
61 );
62 }
63
64 pub fn emit_warning(
65 &self,
66 span: SpanId,
67 shell: &str,
68 marker: &str,
69 message: &str,
70 location: Option<&IrSpan>,
71 ) {
72 self.push_event(
73 span,
74 Some(shell),
75 Some(marker),
76 location,
77 EventKind::Warning {
78 message: message.to_string(),
79 },
80 );
81 self.push_progress(ProgressEvent::Warning(message.to_string()));
82 }
83
84 pub fn emit_error(
85 &self,
86 span: SpanId,
87 shell: &str,
88 marker: &str,
89 message: &str,
90 location: Option<&IrSpan>,
91 ) {
92 self.push_event(
93 span,
94 Some(shell),
95 Some(marker),
96 location,
97 EventKind::Error {
98 message: message.to_string(),
99 },
100 );
101 self.push_progress(ProgressEvent::Error(message.to_string()));
102 }
103
104 pub fn emit_cancelled(
110 &self,
111 span: SpanId,
112 shell: Option<&str>,
113 shell_marker: Option<&str>,
114 reason: &crate::cancel::CancelReason,
115 ) {
116 self.push_event(
117 span,
118 shell,
119 shell_marker,
120 None,
121 EventKind::Cancelled {
122 reason: CancelReasonRecord::from(reason),
123 },
124 );
125 self.push_progress(ProgressEvent::Cancellation);
126 }
127
128 pub fn emit_failure_progress(&self) {
131 self.push_progress(ProgressEvent::Failure);
132 }
133
134 pub fn failure_record(&self, failure: &crate::report::result::Failure) -> FailureRecord {
141 use crate::report::result::Failure;
142 match failure {
143 Failure::MatchTimeout {
144 pattern,
145 shell,
146 effective,
147 context,
148 ..
149 } => FailureRecord::MatchTimeout {
150 span: context.span().unwrap_or(0),
151 event_seq: context.event_seq().unwrap_or(0),
152 shell: shell.clone(),
153 pattern: pattern.clone(),
154 effective: self.timeout_value(effective),
155 call_stack: context.call_stack().to_vec(),
156 buffer_tail: context.buffer_tail().to_string(),
157 vars_in_scope: context.vars_in_scope().to_vec(),
158 },
159 Failure::FailPatternMatched {
160 pattern,
161 matched_line,
162 shell,
163 context,
164 ..
165 } => FailureRecord::FailPatternMatched {
166 span: context.span().unwrap_or(0),
167 event_seq: context.event_seq().unwrap_or(0),
168 shell: shell.clone(),
169 pattern: pattern.clone(),
170 matched_line: matched_line.clone(),
171 call_stack: context.call_stack().to_vec(),
172 buffer_tail: context.buffer_tail().to_string(),
173 vars_in_scope: context.vars_in_scope().to_vec(),
174 },
175 Failure::ShellExited {
176 shell,
177 exit_code,
178 context,
179 ..
180 } => FailureRecord::ShellExited {
181 span: context.span().unwrap_or(0),
182 event_seq: context.event_seq().unwrap_or(0),
183 shell: shell.clone(),
184 exit_code: *exit_code,
185 call_stack: context.call_stack().to_vec(),
186 buffer_tail: context.buffer_tail().to_string(),
187 vars_in_scope: context.vars_in_scope().to_vec(),
188 },
189 Failure::Runtime {
190 message,
191 shell,
192 context,
193 ..
194 } => FailureRecord::Runtime {
195 span: context.span(),
196 event_seq: context.event_seq(),
197 shell: shell.clone(),
198 message: message.clone(),
199 call_stack: context.call_stack().to_vec(),
200 vars_in_scope: context.vars_in_scope().to_vec(),
201 },
202 Failure::PureMatch {
203 value,
204 pattern,
205 is_regex,
206 match_context,
207 context,
208 ..
209 } => FailureRecord::PureMatch {
210 span: context
211 .span()
212 .expect("pure-match failure always carries a span"),
213 event_seq: context
214 .event_seq()
215 .expect("pure-match failure always carries an event seq"),
216 match_context: match_context.clone(),
217 value: value.clone(),
218 pattern: pattern.clone(),
219 is_regex: *is_regex,
220 call_stack: context.call_stack().to_vec(),
221 vars_in_scope: context.vars_in_scope().to_vec(),
222 },
223 Failure::MultiMatch {
224 shell,
225 patterns,
226 matched,
227 effective,
228 context,
229 ..
230 } => FailureRecord::MultiMatch {
231 span: context.span().unwrap_or(0),
232 event_seq: context.event_seq().unwrap_or(0),
233 shell: shell.clone(),
234 patterns: patterns.clone(),
235 matched: matched.clone(),
236 effective: self.timeout_value(effective),
237 call_stack: context.call_stack().to_vec(),
238 buffer_tail: context.buffer_tail().to_string(),
239 vars_in_scope: context.vars_in_scope().to_vec(),
240 },
241 }
242 }
243
244 pub fn cancellation_record(
246 &self,
247 c: &crate::report::result::Cancellation,
248 ) -> CancellationRecord {
249 let ctx = &c.context;
250 CancellationRecord {
251 reason: CancelReasonRecord::from(&c.reason),
252 span: ctx.span(),
253 event_seq: ctx.event_seq(),
254 shell: None,
255 call_stack: ctx.call_stack().to_vec(),
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use std::path::PathBuf;
263 use std::sync::Arc;
264 use std::time::Instant;
265
266 use super::StructuredLogBuilder;
267 use crate::observe::progress;
268 use crate::observe::structured::MatchContext;
269 use crate::observe::structured::failure::FailureRecord;
270 use crate::report::result::Failure;
271 use crate::report::result::FailureContext;
272 use relux_core::diagnostics::IrSpan;
273
274 fn make_builder() -> StructuredLogBuilder {
275 let (tx, _rx) = progress::channel();
276 let sources = relux_core::table::SharedTable::new();
277 StructuredLogBuilder::new(
278 tx,
279 Instant::now(),
280 sources,
281 Arc::from(PathBuf::from("/project").as_path()),
282 )
283 }
284
285 #[test]
286 fn pure_match_record_carries_real_seq_and_vars() {
287 let builder = make_builder();
291 let f = Failure::PureMatch {
292 value: "abc".into(),
293 pattern: "xyz".into(),
294 is_regex: false,
295 span: IrSpan::synthetic(),
296 match_context: MatchContext::TestPreamble {
297 name: "login".into(),
298 },
299 context: FailureContext::pure(7, 3, vec![], vec![("v".into(), "abc".into())]),
300 };
301 match builder.failure_record(&f) {
302 FailureRecord::PureMatch {
303 span,
304 event_seq,
305 match_context,
306 vars_in_scope,
307 value,
308 pattern,
309 ..
310 } => {
311 assert_eq!(span, 7);
312 assert_eq!(event_seq, 3, "real seq is threaded through, not 0");
313 assert_eq!(
314 match_context,
315 MatchContext::TestPreamble {
316 name: "login".into()
317 }
318 );
319 assert_eq!(vars_in_scope, vec![("v".to_string(), "abc".to_string())]);
320 assert_eq!(value, "abc");
321 assert_eq!(pattern, "xyz");
322 }
323 other => panic!("expected PureMatch, got {other:?}"),
324 }
325 }
326}