use std::{
borrow::Cow,
fmt::Display,
hash::{DefaultHasher, Hash as _, Hasher},
};
use futures::future::Either;
use jsonrpsee::MethodResponse;
use jsonrpsee::core::middleware::{Batch, Notification};
use jsonrpsee::{server::middleware::rpc::RpcServiceT, types::Id};
use tower::Layer;
#[derive(Clone, Default)]
pub(super) struct LogLayer {}
impl<S> Layer<S> for LogLayer {
type Service = Logging<S>;
fn layer(&self, service: S) -> Self::Service {
Logging { service }
}
}
#[derive(Clone)]
pub(super) struct Logging<S> {
service: S,
}
impl<S> Logging<S> {
fn log_enabled() -> bool {
tracing::enabled!(tracing::Level::DEBUG)
}
async fn log<F>(
id: Id<'_>,
method_name: impl Display,
params: impl Display,
future: F,
) -> MethodResponse
where
F: Future<Output = MethodResponse>,
{
if !Self::log_enabled() {
return future.await;
}
let start_time = std::time::Instant::now();
let id = create_unique_id(id, start_time);
tracing::trace!("RPC#{id}: {method_name}. Params: {params}");
let resp = future.await;
let elapsed = start_time.elapsed();
let result = resp.as_error_code().map_or(Cow::Borrowed("OK"), |code| {
Cow::Owned(format!("ERR({code})"))
});
tracing::debug!(
"RPC#{id} {result}: {method_name}. Took {}",
humantime::format_duration(elapsed)
);
resp
}
}
impl<S> RpcServiceT for Logging<S>
where
S: RpcServiceT<MethodResponse = MethodResponse, NotificationResponse = MethodResponse>
+ Send
+ Sync
+ Clone
+ 'static,
{
type MethodResponse = S::MethodResponse;
type NotificationResponse = S::NotificationResponse;
type BatchResponse = S::BatchResponse;
fn call<'a>(
&self,
req: jsonrpsee::types::Request<'a>,
) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
if !Self::log_enabled() {
Either::Left(self.service.call(req))
} else {
Either::Right(Self::log(
req.id(),
req.method_name().to_owned(),
req.params().as_str().unwrap_or("[]").to_owned(),
self.service.call(req),
))
}
}
fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
self.service.batch(batch)
}
fn notification<'a>(
&self,
n: Notification<'a>,
) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
if !Self::log_enabled() {
Either::Left(self.service.notification(n))
} else {
Either::Right(Self::log(
Id::Null,
n.method_name().to_owned(),
"",
self.service.notification(n),
))
}
}
}
fn create_unique_id(id: Id, start_time: std::time::Instant) -> String {
const ID_LEN: usize = 6;
let mut hasher = DefaultHasher::new();
id.hash(&mut hasher);
start_time.hash(&mut hasher);
let mut id = format!("{:x}", hasher.finish());
id.truncate(ID_LEN);
id
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn test_create_unique_id_same() {
let id = Id::Number(1);
let start_time = std::time::Instant::now();
let id1 = create_unique_id(id.clone(), start_time);
let id2 = create_unique_id(id, start_time);
assert_eq!(id1, id2);
}
#[test]
fn test_create_unique_id_different_ids() {
let id1 = Id::Number(1);
let id2 = Id::Number(2);
let start_time = std::time::Instant::now();
let id1 = create_unique_id(id1, start_time);
let id2 = create_unique_id(id2, start_time);
assert_ne!(id1, id2);
}
#[test]
fn test_create_unique_id_different_times() {
let id = Id::Number(1);
let start_time1 = std::time::Instant::now();
let start_time2 = std::time::Instant::now() + Duration::from_nanos(1);
let id1 = create_unique_id(id.clone(), start_time1);
let id2 = create_unique_id(id, start_time2);
assert_ne!(id1, id2);
}
}