refactor?

This commit is contained in:
2024-02-16 13:43:24 +01:00
parent d180e72373
commit 506ce117d3
16 changed files with 44 additions and 34 deletions

17
snitch_srv/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "snitch_srv"
version = "1.0.0"
edition = "2021"
[dependencies]
snitch = { path = "../snitch" }
anyhow = "*"
log = "0.4"
serde = { version = "1", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "postgres", "sqlite", "migrate", "macros", "chrono"] }
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["handlebars"] }
rocket_db_pools = { version = "0.1.0-rc.2", features = ["sqlx_postgres"] }
rocket = { version = "0.5.0-rc.2", features = ["json"] }
clap = { version = "4.0.23", features = ["derive", "env"] }

3
snitch_srv/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rerun-if-changed=migrations");
}

View File

@ -0,0 +1 @@
DROP TABLE record;

View File

@ -0,0 +1,10 @@
CREATE TABLE record (
id SERIAL PRIMARY KEY,
time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
service VARCHAR(255) NOT NULL,
severity VARCHAR(16) NOT NULL,
message TEXT NOT NULL,
file_path TEXT,
file_line INTEGER
);

49
snitch_srv/src/api.rs Normal file
View File

@ -0,0 +1,49 @@
use rocket::{http::Status, serde::json::Json, State};
use snitch::LogMsg;
use sqlx::query;
use crate::database::Database;
#[post("/", data = "<record>")]
pub async fn post_record(db: &State<Database>, record: Json<LogMsg>) -> Status {
let mut conn = match db.inner().acquire().await {
Ok(conn) => conn,
Err(e) => {
log::error!("failed to get db connection: {e}");
return Status::InternalServerError;
}
};
let LogMsg {
time: _,
severity,
service,
message,
file,
line,
} = record.into_inner();
let result = query!(
"
INSERT INTO record(service,
severity,
message,
file_path,
file_line)
VALUES ($1, $2, $3, $4, $5);
",
service,
format!("{:?}", severity),
message,
file,
line.map(|line| line as i32),
)
.execute(&mut conn)
.await;
if let Err(e) = result {
log::error!("failed to insert record: {e:?}");
}
Status::Ok
}

View File

@ -0,0 +1,62 @@
use chrono::{DateTime, Local};
use rocket::{http::Status, response::content::RawHtml, State};
use rocket_dyn_templates::{context, Template};
use serde::Serialize;
use sqlx::query;
use crate::database::Database;
#[get("/")]
pub async fn index(db: &State<Database>) -> Result<RawHtml<Template>, Status> {
let mut conn = db.inner().acquire().await.map_err(|e| {
log::error!("failed to get db connection: {e}");
Status::InternalServerError
})?;
// TODO:
// maybe only show most recent error for each service in dashboard view, i.e.:
// select distinct on (service) time, message from (select * from record order by time desc) as foo;
let messages = query!(
"
SELECT time, service, severity, message, file_path, file_line FROM record
ORDER BY time DESC;
"
)
.fetch_all(&mut conn)
.await
.map_err(|e| {
log::error!("failed to query record: {e}");
Status::InternalServerError
})?;
#[derive(Serialize)]
struct Record {
time: DateTime<Local>,
severity: String,
service: String,
message: String,
location: String,
}
let messages: Vec<_> = messages
.into_iter()
.map(|record| Record {
time: record.time.into(),
severity: record.severity,
service: record.service,
message: record.message,
location: record
.file_path
.zip(record.file_line)
.map(|(path, line)| format!("{path}:{line}"))
.unwrap_or_default(),
})
.collect();
Ok(RawHtml(Template::render(
"index",
context! {
messages: &messages
},
)))
}

View File

@ -0,0 +1,12 @@
use crate::Opt;
use sqlx::PgPool;
pub type Database = PgPool;
pub(crate) async fn connect(opt: &Opt) -> anyhow::Result<Database> {
let pool = PgPool::connect(&opt.db).await?;
sqlx::migrate!().run(&pool).await?;
Ok(pool)
}

34
snitch_srv/src/main.rs Normal file
View File

@ -0,0 +1,34 @@
#[macro_use]
extern crate rocket;
mod api;
mod dashboard;
mod database;
use clap::Parser;
use rocket_dyn_templates::Template;
#[derive(Parser)]
struct Opt {
/// PostgreSQL connect string
///
/// e.g. postgresql://user:pass@localhost:5432/database
#[clap(
long,
env = "DATABASE_URL",
default_value = "postgresql://postgres@localhost:5432/postgres"
)]
db: String,
}
#[launch]
async fn rocket() -> _ {
let opt = Opt::parse();
let db = database::connect(&opt).await.expect("connect to database");
rocket::build()
.manage(db)
.mount("/", routes![dashboard::index, api::post_record])
.attach(Template::fairing())
}

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<link rel="icon" type="image/svg+xml" href="/static/icon.svg">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu|Ubuntu+Mono&display=swap">
<title>snitch</title>
<style>
body {
color: white;
background-color: #302f3b;
font-family: 'Ubuntu', sans-serif;
}
th {
color: wheat;
}
td {
border: 0.1em solid #5b3f63;
padding: 0.5em;
font-family: 'Ubuntu Mono', mono;
}
</style>
</head>
<body>
<table>
<tr>
<th>Service</th>
<th>Severity</th>
<th>Time</th>
<th>Message</th>
<th>Location</th>
</tr>
{{#each messages}}
<tr>
<td>{{this.service}}</td>
<td>{{this.severity}}</td>
<td>{{this.time}}</td>
<td>{{this.message}}</td>
<td>{{this.location}}</td>
</tr>
{{/each}}
</table>
</body>
</html>