blob: 549b521498e665b1b49f03faa8183ab02d3c1d70 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::error::MessageError;
use byteorder::{ByteOrder, LittleEndian};
use diagnostics_data::{
BuilderArgs, LegacySeverity, LogsData, LogsDataBuilder, LogsField, LogsProperty, Severity,
};
use diagnostics_log_encoding::{Argument, Value, ValueUnknown};
use fuchsia_zircon as zx;
use libc::{c_char, c_int};
use serde::Serialize;
use std::{mem, str};
mod constants;
pub mod error;
pub use constants::*;
#[cfg(test)]
mod test;
#[derive(Clone, Serialize)]
pub struct MonikerWithUrl {
pub moniker: String,
pub url: String,
}
/// Transforms the given legacy log message (already parsed) into a `LogsData` containing the
/// given identity information.
pub fn from_logger(source: MonikerWithUrl, msg: LoggerMessage) -> LogsData {
let mut builder = LogsDataBuilder::new(BuilderArgs {
timestamp_nanos: msg.timestamp.into(),
component_url: Some(source.url),
moniker: source.moniker,
severity: msg.severity,
})
.set_pid(msg.pid)
.set_tid(msg.tid)
.set_dropped(msg.dropped_logs)
.set_message(msg.message);
for tag in &msg.tags {
builder = builder.add_tag(tag.as_ref());
}
let mut result = builder.build();
if let Some(verbosity) = msg.verbosity {
result.set_raw_severity(verbosity);
}
result
}
/// Constructs a `LogsData` from the provided bytes, assuming the bytes
/// are in the format specified as in the [log encoding].
///
/// [log encoding] https://fuchsia.dev/fuchsia-src/development/logs/encodings
pub fn from_structured(source: MonikerWithUrl, bytes: &[u8]) -> Result<LogsData, MessageError> {
let (record, _) = diagnostics_log_encoding::parse::parse_record(bytes)?;
let mut builder = LogsDataBuilder::new(BuilderArgs {
timestamp_nanos: record.timestamp.into(),
component_url: Some(source.url),
moniker: source.moniker,
// NOTE: this severity is not final. Severity will be set after parsing the
// record.arguments
severity: Severity::Info,
});
// Raw value from the client that we don't trust (not yet sanitized)
let mut severity_untrusted = None;
for Argument { name, value } in record.arguments {
let label = LogsField::from(name);
match (value, label) {
(Value::SignedInt(v), LogsField::Dropped) => {
builder = builder.set_dropped(v as u64);
}
(Value::UnsignedInt(v), LogsField::Dropped) => {
builder = builder.set_dropped(v);
}
(Value::Floating(f), LogsField::Dropped) => {
return Err(MessageError::ExpectedInteger {
value: format!("{:?}", f),
found: "float",
})
}
(Value::Text(t), LogsField::Dropped) => {
return Err(MessageError::ExpectedInteger { value: t, found: "text" });
}
(Value::SignedInt(v), LogsField::RawSeverity) => {
severity_untrusted = Some(v);
}
(_, LogsField::RawSeverity) => {
return Err(MessageError::ExpectedInteger { value: "".into(), found: "other" });
}
(Value::Text(text), LogsField::Tag) => {
builder = builder.add_tag(text);
}
(_, LogsField::Tag) => {
return Err(MessageError::UnrecognizedValue);
}
(Value::UnsignedInt(v), LogsField::ProcessId) => {
builder = builder.set_pid(v);
}
(Value::UnsignedInt(v), LogsField::ThreadId) => {
builder = builder.set_tid(v);
}
(Value::Text(v), LogsField::Msg) => {
builder = builder.set_message(v);
}
(Value::Text(v), LogsField::FilePath) => {
builder = builder.set_file(v);
}
(Value::UnsignedInt(v), LogsField::LineNumber) => {
builder = builder.set_line(v);
}
(value, label) => {
builder = builder.add_key(match value {
Value::SignedInt(v) => LogsProperty::Int(label, v),
Value::UnsignedInt(v) => LogsProperty::Uint(label, v),
Value::Floating(v) => LogsProperty::Double(label, v),
Value::Text(v) => LogsProperty::String(label, v),
Value::Boolean(v) => LogsProperty::Bool(label, v),
ValueUnknown!() => return Err(MessageError::UnrecognizedValue),
})
}
}
}
let raw_severity = if severity_untrusted.is_some() {
let transcoded_i32: i32 = severity_untrusted.unwrap().to_string().parse().unwrap();
LegacySeverity::try_from(transcoded_i32)?
} else {
// Cast from the u8 to i8 since verbosity is signed
let transcoded_i32 = i8::from_le_bytes(record.severity.to_le_bytes()) as i32;
LegacySeverity::try_from(transcoded_i32)?
};
let (severity, verbosity) = raw_severity.for_structured();
builder = builder.set_severity(severity);
let mut result = builder.build();
if verbosity.is_some() {
result.set_raw_severity(verbosity.unwrap())
}
Ok(result)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LoggerMessage {
pub timestamp: i64,
pub severity: Severity,
pub verbosity: Option<i8>,
pub pid: u64,
pub tid: u64,
pub size_bytes: usize,
pub dropped_logs: u64,
pub message: Box<str>,
pub tags: Vec<Box<str>>,
}
/// Parse the provided buffer as if it implements the [logger/syslog wire format].
///
/// Note that this is distinct from the parsing we perform for the debuglog log, which also
/// takes a `&[u8]` and is why we don't implement this as `TryFrom`.
///
/// [logger/syslog wire format]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/ulib/syslog/include/lib/syslog/wire_format.h
impl TryFrom<&[u8]> for LoggerMessage {
type Error = MessageError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() < MIN_PACKET_SIZE {
return Err(MessageError::ShortRead { len: bytes.len() });
}
let terminator = bytes[bytes.len() - 1];
if terminator != 0 {
return Err(MessageError::NotNullTerminated { terminator });
}
let pid = LittleEndian::read_u64(&bytes[..8]);
let tid = LittleEndian::read_u64(&bytes[8..16]);
let timestamp = LittleEndian::read_i64(&bytes[16..24]);
let raw_severity = LittleEndian::read_i32(&bytes[24..28]);
let severity = LegacySeverity::try_from(raw_severity)?;
let dropped_logs = LittleEndian::read_u32(&bytes[28..METADATA_SIZE]) as u64;
// start reading tags after the header
let mut cursor = METADATA_SIZE;
let mut tag_len = bytes[cursor] as usize;
let mut tags = Vec::new();
while tag_len != 0 {
if tags.len() == MAX_TAGS {
return Err(MessageError::TooManyTags);
}
if tag_len > MAX_TAG_LEN - 1 {
return Err(MessageError::TagTooLong { index: tags.len(), len: tag_len });
}
if (cursor + tag_len + 1) > bytes.len() {
return Err(MessageError::OutOfBounds);
}
let tag_start = cursor + 1;
let tag_end = tag_start + tag_len;
let tag = String::from_utf8_lossy(&bytes[tag_start..tag_end]);
tags.push(tag.into());
cursor = tag_end;
tag_len = bytes[cursor] as usize;
}
let msg_start = cursor + 1;
let mut msg_end = cursor + 1;
while msg_end < bytes.len() {
if bytes[msg_end] > 0 {
msg_end += 1;
continue;
}
let message = String::from_utf8_lossy(&bytes[msg_start..msg_end]).into_owned();
let message_len = message.len();
let (severity, verbosity) = severity.for_structured();
let result = LoggerMessage {
timestamp,
severity,
verbosity,
message: message.into_boxed_str(),
pid,
tid,
dropped_logs,
tags,
size_bytes: cursor + message_len + 1,
};
return Ok(result);
}
Err(MessageError::OutOfBounds)
}
}
#[allow(non_camel_case_types)]
pub type fx_log_severity_t = c_int;
#[repr(C)]
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
pub struct fx_log_metadata_t {
pub pid: zx::sys::zx_koid_t,
pub tid: zx::sys::zx_koid_t,
pub time: zx::sys::zx_time_t,
pub severity: fx_log_severity_t,
pub dropped_logs: u32,
}
#[repr(C)]
#[derive(Clone)]
pub struct fx_log_packet_t {
pub metadata: fx_log_metadata_t,
// Contains concatenated tags and message and a null terminating character at
// the end.
// char(tag_len) + "tag1" + char(tag_len) + "tag2\0msg\0"
pub data: [c_char; MAX_DATAGRAM_LEN - METADATA_SIZE],
}
impl Default for fx_log_packet_t {
fn default() -> fx_log_packet_t {
fx_log_packet_t {
data: [0; MAX_DATAGRAM_LEN - METADATA_SIZE],
metadata: Default::default(),
}
}
}
impl fx_log_packet_t {
/// This struct has no padding bytes, but we can't use zerocopy because it needs const
/// generics to support arrays this large.
pub fn as_bytes(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(
(self as *const Self) as *const u8,
mem::size_of::<fx_log_packet_t>(),
)
}
}
/// Fills data with a single value for defined region.
pub fn fill_data(&mut self, region: std::ops::Range<usize>, with: c_char) {
self.data[region].iter_mut().for_each(|c| *c = with);
}
/// Copies bytes to data at specifies offset.
pub fn add_data<T: std::convert::TryInto<c_char> + Copy>(&mut self, offset: usize, bytes: &[T])
where
<T as std::convert::TryInto<c_char>>::Error: std::fmt::Debug,
{
self.data[offset..(offset + bytes.len())]
.iter_mut()
.enumerate()
.for_each(|(i, x)| *x = bytes[i].try_into().unwrap());
}
}
pub fn parse_basic_structured_info(bytes: &[u8]) -> Result<(i64, Severity), MessageError> {
let (record, _) = diagnostics_log_encoding::parse::parse_record(bytes)?;
let mut severity_untrusted = None;
for a in record.arguments {
let label = LogsField::from(a.name);
match (a.value, label) {
(Value::SignedInt(v), LogsField::RawSeverity) => {
severity_untrusted = Some(v);
break;
}
_ => {}
}
}
let raw_severity = if severity_untrusted.is_some() {
let transcoded_i32: i32 = severity_untrusted.unwrap().to_string().parse().unwrap();
LegacySeverity::try_from(transcoded_i32)?
} else {
// Cast from the u8 to i8 since verbosity is signed
let transcoded_i32 = i8::from_le_bytes(record.severity.to_le_bytes()) as i32;
LegacySeverity::try_from(transcoded_i32)?
};
let (severity, _) = raw_severity.for_structured();
Ok((record.timestamp, severity))
}