1use crate::error::MessageError;
6use crate::{ExtendedMetadata, MessageFormatter};
7use bumpalo::Bump;
8use bumpalo::collections::{String as BumpaloString, Vec as BumpaloVec};
9use diagnostics_data::{ExtendedMoniker, Severity};
10use diagnostics_log_encoding::{Argument, Record, Value};
11use flyweights::FlyStr;
12use static_assertions::const_assert;
13use std::fmt::Write;
14use std::marker::PhantomData;
15use std::str;
16use zx::BootInstant;
17
18pub use crate::constants::*;
19
20#[repr(C)]
24pub struct CPPArray<'a, T> {
25 pub len: usize,
27 pub ptr: *const T,
32
33 phantom: PhantomData<&'a T>,
34}
35
36impl<T> Default for CPPArray<'_, T> {
37 fn default() -> Self {
38 CPPArray { len: 0, ptr: std::ptr::null(), phantom: PhantomData }
39 }
40}
41
42impl CPPArray<'_, u8> {
43 pub unsafe fn as_utf8_str(&self) -> &str {
51 unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len)) }
52 }
53}
54
55impl<'a> From<&'a str> for CPPArray<'a, u8> {
56 fn from(value: &'a str) -> Self {
57 value.as_bytes().into()
58 }
59}
60
61impl<'a> From<Option<&'a str>> for CPPArray<'a, u8> {
62 fn from(value: Option<&'a str>) -> Self {
63 value.map(|v| v.into()).unwrap_or_default()
64 }
65}
66
67impl<'a, T> From<&'a [T]> for CPPArray<'a, T> {
68 fn from(value: &'a [T]) -> Self {
69 CPPArray { len: value.len(), ptr: value.as_ptr(), phantom: PhantomData }
70 }
71}
72
73impl<'a> From<BumpaloString<'a>> for CPPArray<'a, u8> {
74 fn from(value: BumpaloString<'a>) -> Self {
75 value.into_bump_str().into()
76 }
77}
78
79#[repr(C)]
81pub struct LogMessage<'a> {
82 severity: u8,
84 tags: CPPArray<'a, CPPArray<'a, u8>>,
86 pid: u64,
88 tid: u64,
90 dropped: u64,
92 message: CPPArray<'a, u8>,
94 timestamp: i64,
97}
98
99const_assert!(!std::mem::needs_drop::<LogMessage<'_>>());
101
102pub struct CPPLogMessageBuilder<'a> {
103 severity: u8,
104 tags: BumpaloVec<'a, BumpaloString<'a>>,
105 pid: Option<u64>,
106 tid: Option<u64>,
107 dropped: u64,
108 file: Option<String>,
109 line: Option<u64>,
110 moniker: Option<BumpaloString<'a>>,
111 message: Option<String>,
112 timestamp: i64,
113 kvps: String,
114 allocator: &'a Bump,
115}
116
117fn escape_quotes(input: &str, output: &mut String) {
119 for ch in input.chars() {
120 if ch == '"' || ch == '\\' {
121 output.push('\\');
122 }
123 output.push(ch);
124 }
125}
126
127impl<'a> CPPLogMessageBuilder<'a> {
128 fn set_raw_severity(mut self, raw_severity: u8) -> Self {
129 self.severity = raw_severity;
130 self
131 }
132
133 fn add_tag(mut self, tag: impl Into<String>) -> Self {
134 self.tags.push(BumpaloString::from_str_in(&tag.into(), self.allocator));
135 self
136 }
137
138 fn set_pid(mut self, pid: u64) -> Self {
139 self.pid = Some(pid);
140 self
141 }
142
143 fn set_tid(mut self, tid: u64) -> Self {
144 self.tid = Some(tid);
145 self
146 }
147
148 fn set_dropped(mut self, dropped: u64) -> Self {
149 self.dropped = dropped;
150 self
151 }
152
153 fn set_file(mut self, file: impl Into<String>) -> Self {
154 self.file = Some(file.into());
155 self
156 }
157
158 fn set_line(mut self, line: u64) -> Self {
159 self.line = Some(line);
160 self
161 }
162
163 fn set_message(mut self, msg: impl Into<String>) -> Self {
164 self.message = Some(msg.into());
165 self
166 }
167
168 fn add_kvp(mut self, kvp: &Argument<'_>) -> Self {
169 if !self.kvps.is_empty() {
170 self.kvps.push(' ');
171 }
172
173 self.kvps.push_str(kvp.name());
174 self.kvps.push('=');
175 match kvp.value() {
176 Value::Text(value) => {
177 self.kvps.push('"');
178 escape_quotes(&value, &mut self.kvps);
179 self.kvps.push('"');
180 }
181 Value::SignedInt(value) => {
182 write!(self.kvps, "{value}").unwrap();
183 }
184 Value::UnsignedInt(value) => {
185 write!(self.kvps, "{value}").unwrap();
186 }
187 Value::Floating(value) => {
188 write!(self.kvps, "{value}").unwrap();
189 }
190 Value::Boolean(value) => {
191 if value {
192 write!(self.kvps, "true").unwrap();
193 } else {
194 write!(self.kvps, "false").unwrap();
195 }
196 }
197 }
198 self
199 }
200
201 fn set_moniker(mut self, value: &str) -> Self {
202 self.moniker = Some(BumpaloString::from_str_in(value, self.allocator));
203 self
204 }
205
206 pub fn build(mut self) -> &'a mut LogMessage<'a> {
207 let allocator = self.allocator;
208
209 let msg_str = self
211 .message
212 .as_ref()
213 .map(|value| bumpalo::format!(in &allocator,"{value}",))
214 .unwrap_or_else(|| BumpaloString::new_in(allocator));
215
216 let mut output = match (&self.file, &self.line) {
217 (Some(file), Some(line)) => {
218 let mut value = bumpalo::format!(in &allocator, "[{file}({line})]",);
219 if !msg_str.is_empty() {
220 value.push(' ');
221 }
222 value
223 }
224 _ => BumpaloString::new_in(allocator),
225 };
226
227 output.push_str(&msg_str);
228 if !msg_str.is_empty() && !self.kvps.is_empty() {
229 output.push(' ');
230 }
231 output.push_str(&self.kvps);
232
233 if let Some(moniker) = &self.moniker {
234 let component_name = moniker.split("/").last();
235 if let Some(component_name) = component_name
236 && !self.tags.iter().any(|value| value.as_str() == component_name)
237 {
238 self.tags.insert(0, bumpalo::format!(in &allocator, "{}", component_name));
239 }
240 }
241
242 let tags: &[_] =
243 self.allocator.alloc_slice_fill_iter(self.tags.drain(..).map(|s| s.into()));
244
245 allocator.alloc(LogMessage {
246 severity: self.severity,
247 dropped: self.dropped,
248 tags: tags.into(),
249 pid: self.pid.unwrap_or(0),
250 tid: self.tid.unwrap_or(0),
251 message: output.into(),
252 timestamp: self.timestamp,
253 })
254 }
255}
256
257struct CPPLogMessageBuilderBuilder<'a>(&'a Bump);
258
259impl<'a> CPPLogMessageBuilderBuilder<'a> {
260 fn configure(
261 self,
262 _component_url: Option<FlyStr>,
263 moniker: Option<ExtendedMoniker>,
264 severity: Severity,
265 timestamp: BootInstant,
266 ) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
267 Ok(CPPLogMessageBuilder {
268 severity: severity as u8,
269 tags: BumpaloVec::new_in(self.0),
270 pid: None,
271 tid: None,
272 dropped: 0,
273 file: None,
274 timestamp: timestamp.into_nanos(),
275 line: None,
276 allocator: self.0,
277 kvps: String::new(),
278 moniker: moniker.map(|value| bumpalo::format!(in self.0,"{}", value)),
279 message: None,
280 })
281 }
282}
283
284pub fn build_logs_data<'a>(
285 input: &Record<'_>,
286 source: Option<ExtendedMetadata>,
287 allocator: &'a Bump,
288) -> Result<&'a mut LogMessage<'a>, MessageError> {
289 let builder = CPPLogMessageBuilderBuilder(allocator);
290 let (raw_severity, severity) = Severity::parse_exact(input.severity);
291 let (maybe_moniker, maybe_url, _) = source
292 .map(|value| (Some(value.moniker), Some(value.url), Some(value.rolled_out_logs)))
293 .unwrap_or((None, None, None));
294 let mut builder =
295 builder.configure(maybe_url.map(FlyStr::new), None, severity, input.timestamp)?;
296 if let Some(moniker) = maybe_moniker {
297 builder = builder.set_moniker(moniker.as_ref());
298 }
299 if let Some(raw_severity) = raw_severity {
300 builder = builder.set_raw_severity(raw_severity);
301 }
302
303 for argument in input.arguments.iter() {
304 match argument {
305 Argument::Tag(tag) => {
306 builder = builder.add_tag(tag.as_ref());
307 }
308 Argument::Pid(pid) => {
309 builder = builder.set_pid(pid.raw_koid());
310 }
311 Argument::Tid(tid) => {
312 builder = builder.set_tid(tid.raw_koid());
313 }
314 Argument::Dropped(dropped) => {
315 builder = builder.set_dropped(*dropped);
316 }
317 Argument::File(file) => {
318 builder = builder.set_file(file.as_ref());
319 }
320 Argument::Line(line) => {
321 builder = builder.set_line(*line);
322 }
323 Argument::Message(msg) => {
324 builder = builder.set_message(msg.as_ref());
325 }
326 Argument::Other { value: _, name: _ } => builder = builder.add_kvp(argument),
327 }
328 }
329
330 Ok(builder.build())
331}
332
333pub fn ffi_from_extended_record<'a, 'b>(
339 bytes: &'a [u8],
340 allocator: &'b Bump,
341) -> Result<(&'b mut LogMessage<'b>, &'a [u8]), MessageError> {
342 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
343 let (source, new_remaining) = if remaining.len() >= 16 {
344 let moniker_len = u32::from_le_bytes(remaining[0..4].try_into().unwrap()) as usize;
345 let component_url_len = u32::from_le_bytes(remaining[4..8].try_into().unwrap()) as usize;
346 let rolled_out_logs = u64::from_le_bytes(remaining[8..16].try_into().unwrap());
347 let mut offset = 16;
348 let moniker = str::from_utf8(&remaining[offset..offset + moniker_len])?;
349 let moniker_padded_len = (moniker_len + 7) & !7;
350 offset += moniker_padded_len;
351 let url = str::from_utf8(&remaining[offset..offset + component_url_len])?;
352 let component_url_padded_len = (component_url_len + 7) & !7;
353 offset += component_url_padded_len;
354 (
355 Some(ExtendedMetadata {
356 moniker: ExtendedMoniker::parse_str(moniker)?,
357 url: url.into(),
358 rolled_out_logs,
359 }),
360 &remaining[offset..],
361 )
362 } else {
363 (None, remaining)
364 };
365 let record = build_logs_data(&input, source, allocator)?;
366 Ok((record, new_remaining))
367}
368
369pub struct CPPMessageFormatter<'a>(pub &'a Bump);
370impl<'a> MessageFormatter for &CPPMessageFormatter<'a> {
371 type Result = &'a mut LogMessage<'a>;
372
373 fn format(
374 &mut self,
375 record: &Record<'_>,
376 metadata: Option<ExtendedMetadata>,
377 ) -> Result<Self::Result, MessageError> {
378 build_logs_data(record, metadata, self.0)
379 }
380}
381
382#[cfg(test)]
383mod test {
384 use super::*;
385 use crate::MessageParser;
386 use bumpalo::Bump;
387 use diagnostics_log_encoding::encode::{Encoder, EncoderOpts};
388 use diagnostics_log_encoding::{Argument, Header, LOG_CONTROL_BIT, Record};
389 use std::io::Cursor;
390 use zx::BootInstant;
391
392 fn overwrite_header_tag(bytes: &mut [u8], tag: u32) {
393 if bytes.len() >= 8 {
394 let mut header = Header(u64::from_le_bytes(bytes[0..8].try_into().unwrap()));
395 header.set_tag(tag);
396 bytes[0..8].copy_from_slice(&header.0.to_le_bytes());
397 }
398 }
399
400 #[fuchsia::test]
401 fn test_short_read() {
402 let mut parser = MessageParser::default();
403 let allocator = Bump::new();
404 let formatter = CPPMessageFormatter(&allocator);
405 let bytes = vec![0u8; 7];
406 let res = parser.parse_next(&bytes, &formatter);
407 assert!(matches!(res, Err(MessageError::ShortRead { len: 7 })));
408 }
409
410 #[fuchsia::test]
411 fn test_normal_parsing() {
412 let mut parser = MessageParser::default();
413 let allocator = Bump::new();
414 let formatter = CPPMessageFormatter(&allocator);
415
416 let record = Record {
417 timestamp: BootInstant::from_nanos(72),
418 severity: 0x30,
419 arguments: vec![Argument::message("hello world")],
420 };
421 let mut buffer = Cursor::new(vec![0u8; 1024]);
422 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
423 encoder.write_record(record).unwrap();
424
425 let len = buffer.position() as usize;
426 let mut bytes = buffer.into_inner();
427 bytes.truncate(len);
428
429 let res = parser.parse_next(&bytes, &formatter).unwrap();
430 assert!(res.0.is_some());
431 let log_message = res.0.unwrap();
432 assert_eq!(unsafe { log_message.message.as_utf8_str() }, "hello world");
433 assert_eq!(log_message.timestamp, 72);
434 assert_eq!(log_message.severity, 0x30);
435 }
436
437 #[fuchsia::test]
438 fn test_escaping_in_kvp() {
439 let mut parser = MessageParser::default();
440 let allocator = Bump::new();
441 let formatter = CPPMessageFormatter(&allocator);
442
443 let record = Record {
444 timestamp: BootInstant::from_nanos(72),
445 severity: 0x30,
446 arguments: vec![
447 Argument::message("hello world"),
448 Argument::new("key", r#"val"with\escapes"#),
449 ],
450 };
451 let mut buffer = Cursor::new(vec![0u8; 1024]);
452 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
453 encoder.write_record(record).unwrap();
454
455 let len = buffer.position() as usize;
456 let mut bytes = buffer.into_inner();
457 bytes.truncate(len);
458
459 let res = parser.parse_next(&bytes, &formatter).unwrap();
460 assert!(res.0.is_some());
461 let log_message = &res.0.unwrap();
462 assert_eq!(
463 unsafe { log_message.message.as_utf8_str() },
464 r#"hello world key="val\"with\\escapes""#
465 );
466 }
467
468 #[fuchsia::test]
469 fn test_control_message_tags() {
470 let allocator = Bump::new();
471 let formatter = CPPMessageFormatter(&allocator);
472 let mut parser = MessageParser::default();
473
474 let tag_id = 0;
475
476 let control_record = Record {
477 timestamp: BootInstant::from_nanos(72),
478 severity: 0x30,
479 arguments: vec![
480 Argument::new("moniker", "test/moniker"),
481 Argument::new("url", "fuchsia-pkg://test"),
482 ],
483 };
484
485 let mut buffer = Cursor::new(vec![0u8; 1024]);
486 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
487 encoder.write_record(control_record).unwrap();
488
489 let len = buffer.position() as usize;
490 let mut bytes = buffer.into_inner();
491 bytes.truncate(len);
492
493 overwrite_header_tag(&mut bytes, LOG_CONTROL_BIT);
494
495 let (log, _) = parser.parse_next(&bytes, &formatter).unwrap();
496 assert!(log.is_none());
497
498 let tag_data = parser.tag_map.get(&tag_id).unwrap();
499 assert_eq!(tag_data.moniker, ExtendedMoniker::parse_str("test/moniker").unwrap());
500 assert_eq!(tag_data.url, "fuchsia-pkg://test");
501
502 let rolled_out_record = Record {
503 timestamp: BootInstant::from_nanos(73),
504 severity: 0x30,
505 arguments: vec![Argument::new("rolled_out", 5u64)],
506 };
507
508 let mut buffer2 = Cursor::new(vec![0u8; 1024]);
509 let mut encoder2 = Encoder::new(&mut buffer2, EncoderOpts::default());
510 encoder2.write_record(rolled_out_record).unwrap();
511
512 let len2 = buffer2.position() as usize;
513 let mut bytes2 = buffer2.into_inner();
514 bytes2.truncate(len2);
515
516 overwrite_header_tag(&mut bytes2, LOG_CONTROL_BIT);
517
518 let (log2, _) = parser.parse_next(&bytes2, &formatter).unwrap();
519 assert!(log2.is_some());
520
521 let tag_data2 = parser.tag_map.get(&tag_id).unwrap();
522 assert_eq!(unsafe { log2.unwrap().message.as_utf8_str() }, "rolled_out=5");
523 assert_eq!(tag_data2.moniker, ExtendedMoniker::parse_str("test/moniker").unwrap());
524
525 let normal_record = Record {
526 timestamp: BootInstant::from_nanos(74),
527 severity: 0x30,
528 arguments: vec![Argument::message("some log with tag")],
529 };
530
531 let mut buffer3 = Cursor::new(vec![0u8; 1024]);
532 let mut encoder3 = Encoder::new(&mut buffer3, EncoderOpts::default());
533 encoder3.write_record(normal_record).unwrap();
534
535 let len3 = buffer3.position() as usize;
536 let mut bytes3 = buffer3.into_inner();
537 bytes3.truncate(len3);
538
539 overwrite_header_tag(&mut bytes3, tag_id);
540
541 let (log3, _) = parser.parse_next(&bytes3, &formatter).unwrap();
542
543 let log_msg3 = log3.unwrap();
544 assert_eq!(unsafe { log_msg3.message.as_utf8_str() }, "some log with tag");
545 assert_eq!(log_msg3.tags.len, 1);
546 let tag_str = unsafe { (*log_msg3.tags.ptr).as_utf8_str() };
547 assert_eq!(tag_str, "moniker");
548 }
549
550 #[fuchsia::test]
551 fn test_message_with_kvps() {
552 let mut parser = MessageParser::default();
553 let allocator = Bump::new();
554 let formatter = CPPMessageFormatter(&allocator);
555
556 let record = Record {
557 timestamp: BootInstant::from_nanos(100),
558 severity: 0x10,
559 arguments: vec![
560 Argument::message("A message"),
561 Argument::new("key1", "value1"),
562 Argument::new("key2", 123u64),
563 ],
564 };
565 let mut buffer = Cursor::new(vec![0u8; 1024]);
566 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
567 encoder.write_record(record).unwrap();
568
569 let len = buffer.position() as usize;
570 let mut bytes = buffer.into_inner();
571 bytes.truncate(len);
572
573 let res = parser.parse_next(&bytes, &formatter).unwrap();
574 assert!(res.0.is_some());
575 let log_message = res.0.unwrap();
576 assert_eq!(
577 unsafe { log_message.message.as_utf8_str() },
578 "A message key1=\"value1\" key2=123"
579 );
580 }
581
582 #[fuchsia::test]
583 fn test_file_line_message_with_kvps() {
584 let mut parser = MessageParser::default();
585 let allocator = Bump::new();
586 let formatter = CPPMessageFormatter(&allocator);
587
588 let record = Record {
589 timestamp: BootInstant::from_nanos(100),
590 severity: 0x10,
591 arguments: vec![
592 Argument::file("src/file.rs"),
593 Argument::line(42),
594 Argument::message("Another message"),
595 Argument::new("temp", 30.5),
596 Argument::new("valid", true),
597 ],
598 };
599 let mut buffer = Cursor::new(vec![0u8; 1024]);
600 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
601 encoder.write_record(record).unwrap();
602
603 let len = buffer.position() as usize;
604 let mut bytes = buffer.into_inner();
605 bytes.truncate(len);
606
607 let res = parser.parse_next(&bytes, &formatter).unwrap();
608 assert!(res.0.is_some());
609 let log_message = res.0.unwrap();
610 assert_eq!(
611 unsafe { log_message.message.as_utf8_str() },
612 "[src/file.rs(42)] Another message temp=30.5 valid=true"
613 );
614 }
615
616 #[fuchsia::test]
617 fn test_only_kvps() {
618 let mut parser = MessageParser::default();
619 let allocator = Bump::new();
620 let formatter = CPPMessageFormatter(&allocator);
621
622 let record = Record {
623 timestamp: BootInstant::from_nanos(100),
624 severity: 0x10,
625 arguments: vec![Argument::new("status", "ok"), Argument::new("code", 200i64)],
626 };
627 let mut buffer = Cursor::new(vec![0u8; 1024]);
628 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
629 encoder.write_record(record).unwrap();
630
631 let len = buffer.position() as usize;
632 let mut bytes = buffer.into_inner();
633 bytes.truncate(len);
634
635 let res = parser.parse_next(&bytes, &formatter).unwrap();
636 assert!(res.0.is_some());
637 let log_message = res.0.unwrap();
638 assert_eq!(unsafe { log_message.message.as_utf8_str() }, "status=\"ok\" code=200");
639 }
640}