Skip to content
Open
Show file tree
Hide file tree
Changes from all 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