1use crate::identity::ComponentIdentity;
6use crate::logs::stats::LogStreamStats;
7use anyhow::Result;
8use bstr::{BStr, ByteSlice};
9use diagnostics_data::{LogsData, Severity};
10use diagnostics_log_encoding::encode::{
11 Encoder, EncoderOpts, EncodingError, MutableBuffer, RecordEvent, WriteEventParams,
12};
13use diagnostics_log_encoding::Argument;
14use diagnostics_message::LoggerMessage;
15use fidl_fuchsia_diagnostics_types as fdiagnostics;
16use fidl_fuchsia_logger::MAX_DATAGRAM_LEN_BYTES;
17use std::fmt::Debug;
18use std::io::Cursor;
19use std::sync::Arc;
20
21type RawSeverity = u8;
22
23#[derive(Debug, Eq, PartialEq)]
24pub struct StoredMessage {
25 bytes: Box<[u8]>,
26 severity: Severity,
27 timestamp: zx::BootInstant,
28}
29
30impl StoredMessage {
31 pub fn new(buf: Box<[u8]>, stats: &Arc<LogStreamStats>) -> Option<Self> {
32 match diagnostics_log_encoding::parse::basic_info(&buf) {
33 Ok((timestamp, severity)) => {
34 Some(StoredMessage { bytes: buf, severity: severity.into(), timestamp })
35 }
36 _ => {
37 stats.increment_invalid(buf.len());
38 None
39 }
40 }
41 }
42
43 pub fn from_legacy(buf: Box<[u8]>, stats: &Arc<LogStreamStats>) -> Option<Self> {
44 let Ok(LoggerMessage {
45 timestamp,
46 raw_severity,
47 message,
48 pid,
49 tid,
50 dropped_logs,
51 tags,
52 size_bytes: _,
53 }) = LoggerMessage::try_from(buf.as_ref())
54 else {
55 stats.increment_invalid(buf.len());
56 return None;
57 };
58 let mut encoder =
59 Encoder::new(Cursor::new([0u8; MAX_DATAGRAM_LEN_BYTES as _]), EncoderOpts::default());
60 let _ = encoder.write_event(WriteEventParams {
61 event: LegacyMessageRecord { severity: raw_severity, data: &message, timestamp },
62 tags: &tags,
63 metatags: std::iter::empty(),
64 pid: zx::Koid::from_raw(pid),
65 tid: zx::Koid::from_raw(tid),
66 dropped: dropped_logs,
67 });
68 let cursor = encoder.take();
69 let position = cursor.position() as usize;
70 let buf = cursor.get_ref();
71 Some(Self {
72 timestamp,
73 severity: Severity::from(raw_severity),
74 bytes: Box::from(&buf[..position]),
75 })
76 }
77
78 pub fn from_debuglog(record: zx::DebugLogRecord, dropped: u64) -> Self {
79 let mut data = record.data();
80 if let Some(b'\n') = data.last() {
81 data = &data[..data.len() - 1];
82 }
83
84 let severity = match record.severity {
85 zx::DebugLogSeverity::Trace => fdiagnostics::Severity::Trace,
86 zx::DebugLogSeverity::Debug => fdiagnostics::Severity::Debug,
87 zx::DebugLogSeverity::Warn => fdiagnostics::Severity::Warn,
88 zx::DebugLogSeverity::Error => fdiagnostics::Severity::Error,
89 zx::DebugLogSeverity::Fatal => fdiagnostics::Severity::Fatal,
90 zx::DebugLogSeverity::Unknown => fdiagnostics::Severity::Info,
91 zx::DebugLogSeverity::Info => {
92 const MAX_STRING_SEARCH_SIZE: usize = 170;
97 let last = data
98 .char_indices()
99 .nth(MAX_STRING_SEARCH_SIZE)
100 .map(|(i, _, _)| i)
101 .unwrap_or(data.len());
102 let early_contents = &data[..last];
103 if early_contents.contains_str("ERROR:") {
104 fdiagnostics::Severity::Error
105 } else if early_contents.contains_str("WARNING:") {
106 fdiagnostics::Severity::Warn
107 } else {
108 fdiagnostics::Severity::Info
109 }
110 }
111 };
112
113 let mut encoder =
114 Encoder::new(Cursor::new([0u8; MAX_DATAGRAM_LEN_BYTES as _]), EncoderOpts::default());
115 let _ = encoder.write_event(WriteEventParams {
116 event: DebugLogRecordEvent {
117 severity: severity.into_primitive(),
118 data,
119 timestamp: record.timestamp,
120 },
121 tags: &["klog"],
122 metatags: std::iter::empty(),
123 pid: record.pid,
124 tid: record.tid,
125 dropped,
126 });
127 let cursor = encoder.take();
128 let position = cursor.position() as usize;
129 let buf = cursor.get_ref();
130
131 Self {
132 bytes: Box::from(&buf[..position]),
133 severity: severity.into(),
134 timestamp: record.timestamp,
135 }
136 }
137
138 pub fn bytes(&self) -> &[u8] {
139 &self.bytes
140 }
141
142 pub fn size(&self) -> usize {
143 self.bytes.len()
144 }
145
146 pub fn severity(&self) -> Severity {
147 self.severity
148 }
149
150 pub fn timestamp(&self) -> zx::BootInstant {
151 self.timestamp
152 }
153
154 pub fn parse(&self, source: &ComponentIdentity) -> Result<LogsData> {
155 let mut data = diagnostics_message::from_structured(source.into(), &self.bytes)?;
156 match i8::from_le_bytes(data.metadata.raw_severity().to_le_bytes()) {
159 -1 => data.set_severity(Severity::Debug),
160 -2 => data.set_severity(Severity::Trace),
161 0 => data.set_severity(Severity::Info),
162 _ => {}
163 }
164 Ok(data)
165 }
166}
167
168struct LegacyMessageRecord<'a> {
169 severity: RawSeverity,
170 data: &'a str,
171 timestamp: zx::BootInstant,
172}
173
174impl RecordEvent for LegacyMessageRecord<'_> {
175 fn raw_severity(&self) -> RawSeverity {
176 self.severity
177 }
178
179 fn file(&self) -> Option<&str> {
180 None
181 }
182
183 fn line(&self) -> Option<u32> {
184 None
185 }
186
187 fn target(&self) -> &str {
188 ""
189 }
190
191 fn timestamp(&self) -> zx::BootInstant {
192 self.timestamp
193 }
194
195 fn write_arguments<B: MutableBuffer>(
196 self,
197 writer: &mut Encoder<B>,
198 ) -> Result<(), EncodingError> {
199 writer.write_argument(Argument::message(self.data))?;
200 Ok(())
201 }
202}
203
204struct DebugLogRecordEvent<'a> {
205 severity: RawSeverity,
206 data: &'a BStr,
207 timestamp: zx::BootInstant,
208}
209
210impl RecordEvent for DebugLogRecordEvent<'_> {
211 fn raw_severity(&self) -> RawSeverity {
212 self.severity
213 }
214
215 fn file(&self) -> Option<&str> {
216 None
217 }
218
219 fn line(&self) -> Option<u32> {
220 None
221 }
222
223 fn target(&self) -> &str {
224 ""
225 }
226
227 fn timestamp(&self) -> zx::BootInstant {
228 self.timestamp
229 }
230
231 fn write_arguments<B: MutableBuffer>(
232 self,
233 writer: &mut Encoder<B>,
234 ) -> Result<(), EncodingError> {
235 writer.write_argument(Argument::message(self.data.to_str_lossy().as_ref()))?;
236 Ok(())
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::logs::debuglog::KERNEL_IDENTITY;
244 use crate::logs::testing::TestDebugEntry;
245 use diagnostics_data::{BuilderArgs, LogsDataBuilder};
246 use fidl_fuchsia_logger::LogMessage;
247
248 #[fuchsia::test]
249 fn convert_debuglog_to_log_message_test() {
250 let klog = TestDebugEntry::new("test log".as_bytes());
251 let data = StoredMessage::from_debuglog(klog.record, 10).parse(&KERNEL_IDENTITY).unwrap();
252 assert_eq!(
253 data,
254 LogsDataBuilder::new(BuilderArgs {
255 timestamp: klog.record.timestamp,
256 component_url: Some(KERNEL_IDENTITY.url.clone()),
257 moniker: KERNEL_IDENTITY.moniker.clone(),
258 severity: Severity::Info,
259 })
260 .set_dropped(10)
261 .set_pid(klog.record.pid.raw_koid())
262 .set_tid(klog.record.tid.raw_koid())
263 .add_tag("klog")
264 .set_message("test log".to_string())
265 .build()
266 );
267 let log_message: LogMessage = data.into();
269 assert_eq!(
270 log_message,
271 LogMessage {
272 pid: klog.record.pid.raw_koid(),
273 tid: klog.record.tid.raw_koid(),
274 time: klog.record.timestamp,
275 severity: fdiagnostics::Severity::Info.into_primitive() as i32,
276 dropped_logs: 10,
277 tags: vec!["klog".to_string()],
278 msg: "test log".to_string(),
279 }
280 );
281
282 let klog = TestDebugEntry::new(&vec![b'a'; zx::sys::ZX_LOG_RECORD_DATA_MAX]);
284 let data = StoredMessage::from_debuglog(klog.record, 0).parse(&KERNEL_IDENTITY).unwrap();
285 assert_eq!(
286 data,
287 LogsDataBuilder::new(BuilderArgs {
288 timestamp: klog.record.timestamp,
289 component_url: Some(KERNEL_IDENTITY.url.clone()),
290 moniker: KERNEL_IDENTITY.moniker.clone(),
291 severity: Severity::Info,
292 })
293 .set_pid(klog.record.pid.raw_koid())
294 .set_tid(klog.record.tid.raw_koid())
295 .add_tag("klog")
296 .set_message(String::from_utf8(vec![b'a'; zx::sys::ZX_LOG_RECORD_DATA_MAX]).unwrap())
297 .build()
298 );
299
300 let klog = TestDebugEntry::new(&[]);
302 let data = StoredMessage::from_debuglog(klog.record, 0).parse(&KERNEL_IDENTITY).unwrap();
303 assert_eq!(
304 data,
305 LogsDataBuilder::new(BuilderArgs {
306 timestamp: klog.record.timestamp,
307 component_url: Some(KERNEL_IDENTITY.url.clone()),
308 moniker: KERNEL_IDENTITY.moniker.clone(),
309 severity: Severity::Info,
310 })
311 .set_pid(klog.record.pid.raw_koid())
312 .set_tid(klog.record.tid.raw_koid())
313 .add_tag("klog")
314 .set_message("".to_string())
315 .build()
316 );
317
318 let klog = TestDebugEntry::new(b"\x00\x9f\x92");
320 let data = StoredMessage::from_debuglog(klog.record, 0).parse(&KERNEL_IDENTITY).unwrap();
321 assert_eq!(data.msg().unwrap(), "\x00��");
322 }
323}