1use crate::error::MessageError;
6use bumpalo::Bump;
7use bumpalo::collections::{String as BumpaloString, Vec as BumpaloVec};
8use diagnostics_data::{ExtendedMoniker, Severity};
9use diagnostics_log_encoding::{Argument, Record, Value};
10use flyweights::FlyStr;
11use std::str;
12use zx::BootInstant;
13
14pub use crate::constants::*;
15
16struct ExtendedMetadata<'a> {
17 moniker: &'a str,
18 url: &'a str,
19 rolled_out_logs: u64,
20}
21
22#[repr(C)]
26pub struct CPPArray<T> {
27 pub len: usize,
29 pub ptr: *const T,
34}
35
36impl CPPArray<u8> {
37 pub unsafe fn as_utf8_str(&self) -> &str {
45 std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len))
46 }
47}
48
49impl From<&str> for CPPArray<u8> {
50 fn from(value: &str) -> Self {
51 CPPArray { len: value.len(), ptr: value.as_ptr() }
52 }
53}
54
55impl From<Option<&str>> for CPPArray<u8> {
56 fn from(value: Option<&str>) -> Self {
57 if let Some(value) = value {
58 CPPArray { len: value.len(), ptr: value.as_ptr() }
59 } else {
60 CPPArray { len: 0, ptr: std::ptr::null() }
61 }
62 }
63}
64
65impl<T> From<&Vec<T>> for CPPArray<T> {
66 fn from(value: &Vec<T>) -> Self {
67 CPPArray { len: value.len(), ptr: value.as_ptr() }
68 }
69}
70
71#[repr(C)]
73pub struct LogMessage<'a> {
74 severity: u8,
76 tags: CPPArray<CPPArray<u8>>,
78 pid: u64,
80 tid: u64,
82 dropped: u64,
84 message: CPPArray<u8>,
86 timestamp: i64,
89 builder: *mut CPPLogMessageBuilder<'a>,
92}
93
94impl Drop for LogMessage<'_> {
95 fn drop(&mut self) {
96 unsafe {
97 std::ptr::drop_in_place(self.builder);
102 }
103 }
104}
105
106pub struct CPPLogMessageBuilder<'a> {
107 severity: u8,
108 tags: BumpaloVec<'a, BumpaloString<'a>>,
109 pid: Option<u64>,
110 tid: Option<u64>,
111 dropped: u64,
112 file: Option<String>,
113 line: Option<u64>,
114 moniker: Option<BumpaloString<'a>>,
115 message: Option<String>,
116 timestamp: i64,
117 kvps: BumpaloVec<'a, Argument<'a>>,
118 allocator: &'a Bump,
119}
120
121fn escape_quotes(input: &str, output: &mut BumpaloString<'_>) {
123 for ch in input.chars() {
124 if ch == '"' {
125 output.push('\\');
126 }
127 output.push(ch);
128 }
129}
130
131impl<'a> CPPLogMessageBuilder<'a> {
132 fn convert_string_vec(&self, strings: &[BumpaloString<'_>]) -> CPPArray<CPPArray<u8>> {
133 CPPArray {
134 len: strings.len(),
135 ptr: self
136 .allocator
137 .alloc_slice_fill_iter(strings.iter().map(|value| value.as_str().into()))
138 .as_ptr(),
139 }
140 }
141
142 fn set_raw_severity(mut self, raw_severity: u8) -> Self {
143 self.severity = raw_severity;
144 self
145 }
146
147 fn add_tag(mut self, tag: impl Into<String>) -> Self {
148 self.tags.push(BumpaloString::from_str_in(&tag.into(), self.allocator));
149 self
150 }
151
152 fn set_pid(mut self, pid: u64) -> Self {
153 self.pid = Some(pid);
154 self
155 }
156
157 fn set_tid(mut self, tid: u64) -> Self {
158 self.tid = Some(tid);
159 self
160 }
161
162 fn set_dropped(mut self, dropped: u64) -> Self {
163 self.dropped = dropped;
164 self
165 }
166
167 fn set_file(mut self, file: impl Into<String>) -> Self {
168 self.file = Some(file.into());
169 self
170 }
171
172 fn set_line(mut self, line: u64) -> Self {
173 self.line = Some(line);
174 self
175 }
176
177 fn set_message(mut self, msg: impl Into<String>) -> Self {
178 self.message = Some(msg.into());
179 self
180 }
181
182 fn add_kvp(mut self, kvp: Argument<'a>) -> Self {
183 self.kvps.push(kvp);
184 self
185 }
186
187 fn set_moniker(mut self, value: &str) -> Self {
188 self.moniker = Some(BumpaloString::from_str_in(value, self.allocator));
189 self
190 }
191
192 fn build(self) -> &'a mut LogMessage<'a> {
193 let allocator = self.allocator;
194 let builder = allocator.alloc(self);
195
196 let msg_str = builder
198 .message
199 .as_ref()
200 .map(|value| bumpalo::format!(in &allocator,"{value}",))
201 .unwrap_or_else(|| BumpaloString::new_in(allocator));
202
203 let mut kvp_str = BumpaloString::new_in(allocator);
204 for kvp in &builder.kvps {
205 kvp_str = bumpalo::format!(in &allocator, "{kvp_str} {}=", kvp.name());
206 match kvp.value() {
207 Value::Text(value) => {
208 kvp_str.push('"');
209 escape_quotes(&value, &mut kvp_str);
210 kvp_str.push('"');
211 }
212 Value::SignedInt(value) => {
213 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
214 }
215 Value::UnsignedInt(value) => {
216 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
217 }
218 Value::Floating(value) => {
219 kvp_str.push_str(&bumpalo::format!(in &allocator, "{}",value))
220 }
221 Value::Boolean(value) => {
222 if value {
223 kvp_str.push_str("true");
224 } else {
225 kvp_str.push_str("false");
226 }
227 }
228 }
229 }
230
231 let mut output = match (&builder.file, &builder.line) {
232 (Some(file), Some(line)) => {
233 let mut value = bumpalo::format!(in &allocator, "[{file}({line})]",);
234 if !msg_str.is_empty() {
235 value.push(' ');
236 }
237 value
238 }
239 _ => BumpaloString::new_in(allocator),
240 };
241
242 output.push_str(&msg_str);
243 output.push_str(&kvp_str);
244
245 if let Some(moniker) = &builder.moniker {
246 let component_name = moniker.split("/").last();
247 if let Some(component_name) = component_name {
248 if !builder.tags.iter().any(|value| value.as_str() == component_name) {
249 builder.tags.insert(0, bumpalo::format!(in &allocator, "{}", component_name));
250 }
251 }
252 }
253
254 let log_message = LogMessage {
255 builder,
256 severity: builder.severity,
257 dropped: builder.dropped,
258 tags: builder.convert_string_vec(&builder.tags),
259 pid: builder.pid.unwrap_or(0),
260 tid: builder.tid.unwrap_or(0),
261 message: output.as_str().into(),
262 timestamp: builder.timestamp,
263 };
264
265 allocator.alloc(log_message)
266 }
267}
268
269struct CPPLogMessageBuilderBuilder<'a>(&'a Bump);
270
271impl<'a> CPPLogMessageBuilderBuilder<'a> {
272 fn configure(
273 self,
274 _component_url: Option<FlyStr>,
275 moniker: Option<ExtendedMoniker>,
276 severity: Severity,
277 timestamp: BootInstant,
278 ) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
279 Ok(CPPLogMessageBuilder {
280 severity: severity as u8,
281 tags: BumpaloVec::new_in(self.0),
282 pid: None,
283 tid: None,
284 dropped: 0,
285 file: None,
286 timestamp: timestamp.into_nanos(),
287 line: None,
288 allocator: self.0,
289 kvps: BumpaloVec::new_in(self.0),
290 moniker: moniker.map(|value| bumpalo::format!(in self.0,"{}", value)),
291 message: None,
292 })
293 }
294}
295
296fn build_logs_data<'a>(
297 input: Record<'a>,
298 source: Option<ExtendedMetadata<'_>>,
299 allocator: &'a Bump,
300) -> Result<CPPLogMessageBuilder<'a>, MessageError> {
301 let builder = CPPLogMessageBuilderBuilder(allocator);
302 let (raw_severity, severity) = Severity::parse_exact(input.severity);
303 let (maybe_moniker, maybe_url, _) = source
304 .map(|value| (Some(value.moniker), Some(value.url), Some(value.rolled_out_logs)))
305 .unwrap_or((None, None, None));
306 let mut builder =
307 builder.configure(maybe_url.map(FlyStr::new), None, severity, input.timestamp)?;
308 if let Some(moniker) = maybe_moniker {
309 builder = builder.set_moniker(moniker);
310 }
311 if let Some(raw_severity) = raw_severity {
312 builder = builder.set_raw_severity(raw_severity);
313 }
314 for argument in input.arguments.into_iter() {
315 match argument {
316 Argument::Tag(tag) => {
317 builder = builder.add_tag(tag.as_ref());
318 }
319 Argument::Pid(pid) => {
320 builder = builder.set_pid(pid.raw_koid());
321 }
322 Argument::Tid(tid) => {
323 builder = builder.set_tid(tid.raw_koid());
324 }
325 Argument::Dropped(dropped) => {
326 builder = builder.set_dropped(dropped);
327 }
328 Argument::File(file) => {
329 builder = builder.set_file(file.as_ref());
330 }
331 Argument::Line(line) => {
332 builder = builder.set_line(line);
333 }
334 Argument::Message(msg) => {
335 builder = builder.set_message(msg.as_ref());
336 }
337 Argument::Other { value: _, name: _ } => builder = builder.add_kvp(argument),
338 }
339 }
340
341 Ok(builder)
342}
343
344pub fn ffi_from_extended_record<'a>(
350 bytes: &'a [u8],
351 allocator: &'a Bump,
352) -> Result<(&'a mut LogMessage<'a>, &'a [u8]), MessageError> {
353 let (input, remaining) = diagnostics_log_encoding::parse::parse_record(bytes)?;
354 let (source, new_remaining) = if remaining.len() >= 16 {
355 let moniker_len = u32::from_le_bytes(remaining[0..4].try_into().unwrap()) as usize;
356 let component_url_len = u32::from_le_bytes(remaining[4..8].try_into().unwrap()) as usize;
357 let rolled_out_logs = u64::from_le_bytes(remaining[8..16].try_into().unwrap());
358 let mut offset = 16;
359 let moniker = str::from_utf8(&remaining[offset..offset + moniker_len])?;
360 let moniker_padded_len = (moniker_len + 7) & !7;
361 offset += moniker_padded_len;
362 let url = str::from_utf8(&remaining[offset..offset + component_url_len])?;
363 let component_url_padded_len = (component_url_len + 7) & !7;
364 offset += component_url_padded_len;
365 (Some(ExtendedMetadata { moniker, url, rolled_out_logs }), &remaining[offset..])
366 } else {
367 (None, remaining)
368 };
369 let record = build_logs_data(input, source, allocator)?.build();
370 Ok((record, new_remaining))
371}