Skip to content
Merged
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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ jobs:
cargo run --features="starter-log" --example json_stdout
cargo run --features="starter-log" --example rolling_file
cargo run --features="starter-log" --example single_file
cargo run --features="starter-log,diagnostic-task-local" --example task_local
cargo run --features="starter-log,append-async" --example asynchronous
cargo run --features="starter-log,diagnostic-fastrace,layout-google-cloud-logging" --example google_cloud_logging
cargo run --features="starter-log,append-fastrace,diagnostic-fastrace" --example fastrace
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ All notable changes to this project will be documented in this file.
* Add `Level::Critical` variant to represent critical level logs.
* Redesign `LevelFilter`.

### New features

* Add `logforth-diagnostic-task-local` and `TaskLocalDiagnostic` to support task-local key-value context.

## [0.28.1] 2025-10-06

### Documentation changes
Expand Down
29 changes: 16 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,20 @@ rust-version = "1.85.0"

[workspace.dependencies]
# Workspace dependencies
logforth-append-async = { version = "0.2.1", path = "appenders/async" }
logforth-append-fastrace = { version = "0.2.1", path = "appenders/fastrace" }
logforth-append-file = { version = "0.2.1", path = "appenders/file" }
logforth-append-journald = { version = "0.2.1", path = "appenders/journald" }
logforth-append-opentelemetry = { version = "0.2.1", path = "appenders/opentelemetry" }
logforth-append-syslog = { version = "0.2.1", path = "appenders/syslog" }
logforth-bridge-log = { version = "0.2.1", path = "bridges/log" }
logforth-core = { version = "0.2.1", path = "core" }
logforth-diagnostic-fastrace = { version = "0.2.1", path = "diagnostics/fastrace" }
logforth-layout-google-cloud-logging = { version = "0.2.1", path = "layouts/google-cloud-logging" }
logforth-layout-json = { version = "0.2.1", path = "layouts/json" }
logforth-layout-logfmt = { version = "0.2.1", path = "layouts/logfmt" }
logforth-layout-text = { version = "0.2.1", path = "layouts/text" }
logforth-append-async = { version = "0.3.0", path = "appenders/async" }
logforth-append-fastrace = { version = "0.3.0", path = "appenders/fastrace" }
logforth-append-file = { version = "0.3.0", path = "appenders/file" }
logforth-append-journald = { version = "0.3.0", path = "appenders/journald" }
logforth-append-opentelemetry = { version = "0.3.0", path = "appenders/opentelemetry" }
logforth-append-syslog = { version = "0.3.0", path = "appenders/syslog" }
logforth-bridge-log = { version = "0.3.0", path = "bridges/log" }
logforth-core = { version = "0.3.0", path = "core" }
logforth-diagnostic-fastrace = { version = "0.3.0", path = "diagnostics/fastrace" }
logforth-diagnostic-task-local = { version = "0.3.0", path = "diagnostics/task-local" }
logforth-layout-google-cloud-logging = { version = "0.3.0", path = "layouts/google-cloud-logging" }
logforth-layout-json = { version = "0.3.0", path = "layouts/json" }
logforth-layout-logfmt = { version = "0.3.0", path = "layouts/logfmt" }
logforth-layout-text = { version = "0.3.0", path = "layouts/text" }

# Crates.io dependencies
anyhow = { version = "1.0" }
Expand All @@ -62,10 +63,12 @@ log = { version = "0.4.27", features = ["kv_std", "kv_sval"] }
opentelemetry = { version = "0.31.0", default-features = false }
opentelemetry-otlp = { version = "0.31.0", default-features = false }
opentelemetry_sdk = { version = "0.31.0", default-features = false }
pin-project = { version = "1.1.10" }
rand = { version = "0.9" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
tempfile = { version = "3.16" }
tokio = { version = "1.47.1" }
value-bag = { version = "1.11.1", features = ["inline-i128", "owned", "sval"] }

[workspace.lints.rust]
Expand Down
2 changes: 1 addition & 1 deletion appenders/async/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-append-async"
version = "0.2.1"
version = "0.3.0"

description = "Asynchronous appender for Logforth."
keywords = ["logging", "log", "async"]
Expand Down
2 changes: 1 addition & 1 deletion appenders/fastrace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-append-fastrace"
version = "0.2.1"
version = "0.3.0"

description = "Fastrace appender for Logforth."
keywords = ["logging", "log", "fastrace"]
Expand Down
2 changes: 1 addition & 1 deletion appenders/file/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-append-file"
version = "0.2.1"
version = "0.3.0"

description = "File appender for Logforth with optional rollover strategy."
keywords = ["logging", "log", "file-appender"]
Expand Down
2 changes: 1 addition & 1 deletion appenders/journald/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-append-journald"
version = "0.2.1"
version = "0.3.0"

description = "Journald appender for Logforth."
keywords = ["logging", "log", "journald"]
Expand Down
2 changes: 1 addition & 1 deletion appenders/opentelemetry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-append-opentelemetry"
version = "0.2.1"
version = "0.3.0"

description = "Opemtelemetry appender for Logforth."
keywords = ["logging", "log", "opentelemtry"]
Expand Down
2 changes: 1 addition & 1 deletion appenders/syslog/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-append-syslog"
version = "0.2.1"
version = "0.3.0"

description = "Syslog appender for Logforth."
keywords = ["logging", "log", "syslog"]
Expand Down
2 changes: 1 addition & 1 deletion bridges/log/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-bridge-log"
version = "0.2.1"
version = "0.3.0"

description = "Bridge Logforth with the log crate."
keywords = ["logging", "log"]
Expand Down
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-core"
version = "0.2.1"
version = "0.3.0"

description = "Core structs and functions for Logforth."
keywords = ["logging", "log"]
Expand Down
8 changes: 4 additions & 4 deletions core/src/diagnostic/thread_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::kv::Value;
use crate::kv::Visitor;

thread_local! {
static CONTEXT: RefCell<BTreeMap<String, String>> = const { RefCell::new(BTreeMap::new()) };
static THREAD_LOCAL_MAP: RefCell<BTreeMap<String, String>> = const { RefCell::new(BTreeMap::new()) };
}

/// A diagnostic that stores key-value pairs in a thread-local map.
Expand All @@ -45,22 +45,22 @@ impl ThreadLocalDiagnostic {
K: Into<String>,
V: Into<String>,
{
CONTEXT.with(|map| {
THREAD_LOCAL_MAP.with(|map| {
map.borrow_mut().insert(key.into(), value.into());
});
}

/// Remove a key-value pair from the thread local diagnostic.
pub fn remove(key: &str) {
CONTEXT.with(|map| {
THREAD_LOCAL_MAP.with(|map| {
map.borrow_mut().remove(key);
});
}
}

impl Diagnostic for ThreadLocalDiagnostic {
fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> {
CONTEXT.with(|map| {
THREAD_LOCAL_MAP.with(|map| {
let map = map.borrow();
for (key, value) in map.iter() {
let key = Key::new_ref(key.as_str());
Expand Down
2 changes: 1 addition & 1 deletion diagnostics/fastrace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-diagnostic-fastrace"
version = "0.2.1"
version = "0.3.0"

description = "Fastrace diagnostic for Logforth."
keywords = ["logging", "log", "fastrace"]
Expand Down
42 changes: 42 additions & 0 deletions diagnostics/task-local/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2024 FastLabs Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[package]
name = "logforth-diagnostic-task-local"
version = "0.3.0"

description = "Task-local diagnostic for Logforth."
keywords = ["logging", "log", "async", "task", "future"]

categories.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
rust-version.workspace = true

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
logforth-core = { workspace = true }
pin-project = { workspace = true }

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

[lints]
workspace = true
139 changes: 139 additions & 0 deletions diagnostics/task-local/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2024 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A diagnostic that stores key-value pairs in a task-local map.
//!
//! # Examples
//!
//! ```
//! use logforth_core::Diagnostic;
//! use logforth_core::kv::Visitor;
//! use logforth_diagnostic_task_local::FutureExt;
//!
//! let fut = async { log::info!("Hello, world!") };
//! fut.with_task_local_context([("key".into(), "value".into())]);
//! ```

use std::cell::RefCell;
use std::pin::Pin;
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::Visitor;

thread_local! {
static TASK_LOCAL_MAP: RefCell<Vec<(String, String)>> = 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.
#[derive(Default, Debug, Clone, Copy)]
#[non_exhaustive]
pub struct TaskLocalDiagnostic {}

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::new_ref(key.as_str());
let value = Value::from(value.as_str());
visitor.visit(key, value)?;
}
Ok(())
})
}
}

/// An extension trait for futures to run them with a task-local context.
///
/// See [module-level 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>
where
Self: Sized,
Comment on lines +69 to +74
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd prefer a shorter name, but not have a good idea now.

{
TaskLocalFuture {
future: Some(self),
context: kvs.into_iter().collect(),
}
}
}

impl<F: Future> FutureExt for F {}

#[pin_project::pin_project]
struct TaskLocalFuture<F> {
#[pin]
future: Option<F>,
context: Vec<(String, String)>,
}

impl<F: Future> Future for TaskLocalFuture<F> {
type Output = F::Output;

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,
}

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();
}
});
}
}

TASK_LOCAL_MAP.with(|map| {
let mut map = map.borrow_mut();
for (key, value) in this.context.iter() {
map.push((key.clone(), value.clone()));
}
});
Comment on lines +115 to +120
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This may not be quite efficient. But generally kvs are small and we can anyway improve later.


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

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

drop(guard);
return result;
}

unreachable!("TaskLocalFuture polled after completion");
}
}
2 changes: 1 addition & 1 deletion layouts/google-cloud-logging/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-layout-google-cloud-logging"
version = "0.2.1"
version = "0.3.0"

description = "Google Cloud Structured Logging layout for Logforth."
keywords = ["logging", "log", "google-cloud-logging"]
Expand Down
2 changes: 1 addition & 1 deletion layouts/json/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-layout-json"
version = "0.2.1"
version = "0.3.0"

description = "JSON layout for Logforth."
keywords = ["logging", "log", "json"]
Expand Down
2 changes: 1 addition & 1 deletion layouts/logfmt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "logforth-layout-logfmt"
version = "0.2.1"
version = "0.3.0"

description = "Logfmt layout for Logforth."
keywords = ["logging", "log", "logfmt"]
Expand Down
Loading