1use std::task::Poll;
4
5use pin_project::pin_project;
6
7use crate::{LogContext, scope::LogScope};
8
9pub trait FutureExt: Sized + crate::private::Sealed {
16 fn in_log_context(self, context: LogContext) -> LogContextFuture<Self>;
45}
46
47impl<F> FutureExt for F
48where
49 F: Future,
50{
51 fn in_log_context(self, context: LogContext) -> LogContextFuture<Self> {
52 LogContextFuture {
53 inner: self,
54 log_context: Some(context),
55 }
56 }
57}
58
59#[pin_project]
68#[derive(Debug)]
69pub struct LogContextFuture<F> {
70 #[pin]
71 inner: F,
72 log_context: Option<LogContext>,
73}
74
75impl<F> Future for LogContextFuture<F>
76where
77 F: Future,
78{
79 type Output = F::Output;
80
81 fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
82 let this = self.project();
83
84 let log_context = this
85 .log_context
86 .take()
87 .expect("An attempt to poll panicked future");
88
89 let guard = LogScope::enter(log_context);
90 let result = this.inner.poll(cx);
91 this.log_context.replace(guard.exit());
92
93 result
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use std::panic::AssertUnwindSafe;
100
101 use futures_util::FutureExt as _;
102 use pretty_assertions::assert_eq;
103
104 use super::FutureExt;
105 use crate::{LogContext, LogValue, scope::stack::SCOPE_STACK};
106
107 fn find_local_value(key: &str) -> Option<String> {
108 SCOPE_STACK.with(|stack| {
109 let frame = stack.top()?;
110 frame.0.local.find(key).map(ToString::to_string)
111 })
112 }
113
114 async fn check_nested_different_contexts(answer: u32) {
115 let context = LogContext::new().with_local_field("answer", answer);
116
117 async {
118 tokio::task::yield_now().await;
119
120 async {
121 tokio::task::yield_now().await;
122 assert_eq!(find_local_value("answer"), Some("None".to_string()));
123 }
124 .in_log_context(LogContext::new().with_local_field("answer", LogValue::null()))
125 .await;
126
127 tokio::task::yield_now().await;
128 assert_eq!(find_local_value("answer"), Some(answer.to_string()));
129 }
130 .in_log_context(context)
131 .await;
132
133 assert_eq!(find_local_value("answer"), None);
134 }
135
136 #[tokio::test]
137 async fn test_future_with_context() {
138 let context = LogContext::new().with_local_field("answer", 42);
139
140 async {
141 tokio::task::yield_now().await;
142 assert_eq!(find_local_value("answer"), Some("42".to_string()));
143 }
144 .in_log_context(context)
145 .await;
146
147 assert_eq!(find_local_value("answer"), None);
148 }
149
150 #[tokio::test]
151 async fn test_panicked_future() {
152 let context = LogContext::new().with_local_field("answer", 42);
153
154 AssertUnwindSafe(
155 async {
156 tokio::task::yield_now().await;
157 panic!("Goodbye cruel world");
158 }
159 .in_log_context(context),
160 )
161 .catch_unwind()
162 .await
163 .unwrap_err();
164
165 assert_eq!(find_local_value("answer"), None);
166 }
167
168 #[tokio::test]
169 async fn test_nested_future_with_common_context() {
170 let context = LogContext::new().with_local_field("answer", 42);
171
172 async {
173 tokio::task::yield_now().await;
174
175 async {
176 tokio::task::yield_now().await;
177 assert_eq!(find_local_value("answer"), Some("42".to_string()));
178 }
179 .await;
180
181 assert_eq!(find_local_value("answer"), Some("42".to_string()));
182 }
183 .in_log_context(context)
184 .await;
185
186 assert_eq!(find_local_value("answer"), None);
187 }
188
189 #[tokio::test]
190 async fn test_nested_future_with_different_contexts() {
191 check_nested_different_contexts(42).await;
192 }
193
194 #[tokio::test]
195 async fn test_join_multiple_tasks_single_thread() {
196 let tasks = (0..128).map(check_nested_different_contexts);
197 futures_util::future::join_all(tasks).await;
198 }
199
200 #[tokio::test]
201 async fn test_join_multiple_tasks_multi_thread() {
202 let handles = (0..64).map(|i| {
203 tokio::spawn(futures_util::future::join_all(
204 (0..128).map(|j| check_nested_different_contexts(j * i)),
205 ))
206 });
207
208 let results = futures_util::future::join_all(handles).await;
209 for result in results {
210 result.unwrap();
211 }
212 }
213}