#[cfg(tracing_unstable)]
use crate::json::JsonValuable;
use async_compression::tokio::write::ZlibDecoder;
use axum::Router;
use axum::http::StatusCode;
use axum::routing::post;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use tokio::io::AsyncWriteExt;
#[cfg(tracing_unstable)]
use valuable::{Mappable, Valuable, Value, Visit};
#[derive(Serialize, Deserialize, Debug)]
pub struct ClientEvent {
pub ts: String,
pub tz: String,
#[serde(skip_serializing)]
pub lvl: String,
pub v: String,
pub cid: String,
pub mid: String,
pub p: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub q: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub f: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub flg: Option<BTreeMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub fld: Option<BTreeMap<String, serde_json::Value>>,
#[serde(skip_serializing)]
pub msg: String,
}
#[cfg(tracing_unstable)]
impl Mappable for ClientEvent {
fn size_hint(&self) -> (usize, Option<usize>) {
(6, Some(9))
}
}
#[cfg(tracing_unstable)]
impl Valuable for ClientEvent {
fn as_value(&self) -> Value<'_> {
Value::Mappable(self)
}
fn visit(&self, visit: &mut dyn Visit) {
visit.visit_entry("ts".as_value(), self.ts.as_value());
visit.visit_entry("tz".as_value(), self.tz.as_value());
visit.visit_entry("v".as_value(), self.v.as_value());
visit.visit_entry("mid".as_value(), self.mid.as_value());
visit.visit_entry("cid".as_value(), self.cid.as_value());
visit.visit_entry("p".as_value(), self.p.as_value());
if let Some(q) = &self.q {
visit.visit_entry("q".as_value(), q.as_value());
}
if let Some(f) = &self.f {
visit.visit_entry("f".as_value(), f.as_value());
}
if let Some(flg) = &self.flg {
visit.visit_entry("flg".as_value(), flg.as_value());
}
if let Some(fld) = &self.fld {
for (fld_nm, fld_v) in fld {
if !fld_v.is_null() {
visit.visit_entry(fld_nm.as_value(), JsonValuable(fld_v.to_owned()).as_value());
}
}
}
}
}
#[must_use]
pub fn setup_routes<S>(client_events: Option<&bool>) -> Option<Router<S>>
where
S: Clone + Send + Sync + 'static,
{
if client_events == Some(&true) {
let router = Router::new().route(
"/.ordinary/v1/events",
post(|body: Bytes| async move {
let mut decoder = ZlibDecoder::new(Vec::new());
if let Err(err) = decoder.write_all(body.as_ref()).await {
tracing::error!(%err);
return StatusCode::INTERNAL_SERVER_ERROR;
}
if let Err(err) = decoder.shutdown().await {
tracing::error!(%err);
return StatusCode::INTERNAL_SERVER_ERROR;
}
let mut events_slice = decoder.into_inner();
let events: Vec<ClientEvent> = match simd_json::from_slice(&mut events_slice) {
Ok(parsed) => parsed,
Err(err) => {
tracing::error!(%err);
return StatusCode::INTERNAL_SERVER_ERROR;
}
};
let client_span = tracing::info_span!("client");
client_span.in_scope(|| {
for event in events {
#[cfg(tracing_unstable)]
{
let level = event.lvl.as_str();
let message = event.msg.as_str();
match level {
"TRACE" => tracing::trace!(
_f = tracing::field::valuable(&event),
"{message}"
),
"DEBUG" => tracing::debug!(
_f = tracing::field::valuable(&event),
"{message}"
),
"INFO" => {
tracing::info!(
_f = tracing::field::valuable(&event),
"{message}"
);
}
"WARN" => {
tracing::warn!(
_f = tracing::field::valuable(&event),
"{message}"
);
}
_ => tracing::error!(
_f = tracing::field::valuable(&event),
"{message}"
),
}
}
#[cfg(not(tracing_unstable))]
{
match event.lvl.as_ref() {
"TRACE" => tracing::trace!(evt = debug(&event)),
"DEBUG" => tracing::debug!(evt = debug(&event)),
"INFO" => tracing::info!(evt = debug(&event)),
"WARN" => tracing::warn!(evt = debug(&event)),
_ => tracing::error!(evt = debug(&event)),
}
}
}
});
StatusCode::OK
}),
);
return Some(router);
}
None
}