sol_log_parser/structured_log/
mod.rs

1use std::fmt::{Debug, Display};
2
3use crate::{
4    parsed_log::{
5        ParsedCuLog, ParsedDataLog, ParsedFailedLog, ParsedInvokeLog, ParsedLog, ParsedOtherLog,
6        ParsedProgramLog, ParsedReturnLog, ParsedSuccessLog,
7    },
8    raw_log::{
9        RawCuLog, RawDataLog, RawFailedLog, RawInvokeLog, RawLog, RawOtherLog, RawProgramLog,
10        RawReturnLog, RawSuccessLog,
11    },
12    Result,
13};
14
15pub mod parsed;
16pub mod raw;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ComputeUnits {
20    pub consumed: u64,
21    pub budget: u64,
22}
23
24impl From<RawCuLog<'_>> for ComputeUnits {
25    fn from(value: RawCuLog) -> Self {
26        ComputeUnits {
27            consumed: value.consumed,
28            budget: value.budget,
29        }
30    }
31}
32
33impl From<ParsedCuLog> for ComputeUnits {
34    fn from(value: ParsedCuLog) -> Self {
35        ComputeUnits {
36            consumed: value.consumed,
37            budget: value.budget,
38        }
39    }
40}
41
42/// A generic structured representation of a program execution log.
43///
44/// `StructuredLog` provides a hierarchical view of program execution, including:
45/// - The program's identifier and execution depth (`program_id`, `depth`)
46/// - The outcome of execution (`result`)
47/// - Logs emitted directly by the program (`program_logs`, `data_logs`, `return_data`, `compute_log`)
48/// - Nested logs from CPI (cross-program invocation) calls (`cpi_logs`)
49/// - Raw, unstructured logs that were parsed to build this representation (`raw_logs`)
50///
51/// This struct is parameterized over the types of each log component, allowing it to be reused
52/// in different contexts, such as raw log parsing, typed log rendering, or test scaffolding.
53///
54/// Type Parameters:
55/// - `Id`: The type used to represent a program ID (e.g., `&str`, `Pubkey`)
56/// - `ProgramResult`: The type representing the program’s result log
57/// - `ProgramLog`: The type representing individual log lines emitted by the program
58/// - `DataLog`: The type representing logs carrying custom data
59/// - `ReturnData`: The type for return data emitted at the end of program execution
60/// - `ComputeLog`: The type for compute unit logs
61/// - `RawLog`: The type for raw log entries that this structured log was derived from
62struct StructuredLog<Id, ProgramResult, ProgramLog, DataLog, ReturnData, RawLog> {
63    pub program_id: Id,
64    pub depth: u8,
65    pub result: ProgramResult,
66    pub program_logs: Vec<ProgramLog>,
67    pub data_logs: Vec<DataLog>,
68    pub return_data: Option<ReturnData>,
69    pub compute_log: Option<ComputeUnits>,
70    pub cpi_logs: Vec<Self>,
71    pub raw_logs: Vec<RawLog>,
72}
73
74impl<Id, Program, Data, ReturnData, Err, RawLog>
75    StructuredLog<Id, ProgramResult<Err>, Program, Data, ReturnData, RawLog>
76where
77    Id: Eq + Debug + Display,
78    Program: Log<RawLog = RawLog>,
79    Data: Log<RawLog = RawLog>,
80{
81    #[allow(clippy::type_complexity)]
82    pub fn from_logs<Invoke, Success, Failed, Return, Compute, Other>(
83        logs: Vec<Log2<Invoke, Success, Failed, Program, Data, Return, Compute, Other>>,
84    ) -> Result<Vec<Self>>
85    where
86        Invoke: Log<RawLog = RawLog> + InvokeLog<ProgramId = Id>,
87        Success: Log<RawLog = RawLog> + SuccessLog<ProgramId = Id>,
88        Failed: Log<RawLog = RawLog> + FailedLog<ProgramId = Id, Err = Err>,
89        Return: Log<RawLog = RawLog> + ReturnLog<ProgramId = Id, Data = ReturnData>,
90        Compute: Log<RawLog = RawLog> + ComputeUnitsLog<ProgramId = Id> + Into<ComputeUnits>,
91        Other: Log<RawLog = RawLog>,
92    {
93        let mut stack = Vec::new();
94        let mut completed = Vec::new();
95
96        for log in logs {
97            match log {
98                Log2::Invoke(log) => {
99                    stack.push(FrameBuilder::new(
100                        log.program_id(),
101                        log.depth(),
102                        log.raw_log(),
103                    ));
104                }
105                Log2::Success(log) => {
106                    let builder = stack
107                        .pop()
108                        .expect("Unmatched success log without a prior invoke");
109                    assert_eq!(
110                        builder.program_id,
111                        log.program_id(),
112                        "Mismatched success: expected {}, got {}",
113                        builder.program_id,
114                        log.program_id()
115                    );
116                    let structured = builder.finalize(ProgramResult::Success, log.raw_log());
117
118                    if let Some(parent) = stack.last_mut() {
119                        parent.cpi_logs.push(structured);
120                    } else {
121                        completed.push(structured);
122                    }
123                }
124                Log2::Failed(log) => {
125                    let builder = stack
126                        .pop()
127                        .expect("Unmatched failed log without a prior invoke");
128                    assert_eq!(
129                        builder.program_id,
130                        log.program_id(),
131                        "Mismatched failed: expected {}, got {}",
132                        builder.program_id,
133                        log.program_id()
134                    );
135                    let structured = builder.finalize(ProgramResult::Err(log.err()), log.raw_log());
136
137                    if let Some(parent) = stack.last_mut() {
138                        parent.cpi_logs.push(structured);
139                    } else {
140                        completed.push(structured);
141                    }
142                }
143                Log2::Log(log) => {
144                    if let Some(top) = stack.last_mut() {
145                        top.push_program_log(log);
146                    }
147                }
148                Log2::Data(log) => {
149                    if let Some(top) = stack.last_mut() {
150                        top.push_data_log(log);
151                    }
152                }
153                Log2::Return(log) => {
154                    if let Some(top) = stack.last_mut() {
155                        if top.program_id == log.program_id() {
156                            top.set_return_data(log.data(), log.raw_log());
157                        } else {
158                            top.push_raw(log.raw_log());
159                        }
160                    }
161                }
162                Log2::Cu(log) => {
163                    if let Some(top) = stack.last_mut() {
164                        if top.program_id == log.program_id() {
165                            top.set_compute_log(log);
166                        } else {
167                            top.push_raw(log.raw_log());
168                        }
169                    }
170                }
171                Log2::Other(log) => {
172                    if let Some(top) = stack.last_mut() {
173                        top.push_raw(log.raw_log());
174                    }
175                }
176            }
177        }
178
179        assert!(
180            stack.is_empty(),
181            "Unbalanced log stack: {} frames left",
182            stack.len()
183        );
184        Ok(completed)
185    }
186}
187
188enum ProgramResult<Err> {
189    Success,
190    Err(Err),
191}
192
193pub(crate) enum Log2<Invoke, Success, Failed, Program, Data, Return, Cu, Other> {
194    Invoke(Invoke),
195    Success(Success),
196    Failed(Failed),
197    Log(Program),
198    Data(Data),
199    Return(Return),
200    Cu(Cu),
201    Other(Other),
202}
203
204impl<'a> From<RawLog<'a>>
205    for Log2<
206        RawInvokeLog<'a>,
207        RawSuccessLog<'a>,
208        RawFailedLog<'a>,
209        RawProgramLog<'a>,
210        RawDataLog<'a>,
211        RawReturnLog<'a>,
212        RawCuLog<'a>,
213        RawOtherLog<'a>,
214    >
215{
216    fn from(value: RawLog<'a>) -> Self {
217        match value {
218            RawLog::Invoke(raw_invoke_log) => Log2::Invoke(raw_invoke_log),
219            RawLog::Success(raw_success_log) => Log2::Success(raw_success_log),
220            RawLog::Failed(raw_failed_log) => Log2::Failed(raw_failed_log),
221            RawLog::Log(raw_program_log) => Log2::Log(raw_program_log),
222            RawLog::Data(raw_data_log) => Log2::Data(raw_data_log),
223            RawLog::Return(raw_return_log) => Log2::Return(raw_return_log),
224            RawLog::Cu(raw_cu_log) => Log2::Cu(raw_cu_log),
225            RawLog::Other(raw_other) => Log2::Other(raw_other),
226        }
227    }
228}
229
230impl From<ParsedLog>
231    for Log2<
232        ParsedInvokeLog,
233        ParsedSuccessLog,
234        ParsedFailedLog,
235        ParsedProgramLog,
236        ParsedDataLog,
237        ParsedReturnLog,
238        ParsedCuLog,
239        ParsedOtherLog,
240    >
241{
242    fn from(value: ParsedLog) -> Self {
243        match value {
244            ParsedLog::Invoke(invoke_log) => Log2::Invoke(invoke_log),
245            ParsedLog::Success(success_log) => Log2::Success(success_log),
246            ParsedLog::Failed(failed_log) => Log2::Failed(failed_log),
247            ParsedLog::Log(program_log) => Log2::Log(program_log),
248            ParsedLog::Data(data_log) => Log2::Data(data_log),
249            ParsedLog::Return(return_log) => Log2::Return(return_log),
250            ParsedLog::Cu(cu_log) => Log2::Cu(cu_log),
251            ParsedLog::Other(other) => Log2::Other(other),
252        }
253    }
254}
255
256pub(crate) trait Log {
257    type RawLog;
258
259    fn raw_log(&self) -> Self::RawLog;
260}
261
262pub(crate) trait InvokeLog {
263    type ProgramId;
264
265    fn program_id(&self) -> Self::ProgramId;
266    fn depth(&self) -> u8;
267}
268
269pub(crate) trait SuccessLog {
270    type ProgramId;
271
272    fn program_id(&self) -> Self::ProgramId;
273}
274
275pub(crate) trait FailedLog {
276    type ProgramId;
277    type Err;
278
279    fn program_id(&self) -> Self::ProgramId;
280    fn err(&self) -> Self::Err;
281}
282
283pub(crate) trait ReturnLog {
284    type ProgramId;
285    type Data;
286
287    fn program_id(&self) -> Self::ProgramId;
288    fn data(&self) -> Self::Data;
289}
290
291pub(crate) trait ComputeUnitsLog {
292    type ProgramId;
293
294    fn program_id(&self) -> Self::ProgramId;
295}
296
297struct FrameBuilder<Id, ProgramResult, ProgramLog, DataLog, ReturnData, RawLog> {
298    program_id: Id,
299    depth: u8,
300    program_logs: Vec<ProgramLog>,
301    data_logs: Vec<DataLog>,
302    return_data: Option<ReturnData>,
303    compute_log: Option<ComputeUnits>,
304    raw_logs: Vec<RawLog>,
305    cpi_logs: Vec<StructuredLog<Id, ProgramResult, ProgramLog, DataLog, ReturnData, RawLog>>,
306}
307
308impl<Id, ProgramResult, ProgramLog, DataLog, ReturnData, RawLog>
309    FrameBuilder<Id, ProgramResult, ProgramLog, DataLog, ReturnData, RawLog>
310where
311    Id: Eq + Debug + Display,
312    ProgramLog: Log<RawLog = RawLog>,
313    DataLog: Log<RawLog = RawLog>,
314{
315    fn new(program_id: Id, depth: u8, raw: RawLog) -> Self {
316        Self {
317            program_id,
318            depth,
319            program_logs: vec![],
320            data_logs: vec![],
321            return_data: None,
322            compute_log: None,
323            raw_logs: vec![raw],
324            cpi_logs: vec![],
325        }
326    }
327
328    fn push_program_log(&mut self, log: ProgramLog) {
329        self.raw_logs.push(log.raw_log());
330        self.program_logs.push(log);
331    }
332
333    fn push_data_log(&mut self, log: DataLog) {
334        self.raw_logs.push(log.raw_log());
335        self.data_logs.push(log);
336    }
337
338    fn push_raw(&mut self, raw: RawLog) {
339        self.raw_logs.push(raw);
340    }
341
342    fn set_return_data(&mut self, data: ReturnData, raw: RawLog) {
343        self.raw_logs.push(raw);
344        self.return_data = Some(data);
345    }
346
347    fn set_compute_log<ComputeLog>(&mut self, log: ComputeLog)
348    where
349        ComputeLog: Log<RawLog = RawLog> + Into<ComputeUnits>,
350    {
351        self.raw_logs.push(log.raw_log());
352        self.compute_log = Some(log.into());
353    }
354
355    fn finalize(
356        mut self,
357        result: ProgramResult,
358        final_raw: RawLog,
359    ) -> StructuredLog<Id, ProgramResult, ProgramLog, DataLog, ReturnData, RawLog> {
360        self.raw_logs.push(final_raw);
361        self.raw_logs.shrink_to_fit();
362        self.cpi_logs.shrink_to_fit();
363        self.data_logs.shrink_to_fit();
364        self.program_logs.shrink_to_fit();
365
366        StructuredLog {
367            program_id: self.program_id,
368            depth: self.depth,
369            result,
370            program_logs: self.program_logs,
371            data_logs: self.data_logs,
372            return_data: self.return_data,
373            compute_log: self.compute_log,
374            cpi_logs: self.cpi_logs,
375            raw_logs: self.raw_logs,
376        }
377    }
378}