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 break;
52 }
53 _ => break,
54 }
55 }
56 if !has_moniker {
57 return Err(MessageError::MissingMoniker);
58 }
59 Ok(ArchivistArguments { builder, archivist_argument_count, record: input })
60}
61
62#[repr(C)]
66pub struct CPPArray<T> {
67 pub len: usize,
69 pub ptr: *const T,
74}
75
76impl CPPArray<u8> {
77 pub unsafe fn as_utf8_str(&self) -> &str {
85 std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len))
86 }
87}
88
89impl From<&str> for CPPArray<u8> {
90 fn from(value: &str) -> Self {
91 CPPArray { len: value.len(), ptr: value.as_ptr() }
92 }
93}
94
95impl From<Option<&str>> for CPPArray<u8> {
96 fn from(value: Option<&str>) -> Self {
97 if let Some(value) = value {
98 CPPArray { len: value.len(), ptr: value.as_ptr() }
99 } else {
100 CPPArray { len: 0, ptr: std::ptr::null() }
101 }
102 }
103}
104
105impl<T> From<&Vec<T>> for CPPArray<T> {
106 fn from(value: &Vec<T>) -> Self {
107 CPPArray { len: value.len(), ptr: value.as_ptr() }
108 }
109}
110
111#[repr(C)]
113pub struct LogMessage<'a> {
114 severity: u8,
116 tags: CPPArray<CPPArray<u8>>,
118 pid: u64,
120 tid: u64,
122 dropped: u64,
124 message: CPPArray<u8>,
126 timestamp: i64,
129 builder: *mut CPPLogMessageBuilder<'a>,
132}
133
134impl Drop for LogMessage<'_> {
135 fn drop(&mut self) {
136 unsafe {
137 std::ptr::drop_in_place(self.builder);
142 }
143 }
144}
145
146pub struct CPPLogMessageBuilder<'a> {
147 severity: u8,
148 tags: BumpaloVec<'a, BumpaloString<'a>>,
149 pid: Option<u64>,
150 tid: Option<u64>,
151 dropped: u64,
152 file: Option<String>,
153 line: Option<u64>,
154 moniker: Option<BumpaloString<'a>>,
155 message: Option<String>,
156 timestamp: i64,
157 kvps: BumpaloVec<'a, Argument<'a>>,
158 allocator: &'a Bump,
159}
160
161fn escape_quotes(input: &str, output: &mut BumpaloString<'_>) {
163 for ch in input.chars() {
164 if ch == '\"' {
165 output.push('\\');
166 }
167 output.push(ch);
168 }
169}
170
171impl<'a> CPPLogMessageBuilder<'a> {
172 fn convert_string_vec(&self, strings: &[BumpaloString<'_>]) -> CPPArray<CPPArray<u8>> {
173 CPPArray {
174 len: strings.len(),
175 ptr: self
176 .allocator
177 .alloc_slice_fill_iter(strings.iter().map(|value| value.as_str().into()))
178 .as_ptr(),
179 }
180 }
181
182 fn set_raw_severity(mut self, raw_severity: u8) -> Self {
183 self.severity = raw_severity;
184 self
185 }
186
187 fn add_tag(mut self, tag: impl Into<String>) -> Self {
188 self.tags.push(BumpaloString::from_str_in(&tag.into(), self.allocator));
189 self
190 }
191
192 fn set_pid(mut self, pid: u64) -> Self {
193 self.pid = Some(pid);
194 self
195 }
196
197 fn set_tid(mut self, tid: u64) -> Self {
198 self.tid = Some(tid);
199 self
200 }
201
202 fn set_dropped(mut self, dropped: u64) -> Self {
203 self.dropped = dropped;
204 self
205 }
206
207 fn set_file(mut self, file: impl Into<String>) -> Self {
208 self.file = Some(file.into());
209 self
210 }
211
212 fn set_line(mut self, line: u64) -> Self {
213 self.line = Some(line);
214 self
215 }
216
217 fn set_message(mut self, msg: impl Into<String>) -> Self {
218 self.message = Some(msg.into());
219 self
220 }
221
222 fn add_kvp(mut self, kvp: Argument<'a>) -> Self {
223 self.kvps.push(kvp);
224 self
225 }
226 #[cfg(fuchsia_api_level_at_least = "HEAD")]
227 fn set_moniker(mut self, value: ExtendedMoniker) -> Self {
228 self.moniker = Some(bumpalo::format!(in self.allocator,"{}", value));
229 self
230 }
231 fn build(self) -> &'a mut LogMessage<'a> {
232 let allocator = self.allocator;
233 let builder = allocator.alloc(self);
234
235 let msg_str = builder
237 .message
238 .as_ref()
239 .map(|value| bumpalo::format!(in &allocator,"{value}",))
240 .unwrap_or_else(|| BumpaloString::new_in(allocator));
241
242 let mut kvp_str = BumpaloString::new_in(allocator);
243 for kvp in &builder.kvps {
244 kvp_str = bumpalo::format!(in &allocator, "{kvp_str} {}=", kvp.name());
245 match kvp.value() {
246 Value::Text(value) => {
247 kvp_str.push('"');
248 escape_quotes(&value, &mut kvp_str);
249 kvp_str.push('"');
250 }
251 Value::SignedInt(value) => {
252 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
253 }
254 Value::UnsignedInt(value) => {
255 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
256 }
257 Value::Floating(value) => {
258 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
259 }
260 Value::Boolean(value) => {
261 if value {
262 kvp_str.push_str("true");
263 } else {
264 kvp_str.push_str("false");
265 }
266 }
267 }
268 }
269
270 let mut output = match (&builder.file, &builder.line) {
271 (Some(file), Some(line)) => {
272 let mut value = bumpalo::format!(in &allocator, "[{file}({line})]",);
273 if !msg_str.is_empty() {
274 value.push(' ');
275 }
276 value
277 }
278 _ => BumpaloString::new_in(allocator),
279 };
280
281 output.push_str(&msg_str);
282 output.push_str(&kvp_str);
283
284 if let Some(moniker) = &builder.moniker {
285 let component_name = moniker.split("/").last();
286 if let Some(component_name) = component_name {
287 if !builder.tags.iter().any(|value| value.as_str() == component_name) {
288 builder.tags.insert(0, bumpalo::format!(in &allocator, "{}", component_name));
289 }
290 }
291 }
292
293 let log_message = LogMessage {
294 builder,
295 severity: builder.severity,
296 dropped: builder.dropped,
297 tags: builder.convert_string_vec(&builder.tags),
298 pid: builder.pid.unwrap_or(0),
299 tid: builder.tid.unwrap_or(0),
300 message: output.as_str().into(),
301 timestamp: builder.timestamp,
302 };
303
304 allocator.alloc(log_message)
305 }
306}
307
308struct CPPLogMessageBuilderBuilder<'a>(&'a Bump);
309
310impl<'a> CPPLogMessageBuilderBuilder<'a> {
311 fn configure(
312 self,
313 _component_url: Option<FlyStr>,
314 moniker: Option<ExtendedMoniker>,
315 severity: Severity,
316 timestamp: BootInstant,
317 ) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
318 Ok(CPPLogMessageBuilder {
319 severity: severity as u8,
320 tags: BumpaloVec::new_in(self.0),
321 pid: None,
322 tid: None,
323 dropped: 0,
324 file: None,
325 timestamp: timestamp.into_nanos(),
326 line: None,
327 allocator: self.0,
328 kvps: BumpaloVec::new_in(self.0),
329 moniker: moniker.map(|value| bumpalo::format!(in self.0,"{}", value)),
330 message: None,
331 })
332 }
333}
334
335fn build_logs_data<'a>(
336 input: Record<'a>,
337 source: Option<MonikerWithUrl>,
338 allocator: &'a Bump,
339 expect_extended_attribution: bool,
340) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
341 let builder = CPPLogMessageBuilderBuilder(allocator);
342 let (raw_severity, severity) = Severity::parse_exact(input.severity);
343 let (maybe_moniker, maybe_url) =
344 source.map(|value| (Some(value.moniker), Some(value.url))).unwrap_or((None, None));
345 let mut builder = builder.configure(maybe_url, maybe_moniker, severity, input.timestamp)?;
346 if let Some(raw_severity) = raw_severity {
347 builder = builder.set_raw_severity(raw_severity);
348 }
349 let (archivist_argument_count, input) = if !expect_extended_attribution {
350 (0, input)
351 } else {
352 let arguments = parse_archivist_args(builder, input)?;
353 builder = arguments.builder;
354 (arguments.archivist_argument_count, arguments.record)
355 };
356 let input_argument_len = input.arguments.len();
357 for argument in input.arguments.into_iter().take(input_argument_len - archivist_argument_count)
358 {
359 match argument {
360 Argument::Tag(tag) => {
361 builder = builder.add_tag(tag.as_ref());
362 }
363 Argument::Pid(pid) => {
364 builder = builder.set_pid(pid.raw_koid());
365 }
366 Argument::Tid(tid) => {
367 builder = builder.set_tid(tid.raw_koid());
368 }
369 Argument::Dropped(dropped) => {
370 builder = builder.set_dropped(dropped);
371 }
372 Argument::File(file) => {
373 builder = builder.set_file(file.as_ref());
374 }
375 Argument::Line(line) => {
376 builder = builder.set_line(line);
377 }
378 Argument::Message(msg) => {
379 builder = builder.set_message(msg.as_ref());
380 }
381 Argument::Other { value: _, name: _ } => builder = builder.add_kvp(argument),
382 }
383 }
384
385 Ok(builder)
386}
387
388pub fn ffi_from_extended_record<'a>(
394 bytes: &'a [u8],
395 allocator: &'a Bump,
396 source: Option<MonikerWithUrl>,
397 expect_extended_attribution: bool,
398) -> Result<(&'a mut LogMessage<'a>, &'a [u8]), MessageError> {
399 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
400 let record = build_logs_data(input, source, allocator, expect_extended_attribution)?.build();
401 Ok((record, remaining))
402}