use crate::{
constants, ArgType, Argument, Header, Metatag, RawSeverity, Record, SeverityExt, Value,
};
use fidl_fuchsia_diagnostics::Severity;
use std::array::TryFromSliceError;
use std::borrow::{Borrow, Cow};
use std::fmt::Debug;
use std::io::Cursor;
use std::ops::Deref;
use thiserror::Error;
use tracing::{Event, Metadata, Subscriber};
use tracing_core::field::{Field, Visit};
use tracing_core::span;
use tracing_log::NormalizeEvent;
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::{LookupSpan, Scope};
pub struct Encoder<B> {
pub(crate) buf: B,
found_error: Option<EncodingError>,
options: EncoderOpts,
}
#[derive(Default)]
pub struct EncoderOpts {
pub always_log_file_line: bool,
}
pub struct WriteEventParams<'a, E, T, MS> {
pub event: E,
pub tags: &'a [T],
pub metatags: MS,
pub pid: zx::Koid,
pub tid: zx::Koid,
pub dropped: u64,
}
impl<B> Encoder<B>
where
B: MutableBuffer,
{
pub fn new(buf: B, options: EncoderOpts) -> Self {
Self { buf, found_error: None, options }
}
pub fn inner(&self) -> &B {
&self.buf
}
pub fn take(self) -> B {
self.buf
}
pub fn write_event<'a, E, MS, T>(
&mut self,
params: WriteEventParams<'a, E, T, MS>,
) -> Result<(), EncodingError>
where
E: RecordEvent,
MS: Iterator<Item = &'a Metatag>,
T: AsRef<str>,
{
let WriteEventParams { event, tags, metatags, pid, tid, dropped } = params;
let severity = event.raw_severity();
self.write_inner(event.timestamp(), severity, |this| {
this.write_raw_argument(constants::PID, pid.raw_koid())?;
this.write_raw_argument(constants::TID, tid.raw_koid())?;
if dropped > 0 {
this.write_raw_argument(constants::NUM_DROPPED, dropped)?;
}
if this.options.always_log_file_line || severity >= Severity::Error.into_primitive() {
if let Some(mut file) = event.file() {
let split = file.split("../");
file = split.last().unwrap();
this.write_raw_argument(constants::FILE, Value::Text(Cow::Borrowed(file)))?;
}
if let Some(line) = event.line() {
this.write_raw_argument(constants::LINE, line as u64)?;
}
}
for metatag in metatags {
match metatag {
Metatag::Target => this.write_raw_argument(constants::TAG, event.target())?,
}
}
event.write_arguments(this)?;
for tag in tags {
this.write_raw_argument(constants::TAG, tag.as_ref())?;
}
Ok(())
})?;
Ok(())
}
pub fn write_record<R>(&mut self, record: R) -> Result<(), EncodingError>
where
R: RecordFields,
{
self.write_inner(record.timestamp(), record.raw_severity(), |this| {
record.write_arguments(this)
})
}
fn write_inner<F>(
&mut self,
timestamp: zx::BootInstant,
severity: RawSeverity,
write_args: F,
) -> Result<(), EncodingError>
where
F: FnOnce(&mut Self) -> Result<(), EncodingError>,
{
let starting_idx = self.buf.cursor();
let header_slot = self.buf.put_slot(std::mem::size_of::<u64>())?;
self.write_i64(timestamp.into_nanos())?;
write_args(self)?;
let mut header = Header(0);
header.set_type(crate::TRACING_FORMAT_LOG_RECORD_TYPE);
header.set_severity(severity);
let length = self.buf.cursor() - starting_idx;
header.set_len(length);
assert_eq!(length % 8, 0, "all records must be written 8-byte aligned");
self.buf.fill_slot(header_slot, &header.0.to_le_bytes());
Ok(())
}
pub fn write_raw_argument(
&mut self,
name: &str,
value: impl WriteArgumentValue<B>,
) -> Result<(), EncodingError> {
self.inner_write_argument(move |header, encoder| {
encoder.write_argument_name(header, name)?;
value.write_value(header, encoder)?;
Ok(())
})
}
pub fn write_argument<'a>(
&mut self,
argument: impl Borrow<Argument<'a>>,
) -> Result<(), EncodingError> {
let argument = argument.borrow();
self.inner_write_argument(move |header, encoder| {
encoder.write_argument_name(header, argument.name())?;
argument.write_value(header, encoder)?;
Ok(())
})
}
fn write_argument_name(
&mut self,
header: &mut Header,
name: &str,
) -> Result<(), EncodingError> {
self.write_string(name)?;
header.set_name_ref(string_mask(name));
Ok(())
}
fn inner_write_argument(
&mut self,
cb: impl FnOnce(&mut Header, &mut Self) -> Result<(), EncodingError>,
) -> Result<(), EncodingError> {
let starting_idx = self.buf.cursor();
let header_slot = self.buf.put_slot(std::mem::size_of::<Header>())?;
let mut header = Header(0);
cb(&mut header, self)?;
let record_len = self.buf.cursor() - starting_idx;
assert_eq!(record_len % 8, 0, "arguments must be 8-byte aligned");
header.set_size_words((record_len / 8) as u16);
self.buf.fill_slot(header_slot, &header.0.to_le_bytes());
Ok(())
}
fn maybe_write_argument(
&mut self,
field: &Field,
value: impl WriteArgumentValue<B>,
) -> Result<(), EncodingError> {
let name = field.name();
if !matches!(name, "log.target" | "log.module_path" | "log.file" | "log.line") {
self.write_raw_argument(name, value)?;
}
Ok(())
}
fn write_u64(&mut self, n: u64) -> Result<(), EncodingError> {
self.buf.put_u64_le(n).map_err(|_| EncodingError::BufferTooSmall)
}
fn write_i64(&mut self, n: i64) -> Result<(), EncodingError> {
self.buf.put_i64_le(n).map_err(|_| EncodingError::BufferTooSmall)
}
fn write_f64(&mut self, n: f64) -> Result<(), EncodingError> {
self.buf.put_f64(n).map_err(|_| EncodingError::BufferTooSmall)
}
fn write_string(&mut self, src: &str) -> Result<(), EncodingError> {
self.write_bytes(src.as_bytes())
}
fn write_bytes(&mut self, src: &[u8]) -> Result<(), EncodingError> {
self.buf.put_slice(src).map_err(|_| EncodingError::BufferTooSmall)?;
unsafe {
let align = std::mem::size_of::<u64>();
let num_padding_bytes = (align - src.len() % align) % align;
self.buf.advance_cursor(num_padding_bytes);
}
Ok(())
}
}
mod private {
use super::*;
pub trait Sealed {}
impl Sealed for Value<'_> {}
impl Sealed for Argument<'_> {}
impl Sealed for u64 {}
impl Sealed for f64 {}
impl Sealed for i64 {}
impl Sealed for bool {}
impl Sealed for String {}
impl Sealed for &str {}
impl Sealed for Cow<'_, str> {}
}
pub trait WriteArgumentValue<B>: private::Sealed {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError>;
}
impl<B: MutableBuffer> WriteArgumentValue<B> for Argument<'_> {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
match self {
Self::Pid(value) | Self::Tid(value) => value.raw_koid().write_value(header, encoder),
Self::Line(value) | Self::Dropped(value) => value.write_value(header, encoder),
Self::Tag(value) | Self::File(value) | Self::Message(value) => {
value.write_value(header, encoder)
}
Self::Other { value, .. } => value.write_value(header, encoder),
}
}
}
impl<B: MutableBuffer> WriteArgumentValue<B> for i64 {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
header.set_type(ArgType::I64 as u8);
encoder.write_i64(*self)
}
}
impl<B: MutableBuffer> WriteArgumentValue<B> for u64 {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
header.set_type(ArgType::U64 as u8);
encoder.write_u64(*self)
}
}
impl<B: MutableBuffer> WriteArgumentValue<B> for f64 {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
header.set_type(ArgType::F64 as u8);
encoder.write_f64(*self)
}
}
impl<B: MutableBuffer> WriteArgumentValue<B> for bool {
fn write_value(
&self,
header: &mut Header,
_encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
header.set_type(ArgType::Bool as u8);
header.set_bool_val(*self);
Ok(())
}
}
impl<B: MutableBuffer> WriteArgumentValue<B> for &str {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
header.set_type(ArgType::String as u8);
header.set_value_ref(string_mask(self));
encoder.write_string(self)
}
}
impl<B: MutableBuffer> WriteArgumentValue<B> for String {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
self.as_str().write_value(header, encoder)
}
}
impl<B: MutableBuffer> WriteArgumentValue<B> for Cow<'_, str> {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
self.as_ref().write_value(header, encoder)
}
}
impl<B: MutableBuffer> WriteArgumentValue<B> for Value<'_> {
fn write_value(
&self,
header: &mut Header,
encoder: &mut Encoder<B>,
) -> Result<(), EncodingError> {
match self {
Value::SignedInt(s) => s.write_value(header, encoder),
Value::UnsignedInt(u) => u.write_value(header, encoder),
Value::Floating(f) => f.write_value(header, encoder),
Value::Text(t) => t.write_value(header, encoder),
Value::Boolean(b) => b.write_value(header, encoder),
}
}
}
fn string_mask(s: &str) -> u16 {
let len = s.len();
if len == 0 {
return 0;
}
(len as u16) | (1 << 15)
}
pub struct EncodedSpanArguments {
encoder: Encoder<Cursor<ResizableBuffer>>,
}
impl EncodedSpanArguments {
pub fn new(attrs: &span::Attributes<'_>) -> Result<Self, EncodingError> {
let mut encoder =
Encoder::new(Cursor::new(ResizableBuffer(Vec::new())), EncoderOpts::default());
attrs.record(&mut encoder);
if let Some(err) = encoder.found_error {
return Err(err);
}
Ok(Self { encoder })
}
pub fn from_record(record: &span::Record<'_>) -> Result<Self, EncodingError> {
let mut encoder =
Encoder::new(Cursor::new(ResizableBuffer(Vec::new())), EncoderOpts::default());
record.record(&mut encoder);
if let Some(err) = encoder.found_error {
return Err(err);
}
Ok(Self { encoder })
}
fn copy_to<B: MutableBuffer>(&self, encoder: &mut Encoder<B>) -> Result<(), EncodingError> {
let buffer = self.encoder.inner();
let end = buffer.cursor().min(buffer.get_ref().0.len());
encoder.write_bytes(&buffer.get_ref().0[..end])
}
}
impl<B: MutableBuffer> Visit for Encoder<B> {
fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
if let Err(err) = self.maybe_write_argument(field, format!("{value:?}").as_str()) {
self.found_error = Some(err);
};
}
fn record_str(&mut self, field: &Field, value: &str) {
if let Err(err) = self.maybe_write_argument(field, value) {
self.found_error = Some(err);
}
}
fn record_i64(&mut self, field: &Field, value: i64) {
if let Err(err) = self.maybe_write_argument(field, value) {
self.found_error = Some(err);
}
}
fn record_u64(&mut self, field: &Field, value: u64) {
if let Err(err) = self.maybe_write_argument(field, value) {
self.found_error = Some(err);
}
}
fn record_bool(&mut self, field: &Field, value: bool) {
if let Err(err) = self.maybe_write_argument(field, value) {
self.found_error = Some(err);
}
}
}
pub trait RecordEvent {
fn raw_severity(&self) -> RawSeverity;
fn file(&self) -> Option<&str>;
fn line(&self) -> Option<u32>;
fn target(&self) -> &str;
fn write_arguments<B: MutableBuffer>(
self,
writer: &mut Encoder<B>,
) -> Result<(), EncodingError>;
fn timestamp(&self) -> zx::BootInstant;
}
pub trait RecordFields {
fn raw_severity(&self) -> RawSeverity;
fn timestamp(&self) -> zx::BootInstant;
fn write_arguments<B: MutableBuffer>(
self,
writer: &mut Encoder<B>,
) -> Result<(), EncodingError>;
}
pub struct TracingEvent<'a, S> {
event: &'a Event<'a>,
context: Option<Context<'a, S>>,
metadata: StoredMetadata<'a>,
timestamp: zx::BootInstant,
}
enum StoredMetadata<'a> {
Borrowed(&'a Metadata<'a>),
Owned(Metadata<'a>),
}
impl<'a> Deref for StoredMetadata<'a> {
type Target = Metadata<'a>;
fn deref(&self) -> &Self::Target {
match self {
Self::Borrowed(meta) => meta,
Self::Owned(meta) => meta,
}
}
}
impl<'a, S> TracingEvent<'a, S> {
pub fn new(event: &'a Event<'_>, context: Context<'a, S>) -> TracingEvent<'a, S> {
Self::inner(event, Some(context))
}
#[doc(hidden)]
pub fn from_event(event: &'a Event<'_>) -> TracingEvent<'a, S> {
Self::inner(event, None)
}
fn inner(event: &'a Event<'_>, context: Option<Context<'a, S>>) -> TracingEvent<'a, S> {
if let Some(metadata) = event.normalized_metadata() {
Self {
event,
context,
metadata: StoredMetadata::Owned(metadata),
timestamp: zx::BootInstant::get(),
}
} else {
Self {
event,
context,
metadata: StoredMetadata::Borrowed(event.metadata()),
timestamp: zx::BootInstant::get(),
}
}
}
}
impl<S> RecordEvent for TracingEvent<'_, S>
where
for<'lookup> S: Subscriber + LookupSpan<'lookup>,
{
fn raw_severity(&self) -> RawSeverity {
self.metadata.raw_severity()
}
fn file(&self) -> Option<&str> {
self.metadata.file()
}
fn line(&self) -> Option<u32> {
self.metadata.line()
}
fn target(&self) -> &str {
self.metadata.target()
}
fn timestamp(&self) -> zx::BootInstant {
self.timestamp
}
fn write_arguments<B: MutableBuffer>(
self,
writer: &mut Encoder<B>,
) -> Result<(), EncodingError> {
writer.found_error = None;
if let Some(context) = self.context {
let span_iter =
context.event_scope(self.event).map(Scope::from_root).into_iter().flatten();
for span in span_iter {
let extensions = span.extensions();
if let Some(encoded) = extensions.get::<EncodedSpanArguments>() {
encoded.copy_to(writer)?;
}
}
}
self.event.record(writer);
if let Some(err) = writer.found_error.take() {
return Err(err);
}
Ok(())
}
}
pub struct TestRecord<'a> {
pub severity: RawSeverity,
pub timestamp: zx::BootInstant,
pub file: Option<&'a str>,
pub line: Option<u32>,
pub record_arguments: Vec<Argument<'a>>,
}
impl TestRecord<'_> {
pub fn from<'a>(file: &'a str, line: u32, record: &'a Record<'a>) -> TestRecord<'a> {
TestRecord {
severity: record.severity,
timestamp: record.timestamp,
file: Some(file),
line: Some(line),
record_arguments: record.arguments.clone(),
}
}
}
impl RecordEvent for TestRecord<'_> {
fn raw_severity(&self) -> RawSeverity {
self.severity
}
fn file(&self) -> Option<&str> {
self.file
}
fn line(&self) -> Option<u32> {
self.line
}
fn target(&self) -> &str {
unimplemented!("Unused at the moment");
}
fn timestamp(&self) -> zx::BootInstant {
self.timestamp
}
fn write_arguments<B: MutableBuffer>(
self,
writer: &mut Encoder<B>,
) -> Result<(), EncodingError> {
for argument in self.record_arguments {
writer.write_argument(argument)?;
}
Ok(())
}
}
impl RecordFields for Record<'_> {
fn raw_severity(&self) -> RawSeverity {
self.severity
}
fn write_arguments<B: MutableBuffer>(
self,
writer: &mut Encoder<B>,
) -> Result<(), EncodingError> {
for arg in self.arguments {
writer.write_argument(arg)?;
}
Ok(())
}
fn timestamp(&self) -> zx::BootInstant {
self.timestamp
}
}
#[cfg(test)]
impl RecordFields for &Record<'_> {
fn raw_severity(&self) -> RawSeverity {
self.severity
}
fn write_arguments<B: MutableBuffer>(
self,
writer: &mut Encoder<B>,
) -> Result<(), EncodingError> {
for arg in &self.arguments {
writer.write_argument(arg)?;
}
Ok(())
}
fn timestamp(&self) -> zx::BootInstant {
self.timestamp
}
}
pub trait MutableBuffer {
fn capacity(&self) -> usize;
fn cursor(&self) -> usize;
unsafe fn advance_cursor(&mut self, n: usize);
unsafe fn put_slice_at(&mut self, src: &[u8], offset: usize);
fn has_remaining(&self, num_bytes: usize) -> bool;
fn put_slot(&mut self, width: usize) -> Result<WriteSlot, EncodingError> {
if self.has_remaining(width) {
let slot = WriteSlot { range: self.cursor()..(self.cursor() + width) };
unsafe {
self.advance_cursor(width);
}
Ok(slot)
} else {
Err(EncodingError::BufferTooSmall)
}
}
fn fill_slot(&mut self, slot: WriteSlot, src: &[u8]) {
assert_eq!(
src.len(),
slot.range.end - slot.range.start,
"WriteSlots can only insert exactly-sized content into the buffer"
);
unsafe {
self.put_slice_at(src, slot.range.start);
}
}
fn put_slice(&mut self, src: &[u8]) -> Result<(), EncodingError> {
if self.has_remaining(src.len()) {
unsafe {
self.put_slice_at(src, self.cursor());
self.advance_cursor(src.len());
}
Ok(())
} else {
Err(EncodingError::NoCapacity)
}
}
fn put_u64_le(&mut self, n: u64) -> Result<(), EncodingError> {
self.put_slice(&n.to_le_bytes())
}
fn put_i64_le(&mut self, n: i64) -> Result<(), EncodingError> {
self.put_slice(&n.to_le_bytes())
}
fn put_f64(&mut self, n: f64) -> Result<(), EncodingError> {
self.put_slice(&n.to_bits().to_ne_bytes())
}
}
#[must_use]
pub struct WriteSlot {
range: std::ops::Range<usize>,
}
#[derive(Debug)]
pub struct ResizableBuffer(Vec<u8>);
impl From<Vec<u8>> for ResizableBuffer {
fn from(buf: Vec<u8>) -> Self {
Self(buf)
}
}
impl ResizableBuffer {
pub fn into_inner(self) -> Vec<u8> {
self.0
}
}
impl MutableBuffer for Cursor<ResizableBuffer> {
fn capacity(&self) -> usize {
self.get_ref().0.len()
}
fn cursor(&self) -> usize {
self.position() as usize
}
fn has_remaining(&self, _num_bytes: usize) -> bool {
true
}
unsafe fn advance_cursor(&mut self, n: usize) {
self.set_position(self.position() + n as u64);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
let this = &mut self.get_mut().0;
if offset < this.len() {
let available = this.len() - offset;
let min = available.min(to_put.len());
let dest = &mut this[offset..(offset + min)];
dest.copy_from_slice(&to_put[..min]);
if available < to_put.len() {
this.extend_from_slice(&to_put[available..]);
}
} else {
this.resize(offset, 0);
this.extend_from_slice(to_put);
}
}
}
impl<T: MutableBuffer + ?Sized> MutableBuffer for &mut T {
fn has_remaining(&self, num_bytes: usize) -> bool {
(**self).has_remaining(num_bytes)
}
fn capacity(&self) -> usize {
(**self).capacity()
}
fn cursor(&self) -> usize {
(**self).cursor()
}
unsafe fn advance_cursor(&mut self, n: usize) {
(**self).advance_cursor(n);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
(**self).put_slice_at(to_put, offset);
}
}
impl<T: MutableBuffer + ?Sized> MutableBuffer for Box<T> {
fn has_remaining(&self, num_bytes: usize) -> bool {
(**self).has_remaining(num_bytes)
}
fn capacity(&self) -> usize {
(**self).capacity()
}
fn cursor(&self) -> usize {
(**self).cursor()
}
unsafe fn advance_cursor(&mut self, n: usize) {
(**self).advance_cursor(n);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
(**self).put_slice_at(to_put, offset);
}
}
impl MutableBuffer for Cursor<Vec<u8>> {
fn has_remaining(&self, num_bytes: usize) -> bool {
(self.cursor() + num_bytes) <= self.capacity()
}
fn capacity(&self) -> usize {
self.get_ref().len()
}
fn cursor(&self) -> usize {
self.position() as usize
}
unsafe fn advance_cursor(&mut self, n: usize) {
self.set_position(self.position() + n as u64);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
let dest = &mut self.get_mut()[offset..(offset + to_put.len())];
dest.copy_from_slice(to_put);
}
}
impl MutableBuffer for Cursor<&mut [u8]> {
fn has_remaining(&self, num_bytes: usize) -> bool {
(self.cursor() + num_bytes) <= self.capacity()
}
fn capacity(&self) -> usize {
self.get_ref().len()
}
fn cursor(&self) -> usize {
self.position() as usize
}
unsafe fn advance_cursor(&mut self, n: usize) {
self.set_position(self.position() + n as u64);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
let dest = &mut self.get_mut()[offset..(offset + to_put.len())];
dest.copy_from_slice(to_put);
}
}
impl<const N: usize> MutableBuffer for Cursor<[u8; N]> {
fn has_remaining(&self, num_bytes: usize) -> bool {
(self.cursor() + num_bytes) <= self.capacity()
}
fn capacity(&self) -> usize {
self.get_ref().len()
}
fn cursor(&self) -> usize {
self.position() as usize
}
unsafe fn advance_cursor(&mut self, n: usize) {
self.set_position(self.position() + n as u64);
}
unsafe fn put_slice_at(&mut self, to_put: &[u8], offset: usize) {
let dest = &mut self.get_mut()[offset..(offset + to_put.len())];
dest.copy_from_slice(to_put);
}
}
#[derive(Debug, Error)]
pub enum EncodingError {
#[error("buffer is too small")]
BufferTooSmall,
#[error("unsupported value type")]
Unsupported,
#[error("the buffer has no remaining capacity")]
NoCapacity,
}
impl From<TryFromSliceError> for EncodingError {
fn from(_: TryFromSliceError) -> Self {
EncodingError::BufferTooSmall
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse::parse_record;
use std::sync::{LazyLock, Mutex};
use tracing::info_span;
use tracing_subscriber::layer::{Layer, SubscriberExt};
use tracing_subscriber::Registry;
#[fuchsia::test]
fn build_basic_record() {
let mut encoder = Encoder::new(Cursor::new([0u8; 1024]), EncoderOpts::default());
encoder
.write_event(WriteEventParams::<_, &str, _> {
event: TestRecord {
severity: Severity::Info as u8,
timestamp: zx::BootInstant::from_nanos(12345),
file: None,
line: None,
record_arguments: vec![],
},
tags: &[],
metatags: std::iter::empty(),
pid: zx::Koid::from_raw(0),
tid: zx::Koid::from_raw(0),
dropped: 0,
})
.expect("wrote event");
let (record, _) = parse_record(encoder.inner().get_ref()).expect("wrote valid record");
assert_eq!(
record,
Record {
timestamp: zx::BootInstant::from_nanos(12345),
severity: Severity::Info as u8,
arguments: vec![
Argument::pid(zx::Koid::from_raw(0)),
Argument::tid(zx::Koid::from_raw(0)),
]
}
);
}
#[fuchsia::test]
fn build_records_with_location() {
let mut encoder = Encoder::new(Cursor::new([0u8; 1024]), EncoderOpts::default());
encoder
.write_event(WriteEventParams::<_, &str, _> {
event: TestRecord {
severity: Severity::Error as u8,
timestamp: zx::BootInstant::from_nanos(12345),
file: Some("foo.rs"),
line: Some(10),
record_arguments: vec![],
},
tags: &[],
metatags: std::iter::empty(),
pid: zx::Koid::from_raw(0),
tid: zx::Koid::from_raw(0),
dropped: 0,
})
.expect("wrote event");
let (record, _) = parse_record(encoder.inner().get_ref()).expect("wrote valid record");
assert_eq!(
record,
Record {
timestamp: zx::BootInstant::from_nanos(12345),
severity: Severity::Error as u8,
arguments: vec![
Argument::pid(zx::Koid::from_raw(0)),
Argument::tid(zx::Koid::from_raw(0)),
Argument::file("foo.rs"),
Argument::line(10),
]
}
);
}
#[fuchsia::test]
fn build_record_with_dropped_count() {
let mut encoder = Encoder::new(Cursor::new([0u8; 1024]), EncoderOpts::default());
encoder
.write_event(WriteEventParams::<_, &str, _> {
event: TestRecord {
severity: Severity::Warn as u8,
timestamp: zx::BootInstant::from_nanos(12345),
file: None,
line: None,
record_arguments: vec![],
},
tags: &[],
metatags: std::iter::empty(),
pid: zx::Koid::from_raw(0),
tid: zx::Koid::from_raw(0),
dropped: 7,
})
.expect("wrote event");
let (record, _) = parse_record(encoder.inner().get_ref()).expect("wrote valid record");
assert_eq!(
record,
Record {
timestamp: zx::BootInstant::from_nanos(12345),
severity: Severity::Warn as u8,
arguments: vec![
Argument::pid(zx::Koid::from_raw(0)),
Argument::tid(zx::Koid::from_raw(0)),
Argument::dropped(7),
]
}
);
}
#[derive(Debug)]
struct PrintMe(#[allow(unused)] u32);
type ByteEncoder = Encoder<Cursor<[u8; 1024]>>;
static LAST_RECORD: LazyLock<Mutex<Option<ByteEncoder>>> = LazyLock::new(|| Mutex::new(None));
struct EncoderLayer;
impl<S> Layer<S> for EncoderLayer
where
for<'lookup> S: Subscriber + LookupSpan<'lookup>,
{
fn on_event(&self, event: &Event<'_>, cx: Context<'_, S>) {
let mut encoder = Encoder::new(Cursor::new([0u8; 1024]), EncoderOpts::default());
encoder
.write_event(WriteEventParams {
event: TracingEvent::new(event, cx),
tags: &["a-tag"],
metatags: [Metatag::Target].iter(),
pid: zx::Koid::from_raw(0),
tid: zx::Koid::from_raw(0),
dropped: 0,
})
.expect("wrote event");
*LAST_RECORD.lock().unwrap() = Some(encoder);
}
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
let span = ctx.span(id).expect("Span not found, this is a bug");
let mut extensions = span.extensions_mut();
if extensions.get_mut::<EncodedSpanArguments>().is_none() {
let encoded = EncodedSpanArguments::new(attrs).expect("encoded");
extensions.insert(encoded);
}
}
}
#[test]
fn build_record_from_tracing_event() {
let before_timestamp = zx::BootInstant::get();
let _s = tracing::subscriber::set_default(Registry::default().with(EncoderLayer));
tracing::info!(
is_a_str = "hahaha",
is_debug = ?PrintMe(5),
is_signed = -500,
is_unsigned = 1000u64,
is_bool = false,
"blarg this is a message"
);
let guard = LAST_RECORD.lock().unwrap();
let encoder = guard.as_ref().unwrap();
let (record, _) = parse_record(encoder.inner().get_ref()).expect("wrote valid record");
assert!(record.timestamp > before_timestamp);
assert_eq!(
record,
Record {
timestamp: record.timestamp,
severity: Severity::Info as u8,
arguments: vec![
Argument::pid(zx::Koid::from_raw(0)),
Argument::tid(zx::Koid::from_raw(0)),
Argument::tag("diagnostics_log_encoding_lib_test::encode::tests"),
Argument::message("blarg this is a message"),
Argument::other("is_a_str", "hahaha"),
Argument::other("is_debug", "PrintMe(5)"),
Argument::other("is_signed", -500),
Argument::other("is_unsigned", 1000u64),
Argument::other("is_bool", false),
Argument::tag("a-tag"),
]
}
);
}
#[test]
fn spans_are_supported() {
let before_timestamp = zx::BootInstant::get();
let _s = tracing::subscriber::set_default(Registry::default().with(EncoderLayer));
let span = info_span!("my span", tag = "span_tag", span_field = 42);
span.in_scope(|| {
let nested_span =
info_span!("my other span", tag = "nested_span_tag", nested_span_field = "hello");
nested_span.in_scope(|| {
tracing::info!(is_bool = true, "a log in spans");
});
});
let guard = LAST_RECORD.lock().unwrap();
let encoder = guard.as_ref().unwrap();
let (record, _) = parse_record(encoder.inner().get_ref()).expect("wrote valid record");
assert!(record.timestamp > before_timestamp);
assert_eq!(
record,
Record {
timestamp: record.timestamp,
severity: Severity::Info as u8,
arguments: vec![
Argument::pid(zx::Koid::from_raw(0)),
Argument::tid(zx::Koid::from_raw(0)),
Argument::tag("diagnostics_log_encoding_lib_test::encode::tests"),
Argument::tag("span_tag"),
Argument::other("span_field", 42),
Argument::tag("nested_span_tag"),
Argument::other("nested_span_field", "hello"),
Argument::message("a log in spans"),
Argument::other("is_bool", true,),
Argument::tag("a-tag",),
]
}
);
}
#[test]
fn resizable_vec_mutable_buffer() {
let mut vec = Cursor::new(ResizableBuffer(vec![1u8, 2, 3]));
unsafe {
vec.put_slice_at(&[4, 5, 6], 3);
}
assert_eq!(vec.get_ref().0, vec![1, 2, 3, 4, 5, 6]);
let mut vec = Cursor::new(ResizableBuffer(vec![1, 3, 7, 9, 11, 13, 15]));
unsafe {
vec.put_slice_at(&[2, 4, 6], 2);
}
assert_eq!(vec.get_ref().0, vec![1, 3, 2, 4, 6, 13, 15]);
let mut vec = Cursor::new(ResizableBuffer(vec![1, 2, 3]));
unsafe {
vec.put_slice_at(&[4, 5, 6, 7], 0);
}
assert_eq!(vec.get_ref().0, vec![4, 5, 6, 7]);
let mut vec = Cursor::new(ResizableBuffer(vec![1, 2, 3]));
unsafe {
vec.put_slice_at(&[4, 5, 6], 5);
}
assert_eq!(vec.get_ref().0, vec![1, 2, 3, 0, 0, 4, 5, 6]);
}
}