tork_core/logging/
span.rs1use std::future::Future;
8use std::sync::Arc;
9
10use serde::Serialize;
11use serde_json::{Map, Value};
12use tracing::span::EnteredSpan;
13use tracing::Instrument;
14
15#[must_use = "a LogSpan does nothing until enter() or run() is called"]
18pub struct LogSpan {
19 context: Arc<str>,
20 name: String,
21 fields: Map<String, Value>,
22}
23
24impl LogSpan {
25 pub(crate) fn new(
27 context: Arc<str>,
28 name: impl Into<String>,
29 fields: Map<String, Value>,
30 ) -> Self {
31 Self {
32 context,
33 name: name.into(),
34 fields,
35 }
36 }
37
38 pub fn field<T: Serialize>(mut self, key: &'static str, value: T) -> Self {
40 if let Ok(value) = serde_json::to_value(value) {
41 self.fields.insert(key.to_owned(), value);
42 }
43 self
44 }
45
46 fn build(&self) -> tracing::Span {
48 let fields = serde_json::to_string(&Value::Object(self.fields.clone()))
49 .unwrap_or_else(|_| "{}".to_owned());
50 let context = self.context.as_ref();
51 let name = self.name.as_str();
52 tracing::info_span!(
53 "op",
54 tork.context = %context,
55 tork.op = %name,
56 tork.fields = %fields
57 )
58 }
59
60 pub fn enter(self) -> EnteredSpan {
62 self.build().entered()
63 }
64
65 pub async fn run<F: Future>(self, future: F) -> F::Output {
67 future.instrument(self.build()).await
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use crate::logging::Logger;
74
75 #[tokio::test]
76 async fn instrument_runs_the_future_and_returns_its_value() {
77 let logger = Logger::new("Worker");
78 let output = logger
79 .instrument("job")
80 .field("attempt", 1)
81 .run(async { 21 * 2 })
82 .await;
83 assert_eq!(output, 42);
84 }
85
86 #[test]
87 fn span_enters_a_scope() {
88 let logger = Logger::new("Worker");
89 let guard = logger.span("scope").field("key", "value").enter();
90 drop(guard);
91 }
92}