irongit

Git hosting and a container registry in one Rust binary (axum + Astro)

irongit/backend/src/clientlog.rs
51 lines1.7 KBRust
1//! Browser logs. The page script batches console errors, uncaught
2//! exceptions and failed fetches to POST /api/v1/client-logs; they are
3//! written to LOG_DIR/frontend.<date>.log with the user and page.
4
5use axum::{
6 Json, Router,
7 extract::DefaultBodyLimit,
8 http::{HeaderMap, StatusCode},
9 routing::post,
10};
11use serde::Deserialize;
12
13use crate::{
14 auth::{self, MaybeViewer},
15 logging::FRONTEND_TARGET,
16 state::AppState,
17};
18
19pub fn router() -> Router<AppState> {
20 Router::new().route("/api/v1/client-logs", post(ingest).layer(DefaultBodyLimit::max(64 * 1024)))
21}
22
23#[derive(Deserialize)]
24struct Batch {
25 entries: Vec<Entry>,
26}
27
28#[derive(Deserialize)]
29struct Entry {
30 level: String,
31 message: String,
32 url: String,
33 at: Option<String>,
34 stack: Option<String>,
35}
36
37async fn ingest(MaybeViewer(viewer): MaybeViewer, headers: HeaderMap, Json(batch): Json<Batch>) -> StatusCode {
38 let user = viewer.as_ref().map(|v| v.name.as_str()).unwrap_or("-");
39 let agent = auth::user_agent(&headers).unwrap_or("-");
40 for entry in batch.entries.into_iter().take(50) {
41 let message: String = entry.message.chars().take(2000).collect();
42 let stack: String = entry.stack.unwrap_or_default().chars().take(4000).collect();
43 let at = entry.at.unwrap_or_default();
44 match entry.level.as_str() {
45 "error" => tracing::error!(target: FRONTEND_TARGET, user, url = %entry.url, at, agent, stack, "{message}"),
46 "warn" => tracing::warn!(target: FRONTEND_TARGET, user, url = %entry.url, at, agent, "{message}"),
47 _ => tracing::info!(target: FRONTEND_TARGET, user, url = %entry.url, at, agent, "{message}"),
48 }
49 }
50 StatusCode::NO_CONTENT
51}