Skip to main content

std_mel/engine/
log.rs

1use crate::engine::*;
2use melodium_core::common::executive::Level as LogLevel;
3use melodium_core::DataTrait;
4use melodium_macro::{mel_data, mel_function, mel_treatment};
5
6/// Formats a `Value` through its actual runtime `Display` implementation
7/// (`DataTrait::display`), rather than `Value`'s own `core::fmt::Display`,
8/// which renders `Data` values as a `/* TypeName */` placeholder meant for
9/// regenerating Mélodium source, not for user-facing logging.
10///
11/// `DataTrait::display`'s own fallback for non-`Data` variants used to
12/// silently resolve to the derived `Debug::fmt` instead of `Display::fmt`
13/// (only `Debug` was imported where it is implemented), printing e.g.
14/// `I64(42)` instead of `42` for every plain value, not just `Data` ones.
15/// Fixed at the source in `melodium-common`'s `impl DataTrait for Value`.
16struct RuntimeDisplay<'a>(&'a Value);
17
18impl std::fmt::Display for RuntimeDisplay<'_> {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        DataTrait::display(self.0, f)
21    }
22}
23
24/// Forward a stream of strings to the engine log at the given `level` under `label`.
25///
26/// Each received string is logged as a separate entry. The treatment continues until the stream closes.
27#[mel_treatment(
28    model engine Engine
29    input messages Stream<string>
30)]
31pub async fn log_stream(level: Level, label: string) {
32    let engine = EngineModel::into(engine);
33
34    while let Ok(msgs) = messages.recv_many_as::<string>().await {
35        for msg in msgs {
36            engine
37                .world()
38                .log(level.level, label.clone(), msg, Some(track_id))
39                .await;
40        }
41    }
42}
43
44/// Like `log_stream` but reads `label` as a block input rather than a constant parameter.
45///
46/// Waits for `label` to arrive first, then logs each string in `messages` at `level` under that label.
47#[mel_treatment(
48    model engine Engine
49    input label Block<string>
50    input messages Stream<string>
51)]
52pub async fn log_stream_label(level: Level) {
53    let engine = EngineModel::into(engine);
54
55    if let Ok(label) = label.recv_one_as::<String>().await {
56        while let Ok(msgs) = messages.recv_many_as::<string>().await {
57            for msg in msgs {
58                engine
59                    .world()
60                    .log(level.level, label.clone(), msg, Some(track_id))
61                    .await;
62            }
63        }
64    }
65}
66
67/// Forward a single string block to the engine log at the given `level` under `label`.
68#[mel_treatment(
69    model engine Engine
70    input message Block<string>
71)]
72pub async fn log_block(level: Level, label: string) {
73    let engine = EngineModel::into(engine);
74
75    if let Ok(msg) = message.recv_one_as::<string>().await {
76        engine
77            .world()
78            .log(level.level, label, msg, Some(track_id))
79            .await;
80    }
81}
82
83/// Like `log_block` but reads both `label` and `message` as block inputs.
84///
85/// Waits for `label` first, then logs `message` at `level` under that label.
86#[mel_treatment(
87    model engine Engine
88    input label Block<string>
89    input message Block<string>
90)]
91pub async fn log_block_label(level: Level) {
92    let engine = EngineModel::into(engine);
93
94    if let Ok(label) = label.recv_one_as::<String>().await {
95        if let Ok(msg) = message.recv_one_as::<string>().await {
96            engine
97                .world()
98                .log(level.level, label, msg, Some(track_id))
99                .await;
100        }
101    }
102}
103
104/// Convert each item in a `Display` stream to its string representation and log it at `level` under `label`.
105#[mel_treatment(
106    model engine Engine
107    input display Stream<D>
108    generic D (Display)
109)]
110pub async fn log_data_stream(level: Level, label: string) {
111    let engine = EngineModel::into(engine);
112
113    while let Ok(values) = display
114        .recv_many()
115        .await
116        .map(|values| Into::<VecDeque<Value>>::into(values))
117    {
118        for val in values {
119            engine
120                .world()
121                .log(
122                    level.level,
123                    label.clone(),
124                    format!("{}", RuntimeDisplay(&val)),
125                    Some(track_id),
126                )
127                .await;
128        }
129    }
130}
131
132/// Like `log_data_stream` but reads `label` as a block input.
133///
134/// Waits for `label` first, then converts and logs each item in `display` at `level`.
135#[mel_treatment(
136    model engine Engine
137    input label Block<string>
138    input display Stream<D>
139    generic D (Display)
140)]
141pub async fn log_data_stream_label(level: Level) {
142    let engine = EngineModel::into(engine);
143
144    if let Ok(label) = label.recv_one_as::<String>().await {
145        while let Ok(values) = display
146            .recv_many()
147            .await
148            .map(|values| Into::<VecDeque<Value>>::into(values))
149        {
150            for val in values {
151                engine
152                    .world()
153                    .log(
154                        level.level,
155                        label.clone(),
156                        format!("{}", RuntimeDisplay(&val)),
157                        Some(track_id),
158                    )
159                    .await;
160            }
161        }
162    }
163}
164
165/// Convert a single `Display` block to its string representation and log it at `level` under `label`.
166#[mel_treatment(
167    model engine Engine
168    input display Block<D>
169    generic D (Display)
170)]
171pub async fn log_data_block(level: Level, label: string) {
172    let engine = EngineModel::into(engine);
173
174    if let Ok(val) = display.recv_one().await {
175        engine
176            .world()
177            .log(
178                level.level,
179                label,
180                format!("{}", RuntimeDisplay(&val)),
181                Some(track_id),
182            )
183            .await;
184    }
185}
186
187/// Like `log_data_block` but reads both `label` and `display` as block inputs.
188///
189/// Waits for `label` first, then converts and logs `display` at `level`.
190#[mel_treatment(
191    model engine Engine
192    input label Block<string>
193    input display Block<D>
194    generic D (Display)
195)]
196pub async fn log_data_block_label(level: Level) {
197    let engine = EngineModel::into(engine);
198
199    if let Ok(label) = label.recv_one_as::<String>().await {
200        if let Ok(val) = display.recv_one().await {
201            engine
202                .world()
203                .log(
204                    level.level,
205                    label,
206                    format!("{}", RuntimeDisplay(&val)),
207                    Some(track_id),
208                )
209                .await;
210        }
211    }
212}
213
214#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
215/// Log severity level.
216///
217/// Ordered from lowest to highest verbosity: `trace` < `debug` < `info` < `warning` < `error`.
218/// Use the constructor functions `|trace()`, `|debug()`, `|info()`, `|warning()`, `|error()` to obtain a value.
219#[mel_data(traits(Serialize Deserialize Bounded PartialEquality Equality PartialOrder Order))]
220pub struct Level {
221    pub level: LogLevel,
222}
223
224fn level_bounded_min() -> Level {
225    Level {
226        level: LogLevel::Trace,
227    }
228}
229
230fn level_bounded_max() -> Level {
231    Level {
232        level: LogLevel::Error,
233    }
234}
235
236/// Return the error log level.
237#[mel_function]
238pub fn error() -> Level {
239    Level {
240        level: LogLevel::Error,
241    }
242}
243
244/// Return the warning log level.
245#[mel_function]
246pub fn warning() -> Level {
247    Level {
248        level: LogLevel::Warning,
249    }
250}
251
252/// Return the info log level.
253#[mel_function]
254pub fn info() -> Level {
255    Level {
256        level: LogLevel::Info,
257    }
258}
259
260/// Return the debug log level.
261#[mel_function]
262pub fn debug() -> Level {
263    Level {
264        level: LogLevel::Debug,
265    }
266}
267
268/// Return the trace log level (most verbose).
269#[mel_function]
270pub fn trace() -> Level {
271    Level {
272        level: LogLevel::Trace,
273    }
274}