use crate::{ArgType, Header, Metatag, SeverityExt, StringRef};
use fidl_fuchsia_diagnostics::Severity;
use fidl_fuchsia_diagnostics_stream as fstream;
use fuchsia_zircon as zx;
use std::{array::TryFromSliceError, borrow::Borrow, fmt::Debug, io::Cursor, ops::Deref};
use thiserror::Error;
use tracing::{Event, Metadata, Subscriber};
use tracing_core::{
field::{Field, Visit},
span,
};
use tracing_log::NormalizeEvent;
use tracing_subscriber::{
layer::Context,
registry::{LookupSpan, Scope},
};
pub struct Encoder<B> {
pub(crate) buf: B,
found_error: Option<EncodingError>,
}
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: u32,
}
impl<B> Encoder<B>
where
B: MutableBuffer,
{
pub fn new(buf: B) -> Self {
Self { buf, found_error: None }
}
pub fn inner(&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.severity();
self.write_inner(event.timestamp(), severity, |this| {
this.write_argument(Argument { name: "pid", value: pid.into() })?;
this.write_argument(Argument { name: "tid", value: tid.into() })?;
if dropped > 0 {
this.write_argument(Argument { name: "num_dropped", value: dropped.into() })?;
}
if severity >= Severity::Error.into_primitive() {
if let Some(mut file) = event.file() {
let split = file.split("../");
file = split.last().unwrap();
this.write_argument(Argument { name: "file", value: Value::Text(file) })?;
}
if let Some(line) = event.line() {
this.write_argument(Argument { name: "line", value: line.into() })?;
}
}
for metatag in metatags {
match metatag {
Metatag::Target => this.write_argument(Argument {
name: "tag",
value: Value::Text(event.target()),
})?,
}
}
event.write_arguments(this)?;
for tag in tags {
this.write_argument(Argument { name: "tag", value: Value::Text(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.severity(), |this| record.write_arguments(this))
}
fn write_inner<F>(
&mut self,
timestamp: zx::Time,
severity: fstream::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_argument<'a>(
&mut self,
argument: impl Borrow<Argument<'a>>,
) -> Result<(), EncodingError> {
let argument = argument.borrow();
let starting_idx = self.buf.cursor();
let header_slot = self.buf.put_slot(std::mem::size_of::<Header>())?;
let mut header = Header(0);
self.write_string(argument.name)?;
header.set_name_ref(StringRef::for_str(argument.name).mask());
match &argument.value {
Value::SignedInt(s) => {
header.set_type(ArgType::I64 as u8);
self.write_i64(*s)
}
Value::UnsignedInt(u) => {
header.set_type(ArgType::U64 as u8);
self.write_u64(*u)
}
Value::Floating(f) => {
header.set_type(ArgType::F64 as u8);
self.write_f64(*f)
}
Value::Text(t) => {
header.set_type(ArgType::String as u8);
header.set_value_ref(StringRef::for_str(t).mask());
self.write_string(t)
}
Value::Boolean(b) => {
header.set_type(ArgType::Bool as u8);
header.set_bool_val(*b);
Ok(())
}
}?;
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<'a>(
&mut self,
field: &Field,
value: impl Into<Value<'a>>,
) -> Result<(), EncodingError> {
let name = field.name();
if !matches!(name, "log.target" | "log.module_path" | "log.file" | "log.line") {
self.write_argument(Argument { name, value: value.into() })?;
}
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(())
}
}
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())));
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())));
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);
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Argument<'a> {
pub name: &'a str,
pub value: Value<'a>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Value<'a> {
SignedInt(i64),
UnsignedInt(u64),
Floating(f64),
Boolean(bool),
Text(&'a str),
}
impl From<i64> for Value<'_> {
fn from(number: i64) -> Value<'static> {
Value::SignedInt(number)
}
}
impl From<u64> for Value<'_> {
fn from(number: u64) -> Value<'static> {
Value::UnsignedInt(number)
}
}
impl From<u32> for Value<'_> {
fn from(number: u32) -> Value<'static> {
Value::UnsignedInt(number as u64)
}
}
impl From<zx::Koid> for Value<'_> {
fn from(koid: zx::Koid) -> Value<'static> {
Value::UnsignedInt(koid.raw_koid())
}
}
impl From<f64> for Value<'_> {
fn from(number: f64) -> Value<'static> {
Value::Floating(number)
}
}
impl<'a> From<&'a str> for Value<'a> {
fn from(text: &'a str) -> Value<'a> {
Value::Text(text)
}
}
impl From<bool> for Value<'static> {
fn from(boolean: bool) -> Value<'static> {
Value::Boolean(boolean)
}
}
pub trait RecordEvent {
fn severity(&self) -> u8;
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::Time;
}
pub trait RecordFields {
fn severity(&self) -> u8;
fn timestamp(&self) -> zx::Time;
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::Time,
}
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::Time::get_monotonic(),
}
} else {
Self {
event,
context,
metadata: StoredMetadata::Borrowed(event.metadata()),
timestamp: zx::Time::get_monotonic(),
}
}
}
}
impl<'a, S> RecordEvent for TracingEvent<'a, S>
where
for<'lookup> S: Subscriber + LookupSpan<'lookup>,
{
fn severity(&self) -> fstream::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::Time {
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: fstream::RawSeverity,
pub timestamp: zx::Time,
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 fstream::Record) -> TestRecord<'a> {
TestRecord {
severity: record.severity,
timestamp: zx::Time::from_nanos(record.timestamp),
file: Some(file),
line: Some(line),
record_arguments: record.arguments.iter().map(Argument::from).collect(),
}
}
}
impl RecordEvent for TestRecord<'_> {
fn severity(&self) -> fstream::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::Time {
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 fstream::Record {
fn severity(&self) -> u8 {
self.severity
}
fn write_arguments<B: MutableBuffer>(
&self,
writer: &mut Encoder<B>,
) -> Result<(), EncodingError> {
for arg in &self.arguments {
writer.write_argument(Argument::from(arg))?;
}
Ok(())
}
fn timestamp(&self) -> zx::Time {
zx::Time::from_nanos(self.timestamp)
}
}
impl<'a> PartialEq<fstream::Argument> for Argument<'a> {
fn eq(&self, other: &fstream::Argument) -> bool {
let arg = Argument::from(other);
*self == arg
}
}
impl<'a> From<&'a fstream::Argument> for Argument<'a> {
fn from(arg: &'a fstream::Argument) -> Argument<'a> {
let value = match &arg.value {
fstream::Value::SignedInt(value) => Value::SignedInt(*value),
fstream::Value::UnsignedInt(value) => Value::UnsignedInt(*value),
fstream::Value::Floating(value) => Value::Floating(*value),
fstream::Value::Text(value) => Value::Text(value.as_str()),
fstream::Value::Boolean(value) => Value::Boolean(*value),
_ => unreachable!("we should have covered all values"),
};
Argument { name: arg.name.as_str(), value }
}
}
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)]
struct ResizableBuffer(Vec<u8>);
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<'a, T: MutableBuffer + ?Sized> MutableBuffer for &'a 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 once_cell::sync::Lazy;
use std::sync::Mutex;
use tracing::info_span;
use tracing_subscriber::{
layer::{Layer, SubscriberExt},
Registry,
};
#[fuchsia::test]
fn build_basic_record() {
let mut encoder = Encoder::new(Cursor::new([0u8; 1024]));
encoder
.write_event(WriteEventParams::<_, &str, _> {
event: TestRecord {
severity: Severity::Info.into_primitive(),
timestamp: zx::Time::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,
fstream::Record {
timestamp: 12345,
severity: Severity::Info.into_primitive(),
arguments: vec![
fstream::Argument {
name: "pid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "tid".to_string(),
value: fstream::Value::UnsignedInt(0)
}
]
}
);
}
#[fuchsia::test]
fn build_records_with_location() {
let mut encoder = Encoder::new(Cursor::new([0u8; 1024]));
encoder
.write_event(WriteEventParams::<_, &str, _> {
event: TestRecord {
severity: Severity::Error.into_primitive(),
timestamp: zx::Time::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,
fstream::Record {
timestamp: 12345,
severity: Severity::Error.into_primitive(),
arguments: vec![
fstream::Argument {
name: "pid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "tid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "file".to_string(),
value: fstream::Value::Text("foo.rs".into())
},
fstream::Argument {
name: "line".to_string(),
value: fstream::Value::UnsignedInt(10)
},
]
}
);
}
#[fuchsia::test]
fn build_record_with_dropped_count() {
let mut encoder = Encoder::new(Cursor::new([0u8; 1024]));
encoder
.write_event(WriteEventParams::<_, &str, _> {
event: TestRecord {
severity: Severity::Warn.into_primitive(),
timestamp: zx::Time::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,
fstream::Record {
timestamp: 12345,
severity: Severity::Warn.into_primitive(),
arguments: vec![
fstream::Argument {
name: "pid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "tid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "num_dropped".to_string(),
value: fstream::Value::UnsignedInt(7)
},
]
}
);
}
#[allow(dead_code)] #[derive(Debug)]
struct PrintMe(u32);
#[allow(clippy::type_complexity)]
static LAST_RECORD: Lazy<Mutex<Option<Encoder<Cursor<[u8; 1024]>>>>> =
Lazy::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]));
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::Time::get_monotonic().into_nanos();
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,
fstream::Record {
timestamp: record.timestamp,
severity: Severity::Info.into_primitive(),
arguments: vec![
fstream::Argument {
name: "pid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "tid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "tag".to_string(),
value: fstream::Value::Text(
"diagnostics_log_encoding_lib_test::encode::tests".into()
),
},
fstream::Argument {
name: "message".to_string(),
value: fstream::Value::Text("blarg this is a message".into())
},
fstream::Argument {
name: "is_a_str".to_string(),
value: fstream::Value::Text("hahaha".into())
},
fstream::Argument {
name: "is_debug".to_string(),
value: fstream::Value::Text("PrintMe(5)".into())
},
fstream::Argument {
name: "is_signed".to_string(),
value: fstream::Value::SignedInt(-500)
},
fstream::Argument {
name: "is_unsigned".to_string(),
value: fstream::Value::UnsignedInt(1000)
},
fstream::Argument {
name: "is_bool".to_string(),
value: fstream::Value::Boolean(false)
},
fstream::Argument {
name: "tag".to_string(),
value: fstream::Value::Text("a-tag".into(),)
},
]
}
);
}
#[test]
fn spans_are_supported() {
let before_timestamp = zx::Time::get_monotonic().into_nanos();
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,
fstream::Record {
timestamp: record.timestamp,
severity: Severity::Info.into_primitive(),
arguments: vec![
fstream::Argument {
name: "pid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "tid".to_string(),
value: fstream::Value::UnsignedInt(0)
},
fstream::Argument {
name: "tag".to_string(),
value: fstream::Value::Text(
"diagnostics_log_encoding_lib_test::encode::tests".into()
),
},
fstream::Argument {
name: "tag".to_string(),
value: fstream::Value::Text("span_tag".into(),),
},
fstream::Argument {
name: "span_field".to_string(),
value: fstream::Value::SignedInt(42),
},
fstream::Argument {
name: "tag".to_string(),
value: fstream::Value::Text("nested_span_tag".into(),),
},
fstream::Argument {
name: "nested_span_field".to_string(),
value: fstream::Value::Text("hello".into()),
},
fstream::Argument {
name: "message".to_string(),
value: fstream::Value::Text("a log in spans".into())
},
fstream::Argument {
name: "is_bool".to_string(),
value: fstream::Value::Boolean(true)
},
fstream::Argument {
name: "tag".to_string(),
value: fstream::Value::Text("a-tag".into(),)
},
]
}
);
}
#[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]);
}
}