1#[cfg(tracing_unstable)]
6use crate::json::JsonValuable;
7use async_compression::tokio::write::ZlibDecoder;
8use axum::Router;
9use axum::http::StatusCode;
10use axum::routing::post;
11use bytes::Bytes;
12use serde::{Deserialize, Serialize};
13use std::collections::BTreeMap;
14use tokio::io::AsyncWriteExt;
15#[cfg(tracing_unstable)]
16use valuable::{Mappable, Valuable, Value, Visit};
17
18#[derive(Serialize, Deserialize, Debug)]
19pub struct ClientEvent {
20 pub ts: String,
22 pub tz: String,
24 #[serde(skip_serializing)]
26 pub lvl: String,
27 pub v: String,
29 pub cid: String,
31 pub mid: String,
33 pub p: String,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 #[serde(default)]
37 pub q: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 #[serde(default)]
41 pub f: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 #[serde(default)]
45 pub flg: Option<BTreeMap<String, String>>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 #[serde(default)]
49 pub fld: Option<BTreeMap<String, serde_json::Value>>,
51 #[serde(skip_serializing)]
52 pub msg: String,
54}
55
56#[cfg(tracing_unstable)]
57impl Mappable for ClientEvent {
58 fn size_hint(&self) -> (usize, Option<usize>) {
59 (6, Some(9))
60 }
61}
62
63#[cfg(tracing_unstable)]
64impl Valuable for ClientEvent {
65 fn as_value(&self) -> Value<'_> {
66 Value::Mappable(self)
67 }
68
69 fn visit(&self, visit: &mut dyn Visit) {
70 visit.visit_entry("ts".as_value(), self.ts.as_value());
71 visit.visit_entry("tz".as_value(), self.tz.as_value());
72
73 visit.visit_entry("v".as_value(), self.v.as_value());
74 visit.visit_entry("mid".as_value(), self.mid.as_value());
75 visit.visit_entry("cid".as_value(), self.cid.as_value());
76
77 visit.visit_entry("p".as_value(), self.p.as_value());
78 if let Some(q) = &self.q {
79 visit.visit_entry("q".as_value(), q.as_value());
80 }
81 if let Some(f) = &self.f {
82 visit.visit_entry("f".as_value(), f.as_value());
83 }
84 if let Some(flg) = &self.flg {
85 visit.visit_entry("flg".as_value(), flg.as_value());
86 }
87 if let Some(fld) = &self.fld {
88 for (fld_nm, fld_v) in fld {
89 if !fld_v.is_null() {
90 visit.visit_entry(fld_nm.as_value(), JsonValuable(fld_v.to_owned()).as_value());
91 }
92 }
93 }
94 }
95}
96
97#[must_use]
98pub fn setup_routes<S>(client_events: Option<&bool>) -> Option<Router<S>>
99where
100 S: Clone + Send + Sync + 'static,
101{
102 if client_events == Some(&true) {
103 let router = Router::new().route(
104 "/.ordinary/v1/events",
105 post(|body: Bytes| async move {
106 let mut decoder = ZlibDecoder::new(Vec::new());
107 if let Err(err) = decoder.write_all(body.as_ref()).await {
108 tracing::error!(%err);
109 return StatusCode::INTERNAL_SERVER_ERROR;
110 }
111 if let Err(err) = decoder.shutdown().await {
112 tracing::error!(%err);
113 return StatusCode::INTERNAL_SERVER_ERROR;
114 }
115
116 let mut events_slice = decoder.into_inner();
117
118 let events: Vec<ClientEvent> = match simd_json::from_slice(&mut events_slice) {
119 Ok(parsed) => parsed,
120 Err(err) => {
121 tracing::error!(%err);
122 return StatusCode::INTERNAL_SERVER_ERROR;
123 }
124 };
125
126 let client_span = tracing::info_span!("client");
127
128 client_span.in_scope(|| {
129 for event in events {
130 #[cfg(tracing_unstable)]
131 {
132 let level = event.lvl.as_str();
133 let message = event.msg.as_str();
134
135 match level {
136 "TRACE" => tracing::trace!(
137 _f = tracing::field::valuable(&event),
138 "{message}"
139 ),
140 "DEBUG" => tracing::debug!(
141 _f = tracing::field::valuable(&event),
142 "{message}"
143 ),
144 "INFO" => {
145 tracing::info!(
146 _f = tracing::field::valuable(&event),
147 "{message}"
148 );
149 }
150 "WARN" => {
151 tracing::warn!(
152 _f = tracing::field::valuable(&event),
153 "{message}"
154 );
155 }
156 _ => tracing::error!(
157 _f = tracing::field::valuable(&event),
158 "{message}"
159 ),
160 }
161 }
162
163 #[cfg(not(tracing_unstable))]
164 {
165 match event.lvl.as_ref() {
166 "TRACE" => tracing::trace!(evt = debug(&event)),
167 "DEBUG" => tracing::debug!(evt = debug(&event)),
168 "INFO" => tracing::info!(evt = debug(&event)),
169 "WARN" => tracing::warn!(evt = debug(&event)),
170 _ => tracing::error!(evt = debug(&event)),
171 }
172 }
173 }
174 });
175
176 StatusCode::OK
177 }),
178 );
179 return Some(router);
180 }
181
182 None
183}