relux_runtime/observe/structured/builder/
lifecycle.rs1use relux_core::diagnostics::IrSpan;
9
10use super::SpanGuard;
11use super::StructuredLogBuilder;
12use crate::observe::progress::ProgressEvent;
13use crate::observe::structured::event::EventKind;
14use crate::observe::structured::event::EventSeq;
15use crate::observe::structured::failure::StackFrame;
16use crate::observe::structured::shell::ShellRecord;
17use crate::observe::structured::span::Span;
18use crate::observe::structured::span::SpanId;
19use crate::observe::structured::span::SpanKind;
20
21impl StructuredLogBuilder {
22 pub fn open_span(
29 &self,
30 kind: SpanKind,
31 parent: Option<SpanId>,
32 location: Option<&IrSpan>,
33 ) -> SpanGuard {
34 let location = location.and_then(|s| self.resolve_location(s));
35 let start_ts = self.now();
36 let id = {
37 let mut inner = self.inner.lock().unwrap();
38 let id = inner.next_span_id;
39 inner.next_span_id += 1;
40 inner.spans.insert(
41 id,
42 Span {
43 id,
44 kind,
45 parent,
46 start_ts,
47 end_ts: None,
48 location,
49 },
50 );
51 id
52 };
53 SpanGuard::new(id, self.clone())
54 }
55
56 pub(super) fn close_span_inner(&self, id: SpanId) {
66 let end_ts = self.now();
67 let mut inner = self.inner.lock().unwrap();
68 if let Some(span) = inner.spans.get_mut(&id)
69 && span.end_ts.is_none()
70 {
71 span.end_ts = Some(end_ts);
72 }
73 }
74
75 pub fn close_span(&self, id: SpanId) {
80 self.close_span_inner(id);
81 }
82
83 pub fn set_fn_call_result(&self, id: SpanId, result: &str) {
87 let mut inner = self.inner.lock().unwrap();
88 if let Some(span) = inner.spans.get_mut(&id)
89 && let SpanKind::FnCall { result: slot, .. } = &mut span.kind
90 {
91 *slot = Some(result.to_string());
92 }
93 }
94
95 pub fn resolve_stack(&self, leaf: SpanId) -> Vec<StackFrame> {
99 let inner = self.inner.lock().unwrap();
100 let mut chain: Vec<StackFrame> = Vec::new();
101 let mut next = Some(leaf);
102 while let Some(id) = next {
103 let Some(span) = inner.spans.get(&id) else {
104 break;
105 };
106 let (name, args) = span.kind.frame_data();
107 let kind = match &span.kind {
111 SpanKind::FnCall { is_pure: true, .. } => "pure-fn-call",
112 other => other.kind_str(),
113 };
114 chain.push(StackFrame {
115 span: id,
116 kind: kind.to_string(),
117 name,
118 args,
119 alias: span.kind.frame_alias(),
120 location: span.location.clone(),
121 });
122 next = span.parent;
123 }
124 chain.reverse();
125 chain
126 }
127
128 pub fn open_markers_span(&self, location: Option<&IrSpan>) -> SpanGuard {
131 self.open_span(SpanKind::Markers, None, location)
132 }
133
134 pub fn open_multimatch_span(
139 &self,
140 parent: SpanId,
141 shell: &str,
142 location: Option<&IrSpan>,
143 ) -> SpanGuard {
144 self.open_span(
145 SpanKind::MultiMatch {
146 shell: shell.to_string(),
147 },
148 Some(parent),
149 location,
150 )
151 }
152
153 pub fn open_marker_eval_span(
155 &self,
156 parent: SpanId,
157 marker_kind: super::super::span::MarkerEvalKind,
158 modifier: super::super::span::MarkerEvalModifier,
159 decision: super::super::span::MarkerEvalDecision,
160 location: Option<&IrSpan>,
161 ) -> SpanGuard {
162 self.open_span(
163 SpanKind::MarkerEval {
164 marker_kind,
165 modifier,
166 decision,
167 },
168 Some(parent),
169 location,
170 )
171 }
172
173 pub fn emit_bool_check(
178 &self,
179 span: SpanId,
180 evaluation: super::super::span::MarkerEvalDetail,
181 location: Option<&IrSpan>,
182 ) -> EventSeq {
183 self.push_event(
184 span,
185 None,
186 None,
187 location,
188 EventKind::BoolCheck { evaluation },
189 )
190 }
191
192 pub fn record_shell_spawn(&self, marker: &str, name: &str, command: &str) {
195 let spawn_ts = self.now();
196 let mut inner = self.inner.lock().unwrap();
197 inner.shells.insert(
198 marker.to_string(),
199 ShellRecord {
200 marker: marker.to_string(),
201 name: name.to_string(),
202 spawn_ts,
203 terminate_ts: None,
204 command: command.to_string(),
205 },
206 );
207 }
208
209 pub fn record_shell_terminate(&self, marker: &str) {
210 let terminate_ts = self.now();
211 let mut inner = self.inner.lock().unwrap();
212 if let Some(rec) = inner.shells.get_mut(marker) {
213 rec.terminate_ts = Some(terminate_ts);
214 }
215 }
216
217 pub fn emit_shell_spawn(
220 &self,
221 span: SpanId,
222 shell: &str,
223 marker: &str,
224 command: &str,
225 location: Option<&IrSpan>,
226 ) {
227 self.record_shell_spawn(marker, shell, command);
228 self.push_event(
229 span,
230 Some(shell),
231 Some(marker),
232 location,
233 EventKind::ShellSpawn {
234 name: shell.to_string(),
235 command: command.to_string(),
236 },
237 );
238 self.push_progress(ProgressEvent::ShellSpawn);
239 }
240
241 pub fn emit_shell_ready(
242 &self,
243 span: SpanId,
244 shell: &str,
245 marker: &str,
246 location: Option<&IrSpan>,
247 ) {
248 self.push_event(
249 span,
250 Some(shell),
251 Some(marker),
252 location,
253 EventKind::ShellReady {
254 name: shell.to_string(),
255 },
256 );
257 }
258
259 pub fn emit_shell_switch(
260 &self,
261 span: SpanId,
262 shell: &str,
263 marker: &str,
264 location: Option<&IrSpan>,
265 ) {
266 self.push_event(
267 span,
268 Some(shell),
269 Some(marker),
270 location,
271 EventKind::ShellSwitch {
272 name: shell.to_string(),
273 },
274 );
275 self.push_progress(ProgressEvent::ShellSwitch(shell.to_string()));
276 }
277
278 pub fn emit_shell_terminate(
279 &self,
280 span: SpanId,
281 shell: &str,
282 marker: &str,
283 location: Option<&IrSpan>,
284 ) {
285 self.record_shell_terminate(marker);
286 self.push_event(
287 span,
288 Some(shell),
289 Some(marker),
290 location,
291 EventKind::ShellTerminate {
292 name: shell.to_string(),
293 },
294 );
295 self.push_progress(ProgressEvent::ShellTerminate);
296 }
297
298 pub fn push_fn_enter(&self, name: &str) {
303 self.push_progress(ProgressEvent::FnEnter(name.to_string()));
304 }
305
306 pub fn push_fn_exit(&self) {
307 self.push_progress(ProgressEvent::FnExit);
308 }
309
310 pub fn push_effect_setup(&self, name: &str) {
311 self.push_progress(ProgressEvent::EffectSetup(name.to_string()));
312 }
313
314 pub fn push_effect_teardown(&self) {
315 self.push_progress(ProgressEvent::EffectTeardown);
316 }
317
318 pub fn emit_effect_expose_shell(
321 &self,
322 span: SpanId,
323 name: &str,
324 target: &str,
325 qualifier: Option<&str>,
326 location: Option<&IrSpan>,
327 ) {
328 self.push_event(
329 span,
330 None,
331 None,
332 location,
333 EventKind::EffectExposeShell {
334 name: name.to_string(),
335 target: target.to_string(),
336 qualifier: qualifier.map(String::from),
337 },
338 );
339 }
340
341 pub fn emit_effect_expose_var(
342 &self,
343 span: SpanId,
344 name: &str,
345 target: &str,
346 qualifier: Option<&str>,
347 value: &str,
348 location: Option<&IrSpan>,
349 ) {
350 self.push_event(
351 span,
352 None,
353 None,
354 location,
355 EventKind::EffectExposeVar {
356 name: name.to_string(),
357 target: target.to_string(),
358 qualifier: qualifier.map(String::from),
359 value: value.to_string(),
360 },
361 );
362 }
363}