1use crossbeam_channel::{unbounded, Sender};
2use serde_json::Value as JsonValue;
3use std::net::SocketAddr;
4use std::sync::{Arc, Mutex};
5use std::thread;
6use tiny_http::{Response, Server};
7use tracing::Subscriber;
8use tracing_subscriber::layer::Layer;
9use tracing_subscriber::registry::LookupSpan;
10
11pub struct Log {
12 sender: Sender<JsonValue>,
13}
14
15impl Log {
16 pub fn builder() -> LogBuilder { LogBuilder::new() }
17}
18
19pub struct LogBuilder {
20 host: String,
21}
22
23impl LogBuilder {
24 fn new() -> Self { LogBuilder { host: "127.0.0.1:8362".to_string() } }
25
26 pub fn with_host(mut self, host: &str) -> Self {
27 self.host = host.to_string();
28 self
29 }
30
31 pub fn build(self) -> Log {
32 let (sender, receiver) = unbounded();
33 let addr: SocketAddr = self.host.parse().expect("Invalid host address");
34
35 thread::spawn(move || {
36 let server = Server::http(addr).expect("Failed to start server");
37 let receiver = Arc::new(receiver);
38 let cache = Arc::new(Mutex::new(Vec::new()));
39
40 for request in server.incoming_requests() {
41 let receiver = receiver.clone();
42 let cache = cache.clone();
43 thread::spawn(move || {
44 let mut cache = cache.lock().unwrap();
45 cache.extend(receiver.try_iter());
46 let logs = cache.clone();
47 let json = serde_json::to_string(&logs).unwrap();
49 let response = Response::from_string(json).with_header(tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"application/json"[..]).unwrap());
50 request.respond(response).expect("Failed to send response");
51 });
52 }
53 });
54
55 Log { sender }
56 }
57}
58
59impl<S> Layer<S> for Log
60where
61 S: Subscriber + for<'a> LookupSpan<'a>,
62{
63 fn on_event(&self, event: &tracing::Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
64 let mut visitor = JsonVisitor::default();
65 event.record(&mut visitor);
66 self.sender.send(visitor.fields).expect("Failed to send log entry");
67 }
68}
69
70#[derive(Default)]
71struct JsonVisitor {
72 fields: JsonValue,
73}
74
75impl tracing::field::Visit for JsonVisitor {
76 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { self.fields[field.name()] = JsonValue::String(format!("{:?}", value)); }
77
78 fn record_str(&mut self, field: &tracing::field::Field, value: &str) { self.fields[field.name()] = JsonValue::String(value.to_string()); }
79
80 fn record_i64(&mut self, field: &tracing::field::Field, value: i64) { self.fields[field.name()] = JsonValue::Number(value.into()); }
81
82 fn record_u64(&mut self, field: &tracing::field::Field, value: u64) { self.fields[field.name()] = JsonValue::Number(value.into()); }
83
84 fn record_bool(&mut self, field: &tracing::field::Field, value: bool) { self.fields[field.name()] = JsonValue::Bool(value); }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use reqwest::blocking::Client;
91 use std::{thread, time::Duration};
92 use tracing_subscriber::prelude::*;
93
94 #[test]
95 fn test_weblog() {
96 let log_port = 8363;
97 tracing_subscriber::registry().with(Log::builder().with_host(&format!("127.0.0.1:{}", log_port)).build()).init();
98
99 thread::sleep(Duration::from_secs(1));
100
101 tracing::info!(message = "Test log message 1", index = 1);
102 tracing::warn!(message = "Test log message 2", index = 2);
103 tracing::error!(message = "Test log message 3", index = 3);
104
105 thread::sleep(Duration::from_millis(500));
106
107 let client = Client::new();
108 let response = client.get(&format!("http://127.0.0.1:{}", log_port)).send().expect("Failed to send request");
109
110 assert!(response.status().is_success());
111
112 let body: Vec<JsonValue> = response.json().expect("Failed to parse JSON");
113
114 assert!(body.iter().any(|log| log["message"] == "Test log message 1" && log["index"] == 1));
115 assert!(body.iter().any(|log| log["message"] == "Test log message 2" && log["index"] == 2));
116 assert!(body.iter().any(|log| log["message"] == "Test log message 3" && log["index"] == 3));
117
118 tracing::info!(message = "Test log message 4", index = 4);
120 thread::sleep(Duration::from_millis(500));
121
122 let response = client.get(&format!("http://127.0.0.1:{}", log_port)).send().expect("Failed to send request");
123
124 let body: Vec<JsonValue> = response.json().expect("Failed to parse JSON");
125
126 assert!(body.iter().any(|log| log["message"] == "Test log message 1" && log["index"] == 1));
127 assert!(body.iter().any(|log| log["message"] == "Test log message 2" && log["index"] == 2));
128 assert!(body.iter().any(|log| log["message"] == "Test log message 3" && log["index"] == 3));
129 assert!(body.iter().any(|log| log["message"] == "Test log message 4" && log["index"] == 4));
130 }
131}