use crossbeam_channel::{unbounded, Sender};
use serde_json::Value as JsonValue;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::thread;
use tiny_http::{Response, Server};
use tracing::Subscriber;
use tracing_subscriber::layer::Layer;
use tracing_subscriber::registry::LookupSpan;
pub struct Log {
sender: Sender<JsonValue>,
}
impl Log {
pub fn builder() -> LogBuilder { LogBuilder::new() }
}
pub struct LogBuilder {
host: String,
}
impl LogBuilder {
fn new() -> Self { LogBuilder { host: "127.0.0.1:8362".to_string() } }
pub fn with_host(mut self, host: &str) -> Self {
self.host = host.to_string();
self
}
pub fn build(self) -> Log {
let (sender, receiver) = unbounded();
let addr: SocketAddr = self.host.parse().expect("Invalid host address");
thread::spawn(move || {
let server = Server::http(addr).expect("Failed to start server");
let receiver = Arc::new(receiver);
let cache = Arc::new(Mutex::new(Vec::new()));
for request in server.incoming_requests() {
let receiver = receiver.clone();
let cache = cache.clone();
thread::spawn(move || {
let mut cache = cache.lock().unwrap();
cache.extend(receiver.try_iter());
let logs = cache.clone();
let json = serde_json::to_string(&logs).unwrap();
let response = Response::from_string(json).with_header(tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"application/json"[..]).unwrap());
request.respond(response).expect("Failed to send response");
});
}
});
Log { sender }
}
}
impl<S> Layer<S> for Log
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_event(&self, event: &tracing::Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
let mut visitor = JsonVisitor::default();
event.record(&mut visitor);
self.sender.send(visitor.fields).expect("Failed to send log entry");
}
}
#[derive(Default)]
struct JsonVisitor {
fields: JsonValue,
}
impl tracing::field::Visit for JsonVisitor {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { self.fields[field.name()] = JsonValue::String(format!("{:?}", value)); }
fn record_str(&mut self, field: &tracing::field::Field, value: &str) { self.fields[field.name()] = JsonValue::String(value.to_string()); }
fn record_i64(&mut self, field: &tracing::field::Field, value: i64) { self.fields[field.name()] = JsonValue::Number(value.into()); }
fn record_u64(&mut self, field: &tracing::field::Field, value: u64) { self.fields[field.name()] = JsonValue::Number(value.into()); }
fn record_bool(&mut self, field: &tracing::field::Field, value: bool) { self.fields[field.name()] = JsonValue::Bool(value); }
}
#[cfg(test)]
mod tests {
use super::*;
use reqwest::blocking::Client;
use std::{thread, time::Duration};
use tracing_subscriber::prelude::*;
#[test]
fn test_weblog() {
let log_port = 8363;
tracing_subscriber::registry().with(Log::builder().with_host(&format!("127.0.0.1:{}", log_port)).build()).init();
thread::sleep(Duration::from_secs(1));
tracing::info!(message = "Test log message 1", index = 1);
tracing::warn!(message = "Test log message 2", index = 2);
tracing::error!(message = "Test log message 3", index = 3);
thread::sleep(Duration::from_millis(500));
let client = Client::new();
let response = client.get(&format!("http://127.0.0.1:{}", log_port)).send().expect("Failed to send request");
assert!(response.status().is_success());
let body: Vec<JsonValue> = response.json().expect("Failed to parse JSON");
assert!(body.iter().any(|log| log["message"] == "Test log message 1" && log["index"] == 1));
assert!(body.iter().any(|log| log["message"] == "Test log message 2" && log["index"] == 2));
assert!(body.iter().any(|log| log["message"] == "Test log message 3" && log["index"] == 3));
tracing::info!(message = "Test log message 4", index = 4);
thread::sleep(Duration::from_millis(500));
let response = client.get(&format!("http://127.0.0.1:{}", log_port)).send().expect("Failed to send request");
let body: Vec<JsonValue> = response.json().expect("Failed to parse JSON");
assert!(body.iter().any(|log| log["message"] == "Test log message 1" && log["index"] == 1));
assert!(body.iter().any(|log| log["message"] == "Test log message 2" && log["index"] == 2));
assert!(body.iter().any(|log| log["message"] == "Test log message 3" && log["index"] == 3));
assert!(body.iter().any(|log| log["message"] == "Test log message 4" && log["index"] == 4));
}
}