Skip to content

Commit 62a9695

Browse files
committed
reimpl
Signed-off-by: tison <wander4096@gmail.com>
1 parent a149d22 commit 62a9695

File tree

8 files changed

+247
-4
lines changed

8 files changed

+247
-4
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ jobs:
8989
cargo run --features="starter-log" --example json_stdout
9090
cargo run --features="starter-log" --example rolling_file
9191
cargo run --features="starter-log" --example single_file
92+
cargo run --features="starter-log,diagnostic-task-local" --example task_local
9293
cargo run --features="starter-log,append-async" --example asynchronous
9394
cargo run --features="starter-log,diagnostic-fastrace,layout-google-cloud-logging" --example google_cloud_logging
9495
cargo run --features="starter-log,append-fastrace,diagnostic-fastrace" --example fastrace

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ logforth-append-syslog = { version = "0.3.0", path = "appenders/syslog" }
4343
logforth-bridge-log = { version = "0.3.0", path = "bridges/log" }
4444
logforth-core = { version = "0.3.0", path = "core" }
4545
logforth-diagnostic-fastrace = { version = "0.3.0", path = "diagnostics/fastrace" }
46+
logforth-diagnostic-task-local = { version = "0.3.0", path = "diagnostics/task-local" }
4647
logforth-layout-google-cloud-logging = { version = "0.3.0", path = "layouts/google-cloud-logging" }
4748
logforth-layout-json = { version = "0.3.0", path = "layouts/json" }
4849
logforth-layout-logfmt = { version = "0.3.0", path = "layouts/logfmt" }
@@ -62,6 +63,7 @@ log = { version = "0.4.27", features = ["kv_std", "kv_sval"] }
6263
opentelemetry = { version = "0.31.0", default-features = false }
6364
opentelemetry-otlp = { version = "0.31.0", default-features = false }
6465
opentelemetry_sdk = { version = "0.31.0", default-features = false }
66+
pin-project = { version = "1.1.10" }
6567
rand = { version = "0.9" }
6668
serde = { version = "1.0", features = ["derive"] }
6769
serde_json = { version = "1.0" }

core/src/diagnostic/thread_local.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::kv::Value;
2222
use crate::kv::Visitor;
2323

2424
thread_local! {
25-
static CONTEXT: RefCell<BTreeMap<String, String>> = const { RefCell::new(BTreeMap::new()) };
25+
static THREAD_LOCAL_MAP: RefCell<BTreeMap<String, String>> = const { RefCell::new(BTreeMap::new()) };
2626
}
2727

2828
/// A diagnostic that stores key-value pairs in a thread-local map.
@@ -45,22 +45,22 @@ impl ThreadLocalDiagnostic {
4545
K: Into<String>,
4646
V: Into<String>,
4747
{
48-
CONTEXT.with(|map| {
48+
THREAD_LOCAL_MAP.with(|map| {
4949
map.borrow_mut().insert(key.into(), value.into());
5050
});
5151
}
5252

5353
/// Remove a key-value pair from the thread local diagnostic.
5454
pub fn remove(key: &str) {
55-
CONTEXT.with(|map| {
55+
THREAD_LOCAL_MAP.with(|map| {
5656
map.borrow_mut().remove(key);
5757
});
5858
}
5959
}
6060

6161
impl Diagnostic for ThreadLocalDiagnostic {
6262
fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> {
63-
CONTEXT.with(|map| {
63+
THREAD_LOCAL_MAP.with(|map| {
6464
let map = map.borrow();
6565
for (key, value) in map.iter() {
6666
let key = Key::new_ref(key.as_str());

diagnostics/task-local/Cargo.toml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright 2024 FastLabs Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
[package]
16+
name = "logforth-diagnostic-task-local"
17+
version = "0.3.0"
18+
19+
description = "Task-local diagnostic for Logforth."
20+
keywords = ["logging", "log", "async", "task", "future"]
21+
22+
categories.workspace = true
23+
edition.workspace = true
24+
homepage.workspace = true
25+
license.workspace = true
26+
readme.workspace = true
27+
repository.workspace = true
28+
rust-version.workspace = true
29+
30+
[package.metadata.docs.rs]
31+
all-features = true
32+
rustdoc-args = ["--cfg", "docsrs"]
33+
34+
[dependencies]
35+
logforth-core = { workspace = true }
36+
pin-project = { workspace = true }
37+
38+
[dev-dependencies]
39+
log = { workspace = true }
40+
41+
[lints]
42+
workspace = true

diagnostics/task-local/src/lib.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright 2024 FastLabs Developers
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! A diagnostic that stores key-value pairs in a task-local map.
16+
//!
17+
//! # Examples
18+
//!
19+
//! ```
20+
//! use logforth_core::Diagnostic;
21+
//! use logforth_core::kv::Visitor;
22+
//! use logforth_diagnostic_task_local::FutureExt;
23+
//!
24+
//! let fut = async { log::info!("Hello, world!") };
25+
//! fut.with_task_local_context(["key", "value"]);
26+
//! ```
27+
28+
use std::cell::RefCell;
29+
use std::pin::Pin;
30+
use std::task::Context;
31+
use std::task::Poll;
32+
33+
use logforth_core::Diagnostic;
34+
use logforth_core::Error;
35+
use logforth_core::kv::Key;
36+
use logforth_core::kv::Value;
37+
use logforth_core::kv::Visitor;
38+
39+
thread_local! {
40+
static TASK_LOCAL_MAP: RefCell<Vec<(String, String)>> = const { RefCell::new(Vec::new()) };
41+
}
42+
43+
/// A diagnostic that stores key-value pairs in a task-local context.
44+
///
45+
/// See [module-level documentation](self) for usage examples.
46+
#[derive(Default, Debug, Clone, Copy)]
47+
#[non_exhaustive]
48+
pub struct TaskLocalDiagnostic {}
49+
50+
impl Diagnostic for TaskLocalDiagnostic {
51+
fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> {
52+
TASK_LOCAL_MAP.with(|map| {
53+
let map = map.borrow();
54+
for (key, value) in map.iter() {
55+
let key = Key::new_ref(key.as_str());
56+
let value = Value::from(value.as_str());
57+
visitor.visit(key, value)?;
58+
}
59+
Ok(())
60+
})
61+
}
62+
}
63+
64+
/// An extension trait for futures to run them with a task-local context.
65+
///
66+
/// See [module-level documentation](self) for usage examples.
67+
pub trait FutureExt: Future {
68+
/// Run a future with a task-local context.
69+
fn with_task_local_context(
70+
self,
71+
kvs: impl IntoIterator<Item = (String, String)>,
72+
) -> impl Future<Output = Self::Output>
73+
where
74+
Self: Sized,
75+
{
76+
TaskLocalFuture {
77+
future: Some(self),
78+
context: kvs.into_iter().collect(),
79+
}
80+
}
81+
}
82+
83+
impl<F: Future> FutureExt for F {}
84+
85+
#[pin_project::pin_project]
86+
struct TaskLocalFuture<F> {
87+
#[pin]
88+
future: Option<F>,
89+
context: Vec<(String, String)>,
90+
}
91+
92+
impl<F: Future> Future for TaskLocalFuture<F> {
93+
type Output = F::Output;
94+
95+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
96+
let this = self.project();
97+
98+
let mut fut = this.future;
99+
if let Some(future) = fut.as_mut().as_pin_mut() {
100+
struct Guard {
101+
n: usize,
102+
}
103+
104+
impl Drop for Guard {
105+
fn drop(&mut self) {
106+
TASK_LOCAL_MAP.with(|map| {
107+
let mut map = map.borrow_mut();
108+
for _ in 0..self.n {
109+
map.pop();
110+
}
111+
});
112+
}
113+
}
114+
115+
TASK_LOCAL_MAP.with(|map| {
116+
let mut map = map.borrow_mut();
117+
for (key, value) in this.context.iter() {
118+
map.push((key.clone(), value.clone()));
119+
}
120+
});
121+
122+
let n = this.context.len();
123+
let guard = Guard { n };
124+
125+
let result = match future.poll(cx) {
126+
Poll::Ready(output) => {
127+
fut.set(None);
128+
Poll::Ready(output)
129+
}
130+
Poll::Pending => Poll::Pending,
131+
};
132+
133+
drop(guard);
134+
return result;
135+
}
136+
137+
unreachable!("TaskLocalFuture polled after completion");
138+
}
139+
}

logforth/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ layout-text = ["dep:logforth-layout-text"]
5656

5757
# Diagnostics
5858
diagnostic-fastrace = ["dep:logforth-diagnostic-fastrace"]
59+
diagnostic-task-local = ["dep:logforth-diagnostic-task-local"]
5960

6061
# Standalone features
6162
native-tls = ["logforth-append-syslog?/native-tls"]
@@ -73,6 +74,7 @@ logforth-append-opentelemetry = { workspace = true, optional = true }
7374
logforth-append-syslog = { workspace = true, optional = true }
7475
logforth-bridge-log = { workspace = true, optional = true }
7576
logforth-diagnostic-fastrace = { workspace = true, optional = true }
77+
logforth-diagnostic-task-local = { workspace = true, optional = true }
7678
logforth-layout-google-cloud-logging = { workspace = true, optional = true }
7779
logforth-layout-json = { workspace = true, optional = true }
7880
logforth-layout-logfmt = { workspace = true, optional = true }
@@ -83,6 +85,7 @@ fastrace = { workspace = true, features = ["enable"] }
8385
log = { workspace = true, features = ["kv_serde"] }
8486
logforth-append-file = { workspace = true }
8587
serde = { workspace = true }
88+
tokio = { workspace = true, features = ["full"] }
8689

8790
[lints]
8891
workspace = true
@@ -140,6 +143,12 @@ name = "single_file"
140143
path = "examples/single_file.rs"
141144
required-features = ["starter-log"]
142145

146+
[[example]]
147+
doc-scrape-examples = true
148+
name = "task_local"
149+
path = "examples/task_local.rs"
150+
required-features = ["starter-log", "diagnostic-task-local"]
151+
143152
[[example]]
144153
doc-scrape-examples = true
145154
name = "custom_layout_filter"

logforth/examples/task_local.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2024 FastLabs Developers
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! Example of using task-local storage with `logForth`.
16+
17+
use logforth::diagnostic::TaskLocalDiagnostic;
18+
use logforth::diagnostic::task_local::FutureExt;
19+
use logforth::record::LevelFilter;
20+
use logforth_layout_text::TextLayout;
21+
22+
#[tokio::main]
23+
async fn main() {
24+
logforth::starter_log::builder()
25+
.dispatch(|d| {
26+
d.filter(LevelFilter::All)
27+
.diagnostic(TaskLocalDiagnostic::default())
28+
.append(logforth::append::Stderr::default().with_layout(TextLayout::default()))
29+
})
30+
.apply();
31+
32+
async {
33+
async {
34+
log::error!("Hello error!");
35+
log::warn!("Hello warn!");
36+
log::info!("Hello info!");
37+
}
38+
.with_task_local_context([("k3".to_string(), "v3".to_string())])
39+
.await;
40+
log::debug!("Hello debug!");
41+
log::trace!("Hello trace!");
42+
}
43+
.with_task_local_context([("k1".to_string(), "v1".to_string())])
44+
.with_task_local_context([("k2".to_string(), "v2".to_string())])
45+
.await;
46+
}

logforth/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ pub mod diagnostic {
124124
pub use logforth_core::diagnostic::*;
125125
#[cfg(feature = "diagnostic-fastrace")]
126126
pub use logforth_diagnostic_fastrace::FastraceDiagnostic;
127+
#[cfg(feature = "diagnostic-task-local")]
128+
pub use logforth_diagnostic_task_local as task_local;
129+
#[cfg(feature = "diagnostic-task-local")]
130+
pub use logforth_diagnostic_task_local::TaskLocalDiagnostic;
127131
}
128132

129133
/// Filters for log records.

0 commit comments

Comments
 (0)