rm_lisa/display/renderers/
json.rs1use crate::{
5 display::{
6 renderers::{ConsoleOutputFeatures, ConsoleRenderer, get_ansi_escape_code_regex},
7 tracing::{FlattenedTracingField, SuperConsoleLogMessage},
8 },
9 errors::LisaError,
10 input::{InputProvider, TerminalInputEvent},
11 tasks::{GloballyUniqueTaskId, LisaTaskStatus, TaskEvent},
12};
13use chrono::{DateTime, Utc};
14use fnv::FnvHashMap;
15use parking_lot::RwLock;
16use regex::Regex;
17use serde_json::{Map, Number, Value as JSONValue};
18use std::{
19 borrow::Cow,
20 env::var as env_var,
21 sync::atomic::{AtomicBool, Ordering},
22};
23use valuable_serde::Serializable;
24
25#[derive(Debug)]
27pub struct JSONConsoleRenderer {
28 ansi_escapes: Regex,
30 force_pause: AtomicBool,
32 ps1: RwLock<String>,
34}
35
36impl JSONConsoleRenderer {
37 #[must_use]
39 pub fn new() -> Self {
40 Self {
41 ansi_escapes: get_ansi_escape_code_regex(),
42 force_pause: AtomicBool::new(false),
43 ps1: RwLock::new("> ".to_owned()),
44 }
45 }
46}
47
48impl Default for JSONConsoleRenderer {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl ConsoleRenderer for JSONConsoleRenderer {
55 fn should_use_renderer(
56 &self,
57 _features: &dyn ConsoleOutputFeatures,
58 environment_prefix: &str,
59 ) -> bool {
60 if let Ok(explicit_renderer) = env_var(format!("{environment_prefix}_LOG_FORMAT")) {
62 return explicit_renderer.trim().eq_ignore_ascii_case("json");
63 }
64
65 false
67 }
68
69 fn render_message(
70 &self,
71 _app_name: &'static str,
72 log: SuperConsoleLogMessage,
73 term_width: u16,
74 ) -> Result<String, LisaError> {
75 let mut map = Map::with_capacity(log.metadata().len() + 1);
76
77 {
79 let mut lisa_map = Map::with_capacity(8);
80 lisa_map.insert(
81 "at".to_owned(),
82 Number::from_i128(i128::from(log.at().timestamp()))
83 .map_or(JSONValue::Null, JSONValue::Number),
84 );
85 lisa_map.insert(
86 "id".to_owned(),
87 log.id()
88 .map_or(JSONValue::Null, |dat| JSONValue::String(dat.to_owned())),
89 );
90 lisa_map.insert(
91 "level".to_owned(),
92 JSONValue::String(format!("{}", log.level())),
93 );
94 lisa_map.insert(
95 "should_decorate".to_owned(),
96 JSONValue::Bool(log.should_decorate()),
97 );
98 lisa_map.insert(
99 "subsystem".to_owned(),
100 log.subsytem()
101 .map_or(JSONValue::Null, |dat| JSONValue::String(dat.to_owned())),
102 );
103 lisa_map.insert(
104 "towards_stdout".to_owned(),
105 JSONValue::Bool(log.towards_stdout()),
106 );
107 lisa_map.insert(
108 "term_width".to_owned(),
109 Number::from_u128(u128::from(term_width))
110 .map_or(JSONValue::Null, JSONValue::Number),
111 );
112 lisa_map.insert(
113 "color".to_owned(),
114 log.color()
115 .map_or(JSONValue::Null, |dat| JSONValue::String(dat.to_owned())),
116 );
117 map.insert("lisa".to_owned(), JSONValue::Object(lisa_map));
118 }
119 map.insert(
120 "msg".to_owned(),
121 log.message().map_or(JSONValue::Null, |str_value| {
122 JSONValue::String(str_value.to_owned())
123 }),
124 );
125
126 let mut metadata_map = Map::new();
127 for (key, val) in log.metadata() {
128 metadata_map.insert((*key).to_owned(), field_to_json(val));
129 }
130 map.insert("metadata".to_owned(), JSONValue::Object(metadata_map));
131 let mut data = serde_json::to_string(&JSONValue::Object(map))?;
132 data.push('\n');
133
134 Ok(match self.ansi_escapes.replace_all(&data, "") {
135 Cow::Borrowed(_) => data,
136 Cow::Owned(owned) => owned,
137 })
138 }
139
140 fn default_ps1(&self) -> String {
141 "> ".to_owned()
142 }
143
144 fn update_ps1(&self, new_ps1: String) {
145 let mut guarded = self.ps1.write();
146 *guarded = new_ps1;
147 }
148
149 fn supports_ansi(&self) -> bool {
150 false
151 }
152
153 fn clear_input(&self, _term_width: u16) -> String {
155 String::with_capacity(0)
156 }
157
158 fn clear_task_list(&self, _task_list_size: usize) -> String {
160 String::with_capacity(0)
161 }
162
163 fn render_input(
176 &self,
177 _app_name: &'static str,
178 _provider: &dyn InputProvider,
179 _term_width: u16,
180 ) -> Result<String, LisaError> {
181 Ok(String::with_capacity(0))
182 }
183
184 fn rerender_tasks(
189 &self,
190 new_task_events: &[TaskEvent],
191 _current_task_states: &FnvHashMap<
192 GloballyUniqueTaskId,
193 (DateTime<Utc>, String, LisaTaskStatus),
194 >,
195 _running_since: Option<DateTime<Utc>>,
196 _term_height: u16,
197 ) -> Result<String, LisaError> {
198 let mut data = String::new();
199
200 for event in new_task_events {
201 data.push_str(&serde_json::to_string(&match event {
202 TaskEvent::TaskStart(thread_id, task_id, name, status) => {
203 serde_json::json!({
204 "lisa": {
205 "id": "lisa::display::renderers::json::rerender_task::new_event"
206 },
207 "task": {
208 "event": "started",
209 "id": format!("{thread_id}/{task_id}"),
210 "name": name,
211 "status": Serializable::new(status),
212 }
213 })
214 }
215 TaskEvent::TaskStatusUpdate(thread_id, task_id, new_status) => {
216 serde_json::json!({
217 "lisa": {
218 "id": "lisa::display::renderers::json::rerender_task::new_event"
219 },
220 "task": {
221 "event": "status_update",
222 "id": format!("{thread_id}/{task_id}"),
223 "status": Serializable::new(new_status),
224 }
225 })
226 }
227 TaskEvent::TaskEnd(thread_id, task_id) => {
228 serde_json::json!({
229 "lisa": {
230 "id": "lisa::display::renderers::json::rerender_task::new_event"
231 },
232 "task": {
233 "event": "end",
234 "id": format!("{thread_id}/{task_id}"),
235 }
236 })
237 }
238 })?);
239 data.push('\n');
240 }
241
242 Ok(data)
243 }
244
245 fn on_input(
254 &self,
255 event: TerminalInputEvent,
256 provider: &dyn InputProvider,
257 ) -> Result<String, LisaError> {
258 match event {
259 TerminalInputEvent::InputStarted => {
260 let ps1_read = self.ps1.read();
261 Ok(ps1_read.clone())
262 }
263 TerminalInputEvent::InputFinished => Ok("\n".to_owned()),
264 TerminalInputEvent::InputAppend(character) => {
265 let mut new = String::with_capacity(1);
266 new.push(character);
267 Ok(new)
268 }
269 TerminalInputEvent::InputMassAppend(data) => Ok(data),
270 TerminalInputEvent::InputChanged(_) => {
271 let ps1_read = self.ps1.read();
272 let mut data = String::with_capacity(1 + ps1_read.len());
273 data.push('\n');
274 data.push_str(ps1_read.as_str());
275 data.push_str(&provider.current_input());
276 Ok(data)
277 }
278 TerminalInputEvent::InputCancelled => Ok("<CANCELLED>\n".to_owned()),
279 TerminalInputEvent::ClearScreen => Ok(String::with_capacity(0)),
280 TerminalInputEvent::CursorMoveLeft(_) | TerminalInputEvent::CursorMoveRight(_) => {
281 Ok(String::with_capacity(0))
282 }
283 TerminalInputEvent::ToggleOutputPause => {
284 self.force_pause.fetch_not(Ordering::Release);
285 Ok(String::with_capacity(0))
286 }
287 }
288 }
289
290 fn should_pause_log_events(&self, provider: &dyn InputProvider) -> bool {
291 provider.input_in_progress() || self.force_pause.load(Ordering::Acquire)
292 }
293}
294
295fn field_to_json(field: &FlattenedTracingField) -> JSONValue {
296 match field {
297 FlattenedTracingField::Null => JSONValue::Null,
298 FlattenedTracingField::Boolean(value) => JSONValue::Bool(*value),
299 FlattenedTracingField::Bytes(value) => JSONValue::String(format!("{value:02x?}")),
300 FlattenedTracingField::Float(value) => {
301 Number::from_f64(*value).map_or(JSONValue::Null, JSONValue::Number)
302 }
303 FlattenedTracingField::Int(value) => {
304 Number::from_i128(i128::from(*value)).map_or(JSONValue::Null, JSONValue::Number)
305 }
306 FlattenedTracingField::IntLarge(value) => {
307 Number::from_i128(*value).map_or(JSONValue::Null, JSONValue::Number)
308 }
309 FlattenedTracingField::UnsignedInt(value) => {
310 Number::from_u128(u128::from(*value)).map_or(JSONValue::Null, JSONValue::Number)
311 }
312 FlattenedTracingField::UnsignedIntLarge(value) => {
313 Number::from_u128(*value).map_or(JSONValue::Null, JSONValue::Number)
314 }
315 FlattenedTracingField::Str(value) => JSONValue::String(value.clone()),
316 FlattenedTracingField::List(value) => {
317 let mut items = Vec::with_capacity(value.len());
318 for val in value {
319 items.push(field_to_json(val));
320 }
321 JSONValue::Array(items)
322 }
323 FlattenedTracingField::Object(obj) => {
324 let mut map = Map::new();
325 for (key, value) in obj {
326 map.insert((*key).clone(), field_to_json(value));
327 }
328 JSONValue::Object(map)
329 }
330 }
331}