1use crate::error::MessageError;
6use crate::MonikerWithUrl;
7use bumpalo::collections::{String as BumpaloString, Vec as BumpaloVec};
8use bumpalo::Bump;
9use diagnostics_data::{ExtendedMoniker, Severity};
10use diagnostics_log_encoding::{Argument, Record, Value};
11use flyweights::FlyStr;
12use std::str;
13use zx::BootInstant;
14
15pub use crate::constants::*;
16
17struct ArchivistArguments<'a> {
18 builder: CPPLogMessageBuilder<'a>,
19 archivist_argument_count: usize,
20 record: Record<'a>,
21}
22
23#[cfg(fuchsia_api_level_less_than = "HEAD")]
24fn parse_archivist_args<'a>(
25 builder: CPPLogMessageBuilder<'a>,
26 input: Record<'a>,
27) -> Result<ArchivistArguments<'a>, MessageError> {
28 Ok(ArchivistArguments { builder, archivist_argument_count: 0, record: input })
29}
30
31#[cfg(fuchsia_api_level_at_least = "HEAD")]
32fn parse_archivist_args<'a>(
33 mut builder: CPPLogMessageBuilder<'a>,
34 input: Record<'a>,
35) -> Result<ArchivistArguments<'a>, MessageError> {
36 let mut has_moniker = false;
37 let mut archivist_argument_count = 0;
38 for argument in input.arguments.iter().rev() {
39 match argument {
42 Argument::Other { value, name } => {
43 if name == fidl_fuchsia_diagnostics::MONIKER_ARG_NAME {
44 if let Value::Text(moniker) = value {
45 builder = builder.set_moniker(ExtendedMoniker::parse_str(moniker)?);
46 archivist_argument_count += 1;
47 has_moniker = true;
48 continue;
49 }
50 }
51 if name == fidl_fuchsia_diagnostics::COMPONENT_URL_ARG_NAME {
52 if let Value::Text(_) = value {
53 archivist_argument_count += 1;
54 continue;
55 }
56 }
57 if name == fidl_fuchsia_diagnostics::ROLLED_OUT_ARG_NAME {
58 if let Value::UnsignedInt(_) = value {
59 archivist_argument_count += 1;
60 continue;
61 }
62 }
63 break;
64 }
65 _ => break,
66 }
67 }
68 if !has_moniker {
69 return Err(MessageError::MissingMoniker);
70 }
71 Ok(ArchivistArguments { builder, archivist_argument_count, record: input })
72}
73
74#[repr(C)]
78pub struct CPPArray<T> {
79 pub len: usize,
81 pub ptr: *const T,
86}
87
88impl CPPArray<u8> {
89 pub unsafe fn as_utf8_str(&self) -> &str {
97 std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len))
98 }
99}
100
101impl From<&str> for CPPArray<u8> {
102 fn from(value: &str) -> Self {
103 CPPArray { len: value.len(), ptr: value.as_ptr() }
104 }
105}
106
107impl From<Option<&str>> for CPPArray<u8> {
108 fn from(value: Option<&str>) -> Self {
109 if let Some(value) = value {
110 CPPArray { len: value.len(), ptr: value.as_ptr() }
111 } else {
112 CPPArray { len: 0, ptr: std::ptr::null() }
113 }
114 }
115}
116
117impl<T> From<&Vec<T>> for CPPArray<T> {
118 fn from(value: &Vec<T>) -> Self {
119 CPPArray { len: value.len(), ptr: value.as_ptr() }
120 }
121}
122
123#[repr(C)]
125pub struct LogMessage<'a> {
126 severity: u8,
128 tags: CPPArray<CPPArray<u8>>,
130 pid: u64,
132 tid: u64,
134 dropped: u64,
136 message: CPPArray<u8>,
138 timestamp: i64,
141 builder: *mut CPPLogMessageBuilder<'a>,
144}
145
146impl Drop for LogMessage<'_> {
147 fn drop(&mut self) {
148 unsafe {
149 std::ptr::drop_in_place(self.builder);
154 }
155 }
156}
157
158pub struct CPPLogMessageBuilder<'a> {
159 severity: u8,
160 tags: BumpaloVec<'a, BumpaloString<'a>>,
161 pid: Option<u64>,
162 tid: Option<u64>,
163 dropped: u64,
164 file: Option<String>,
165 line: Option<u64>,
166 moniker: Option<BumpaloString<'a>>,
167 message: Option<String>,
168 timestamp: i64,
169 kvps: BumpaloVec<'a, Argument<'a>>,
170 allocator: &'a Bump,
171}
172
173fn escape_quotes(input: &str, output: &mut BumpaloString<'_>) {
175 for ch in input.chars() {
176 if ch == '\"' {
177 output.push('\\');
178 }
179 output.push(ch);
180 }
181}
182
183impl<'a> CPPLogMessageBuilder<'a> {
184 fn convert_string_vec(&self, strings: &[BumpaloString<'_>]) -> CPPArray<CPPArray<u8>> {
185 CPPArray {
186 len: strings.len(),
187 ptr: self
188 .allocator
189 .alloc_slice_fill_iter(strings.iter().map(|value| value.as_str().into()))
190 .as_ptr(),
191 }
192 }
193
194 fn set_raw_severity(mut self, raw_severity: u8) -> Self {
195 self.severity = raw_severity;
196 self
197 }
198
199 fn add_tag(mut self, tag: impl Into<String>) -> Self {
200 self.tags.push(BumpaloString::from_str_in(&tag.into(), self.allocator));
201 self
202 }
203
204 fn set_pid(mut self, pid: u64) -> Self {
205 self.pid = Some(pid);
206 self
207 }
208
209 fn set_tid(mut self, tid: u64) -> Self {
210 self.tid = Some(tid);
211 self
212 }
213
214 fn set_dropped(mut self, dropped: u64) -> Self {
215 self.dropped = dropped;
216 self
217 }
218
219 fn set_file(mut self, file: impl Into<String>) -> Self {
220 self.file = Some(file.into());
221 self
222 }
223
224 fn set_line(mut self, line: u64) -> Self {
225 self.line = Some(line);
226 self
227 }
228
229 fn set_message(mut self, msg: impl Into<String>) -> Self {
230 self.message = Some(msg.into());
231 self
232 }
233
234 fn add_kvp(mut self, kvp: Argument<'a>) -> Self {
235 self.kvps.push(kvp);
236 self
237 }
238 #[cfg(fuchsia_api_level_at_least = "HEAD")]
239 fn set_moniker(mut self, value: ExtendedMoniker) -> Self {
240 self.moniker = Some(bumpalo::format!(in self.allocator,"{}", value));
241 self
242 }
243 fn build(self) -> &'a mut LogMessage<'a> {
244 let allocator = self.allocator;
245 let builder = allocator.alloc(self);
246
247 let msg_str = builder
249 .message
250 .as_ref()
251 .map(|value| bumpalo::format!(in &allocator,"{value}",))
252 .unwrap_or_else(|| BumpaloString::new_in(allocator));
253
254 let mut kvp_str = BumpaloString::new_in(allocator);
255 for kvp in &builder.kvps {
256 kvp_str = bumpalo::format!(in &allocator, "{kvp_str} {}=", kvp.name());
257 match kvp.value() {
258 Value::Text(value) => {
259 kvp_str.push('"');
260 escape_quotes(&value, &mut kvp_str);
261 kvp_str.push('"');
262 }
263 Value::SignedInt(value) => {
264 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
265 }
266 Value::UnsignedInt(value) => {
267 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
268 }
269 Value::Floating(value) => {
270 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
271 }
272 Value::Boolean(value) => {
273 if value {
274 kvp_str.push_str("true");
275 } else {
276 kvp_str.push_str("false");
277 }
278 }
279 }
280 }
281
282 let mut output = match (&builder.file, &builder.line) {
283 (Some(file), Some(line)) => {
284 let mut value = bumpalo::format!(in &allocator, "[{file}({line})]",);
285 if !msg_str.is_empty() {
286 value.push(' ');
287 }
288 value
289 }
290 _ => BumpaloString::new_in(allocator),
291 };
292
293 output.push_str(&msg_str);
294 output.push_str(&kvp_str);
295
296 if let Some(moniker) = &builder.moniker {
297 let component_name = moniker.split("/").last();
298 if let Some(component_name) = component_name {
299 if !builder.tags.iter().any(|value| value.as_str() == component_name) {
300 builder.tags.insert(0, bumpalo::format!(in &allocator, "{}", component_name));
301 }
302 }
303 }
304
305 let log_message = LogMessage {
306 builder,
307 severity: builder.severity,
308 dropped: builder.dropped,
309 tags: builder.convert_string_vec(&builder.tags),
310 pid: builder.pid.unwrap_or(0),
311 tid: builder.tid.unwrap_or(0),
312 message: output.as_str().into(),
313 timestamp: builder.timestamp,
314 };
315
316 allocator.alloc(log_message)
317 }
318}
319
320struct CPPLogMessageBuilderBuilder<'a>(&'a Bump);
321
322impl<'a> CPPLogMessageBuilderBuilder<'a> {
323 fn configure(
324 self,
325 _component_url: Option<FlyStr>,
326 moniker: Option<ExtendedMoniker>,
327 severity: Severity,
328 timestamp: BootInstant,
329 ) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
330 Ok(CPPLogMessageBuilder {
331 severity: severity as u8,
332 tags: BumpaloVec::new_in(self.0),
333 pid: None,
334 tid: None,
335 dropped: 0,
336 file: None,
337 timestamp: timestamp.into_nanos(),
338 line: None,
339 allocator: self.0,
340 kvps: BumpaloVec::new_in(self.0),
341 moniker: moniker.map(|value| bumpalo::format!(in self.0,"{}", value)),
342 message: None,
343 })
344 }
345}
346
347fn build_logs_data<'a>(
348 input: Record<'a>,
349 source: Option<MonikerWithUrl>,
350 allocator: &'a Bump,
351 expect_extended_attribution: bool,
352) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
353 let builder = CPPLogMessageBuilderBuilder(allocator);
354 let (raw_severity, severity) = Severity::parse_exact(input.severity);
355 let (maybe_moniker, maybe_url) =
356 source.map(|value| (Some(value.moniker), Some(value.url))).unwrap_or((None, None));
357 let mut builder = builder.configure(maybe_url, maybe_moniker, severity, input.timestamp)?;
358 if let Some(raw_severity) = raw_severity {
359 builder = builder.set_raw_severity(raw_severity);
360 }
361 let (archivist_argument_count, input) = if !expect_extended_attribution {
362 (0, input)
363 } else {
364 let arguments = parse_archivist_args(builder, input)?;
365 builder = arguments.builder;
366 (arguments.archivist_argument_count, arguments.record)
367 };
368 let input_argument_len = input.arguments.len();
369 for argument in input.arguments.into_iter().take(input_argument_len - archivist_argument_count)
370 {
371 match argument {
372 Argument::Tag(tag) => {
373 builder = builder.add_tag(tag.as_ref());
374 }
375 Argument::Pid(pid) => {
376 builder = builder.set_pid(pid.raw_koid());
377 }
378 Argument::Tid(tid) => {
379 builder = builder.set_tid(tid.raw_koid());
380 }
381 Argument::Dropped(dropped) => {
382 builder = builder.set_dropped(dropped);
383 }
384 Argument::File(file) => {
385 builder = builder.set_file(file.as_ref());
386 }
387 Argument::Line(line) => {
388 builder = builder.set_line(line);
389 }
390 Argument::Message(msg) => {
391 builder = builder.set_message(msg.as_ref());
392 }
393 Argument::Other { value: _, name: _ } => builder = builder.add_kvp(argument),
394 }
395 }
396
397 Ok(builder)
398}
399
400pub fn ffi_from_extended_record<'a>(
406 bytes: &'a [u8],
407 allocator: &'a Bump,
408 source: Option<MonikerWithUrl>,
409 expect_extended_attribution: bool,
410) -> Result<(&'a mut LogMessage<'a>, &'a [u8]), MessageError> {
411 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
412 let record = build_logs_data(input, source, allocator, expect_extended_attribution)?.build();
413 Ok((record, remaining))
414}