Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ opentelemetry = { version = "0.32.0", default-features = false }
opentelemetry-otlp = { version = "0.32.0", default-features = false }
opentelemetry_sdk = { version = "0.32.0", default-features = false }
pin-project = { version = "1.1.10" }
pollster = { version = "0.4.0" }
rand = { version = "0.10.1" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
Expand Down
2 changes: 1 addition & 1 deletion appenders/file/src/append.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::rotation::Rotation;

/// A builder to configure and create an [`File`] appender.
///
/// See [module-level documentation](super) for usage examples.
/// See the [crate documentation](super) for usage examples.
#[derive(Debug)]
pub struct FileBuilder {
builder: RollingFileWriterBuilder,
Expand Down
2 changes: 1 addition & 1 deletion appenders/syslog/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ mod unix_ext {

/// An appender that writes log records to syslog.
///
/// See [module-level documentation](self) for usage examples.
/// See the [crate documentation](self) for usage examples.
#[derive(Debug)]
pub struct Syslog {
sender: Mutex<SyslogSender>,
Expand Down
52 changes: 47 additions & 5 deletions core/src/kv.rs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we can ensure View and Borrowed Key/Value always Copy but I suggest it is the right design and we should keep it as much as possible.

Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,17 @@ pub trait Visitor {
fn visit(&mut self, key: KeyView, value: ValueView) -> Result<(), Error>;
}

impl<F> Visitor for F
where
F: FnMut(KeyView, ValueView) -> Result<(), Error>,
{
fn visit(&mut self, key: KeyView, value: ValueView) -> Result<(), Error> {
self(key, value)
}
}
Comment on lines +33 to +40

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly should be better. But this impl looks very common to support; as in this PR:

diag.visit(&mut |key: KeyView<'_>, value: ValueView<'_>| {
    assert_eq!(key.as_str(), "key");
    assert_eq!(value.to_str().unwrap(), "value");
    Ok(())
})
.unwrap();


/// A key in a key-value pair.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Key<'a>(RefStr<'a>);

impl fmt::Display for Key<'_> {
Expand Down Expand Up @@ -80,6 +89,19 @@ impl Key<'_> {
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct KeyOwned(Cow<'static, str>);

macro_rules! impl_key_owned_from {
($ty:ty) => {
impl From<$ty> for KeyOwned {
fn from(v: $ty) -> Self {
KeyOwned(Cow::from(v))
}
}
};
}

impl_key_owned_from!(&'static str);
impl_key_owned_from!(String);

impl Borrow<str> for KeyOwned {
fn borrow(&self) -> &str {
&self.0
Expand Down Expand Up @@ -117,7 +139,7 @@ impl KeyOwned {
}

/// A borrowed view of a key in a key-value pair.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct KeyView<'a>(RefStr<'a>);

impl Borrow<str> for KeyView<'_> {
Expand Down Expand Up @@ -341,10 +363,10 @@ impl<'a> Iterator for MapValueIter<'a> {
}

/// A borrowed value in a key-value pair.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct Value<'a>(ValueState<'a>);

#[derive(Clone)]
#[derive(Clone, Copy)]
enum ValueState<'a> {
None,
Bool(bool),
Expand Down Expand Up @@ -513,6 +535,26 @@ enum ValueOwnedState {
Map(Box<HashMap<KeyOwned, ValueOwned>>),
}

macro_rules! impl_value_owned_from {
($ty:ty, $new:ident) => {
impl From<$ty> for ValueOwned {
fn from(v: $ty) -> Self {
Self::$new(v)
}
}
};
}

impl_value_owned_from!(bool, bool);
impl_value_owned_from!(i64, i64);
impl_value_owned_from!(u64, u64);
impl_value_owned_from!(f64, f64);
impl_value_owned_from!(i128, i128);
impl_value_owned_from!(u128, u128);
impl_value_owned_from!(char, char);
impl_value_owned_from!(String, str);
impl_value_owned_from!(&'static str, str);

#[cfg(feature = "serde")]
impl serde::Serialize for ValueOwned {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Expand Down Expand Up @@ -614,7 +656,7 @@ impl ValueOwned {
}

/// A borrowed view of a value.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum ValueView<'a> {
/// The absence of a value.
Expand Down
1 change: 1 addition & 0 deletions diagnostics/task-local/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pin-project = { workspace = true }

[dev-dependencies]
log = { workspace = true }
pollster = { workspace = true }

[lints]
workspace = true
110 changes: 57 additions & 53 deletions diagnostics/task-local/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,33 @@
//! use logforth_diagnostic_task_local::FutureExt;
//!
//! let fut = async { log::info!("Hello, world!") };
//! fut.with_task_local_context([("key".into(), "value".into())]);
//! fut.with_task_local_context([("key", "value")]);
//! ```

#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]

use std::cell::RefCell;
use std::pin::Pin;
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;

use logforth_core::Diagnostic;
use logforth_core::Error;
use logforth_core::kv::Key;
use logforth_core::kv::Value;
use logforth_core::kv::KeyOwned;
use logforth_core::kv::ValueOwned;
use logforth_core::kv::Visitor;

type TaskLocalContext = Arc<[(KeyOwned, ValueOwned)]>;

thread_local! {
static TASK_LOCAL_MAP: RefCell<Vec<(String, String)>> = const { RefCell::new(Vec::new()) };
static TASK_LOCAL_MAP: RefCell<Vec<TaskLocalContext>> = const { RefCell::new(Vec::new()) };
}

/// A diagnostic that stores key-value pairs in a task-local context.
///
/// See [module-level documentation](self) for usage examples.
/// See the [crate documentation](self) for usage examples.
#[derive(Default, Debug, Clone, Copy)]
#[non_exhaustive]
pub struct TaskLocalDiagnostic {}
Expand All @@ -54,10 +57,10 @@ impl Diagnostic for TaskLocalDiagnostic {
fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> {
TASK_LOCAL_MAP.with(|map| {
let map = map.borrow();
for (key, value) in map.iter() {
let key = Key::borrowed(key.as_str());
let value = Value::str(value.as_str());
visitor.visit(key.view(), value.view())?;
for context in map.iter() {
for (key, value) in context.iter() {
visitor.visit(key.view(), value.view())?;
}
}
Ok(())
})
Expand All @@ -66,20 +69,19 @@ impl Diagnostic for TaskLocalDiagnostic {

/// An extension trait for futures to run them with a task-local context.
///
/// See [module-level documentation](self) for usage examples.
/// See the [crate documentation](self) for usage examples.
pub trait FutureExt: Future {
/// Run a future with a task-local context.
fn with_task_local_context(
self,
kvs: impl IntoIterator<Item = (String, String)>,
) -> impl Future<Output = Self::Output>
fn with_task_local_context<K, V, I>(self, kvs: I) -> impl Future<Output = Self::Output>
where
Self: Sized,
K: Into<KeyOwned>,
V: Into<ValueOwned>,
I: IntoIterator<Item = (K, V)>,
{
TaskLocalFuture {
future: Some(self),
context: kvs.into_iter().collect(),
}
let future = self;
let context = kvs.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
TaskLocalFuture { future, context }
}
}

Expand All @@ -88,8 +90,8 @@ impl<F: Future> FutureExt for F {}
#[pin_project::pin_project]
struct TaskLocalFuture<F> {
#[pin]
future: Option<F>,
context: Vec<(String, String)>,
future: F,
context: Arc<[(KeyOwned, ValueOwned)]>,
}

impl<F: Future> Future for TaskLocalFuture<F> {
Expand All @@ -98,45 +100,47 @@ impl<F: Future> Future for TaskLocalFuture<F> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();

let mut fut = this.future;
if let Some(future) = fut.as_mut().as_pin_mut() {
struct Guard {
n: usize,
}
let future = this.future;

impl Drop for Guard {
fn drop(&mut self) {
TASK_LOCAL_MAP.with(|map| {
let mut map = map.borrow_mut();
for _ in 0..self.n {
map.pop();
}
});
}
struct Guard(());

impl Drop for Guard {
fn drop(&mut self) {
TASK_LOCAL_MAP.with(|map| map.borrow_mut().pop());
}
}

TASK_LOCAL_MAP.with(|map| {
let mut map = map.borrow_mut();
for (key, value) in this.context.iter() {
map.push((key.clone(), value.clone()));
}
});
TASK_LOCAL_MAP.with(|map| map.borrow_mut().push(this.context.clone()));

let n = this.context.len();
let guard = Guard { n };
let guard = Guard(());

let result = match future.poll(cx) {
Poll::Ready(output) => {
fut.set(None);
Poll::Ready(output)
}
Poll::Pending => Poll::Pending,
};
let result = future.poll(cx);

drop(guard);
return result;
}
drop(guard);
result
}
}

unreachable!("TaskLocalFuture polled after completion");
#[cfg(test)]
mod tests {
use logforth_core::kv::KeyView;
use logforth_core::kv::ValueView;

use super::*;

#[test]
fn test_task_local_diagnostic() {
let diag = TaskLocalDiagnostic {};
let fut = async {
let mut n = 0;
diag.visit(&mut |key: KeyView<'_>, value: ValueView<'_>| {
n += 1;
assert_eq!(key.as_str(), format!("k{n}"));
assert_eq!(value.to_str().unwrap(), format!("v{n}"));
Ok(())
})
.unwrap();
};
pollster::block_on(fut.with_task_local_context([("k1", "v1"), ("k2", "v2")]));
}
}
2 changes: 1 addition & 1 deletion filters/rustlog/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG";
/// Less exclusive levels (like `trace` or `info`) are considered to be more verbose than more
/// exclusive levels (like `error` or `warn`).
///
/// Read more from the [crate level documentation](self) about the directive syntax and use cases.
/// Read more from the [crate documentation](self) about the directive syntax and use cases.
///
/// [`Record`]: logforth_core::record::Record
#[derive(Debug)]
Expand Down
2 changes: 1 addition & 1 deletion layouts/google-cloud-logging/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl Visitor for KvCollector<'_> {
}
}

let value = match serde_json::to_value(&value) {
let value = match serde_json::to_value(value) {
Ok(value) => value,
Err(_) => value.to_string().into(),
};
Expand Down
2 changes: 1 addition & 1 deletion layouts/json/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ struct KvCollector<'a> {
impl Visitor for KvCollector<'_> {
fn visit(&mut self, key: KeyView, value: ValueView) -> Result<(), Error> {
let key = key.to_string();
match serde_json::to_value(&value) {
match serde_json::to_value(value) {
Ok(value) => self.kvs.insert(key, value),
Err(_) => self.kvs.insert(key, value.to_string().into()),
};
Expand Down