feat(corp/rih/backend): sprinkle some logging all over the place
Change-Id: Ifd55a0bf75070b1d47c2d65c32960f05ad7040a0 Reviewed-on: https://cl.tvl.fyi/c/depot/+/8736 Tested-by: BuildkiteCI Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
parent
f72d1f459d
commit
75ffea3fe6
4 changed files with 100 additions and 10 deletions
1
corp/rih/backend/Cargo.lock
generated
1
corp/rih/backend/Cargo.lock
generated
|
@ -671,6 +671,7 @@ name = "rih-backend"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"rouille",
|
||||
"rust-s3",
|
||||
"serde",
|
||||
|
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
log = "0.4"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.3.3", features = ["v4", "serde"] }
|
||||
|
@ -15,5 +16,5 @@ default-features = false
|
|||
|
||||
[dependencies.rust-s3]
|
||||
version = "0.33"
|
||||
default_features = false
|
||||
default-features = false
|
||||
features = [ "sync-rustls-tls" ]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use log::{debug, error, info, warn, LevelFilter};
|
||||
use rouille::{Request, Response};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
|
@ -7,6 +8,8 @@ use std::net::SocketAddr;
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use uuid::Uuid;
|
||||
|
||||
mod yandex_log;
|
||||
|
||||
/// Represents the request sent by the frontend application.
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FrontendReq {
|
||||
|
@ -42,49 +45,87 @@ impl Record {
|
|||
|
||||
fn persist_record(ip: &SocketAddr, record: &Record) -> Result<()> {
|
||||
let bucket_name = "rih-backend-data";
|
||||
let credentials = s3::creds::Credentials::from_env()?;
|
||||
let credentials =
|
||||
s3::creds::Credentials::from_env().context("failed to initialise storage credentials")?;
|
||||
|
||||
let yandex_region: s3::Region = s3::Region::Custom {
|
||||
region: "ru-central1".to_string(),
|
||||
endpoint: "storage.yandexcloud.net".to_string(),
|
||||
};
|
||||
|
||||
let bucket = s3::Bucket::new(bucket_name, yandex_region, credentials)?;
|
||||
let bucket = s3::Bucket::new(bucket_name, yandex_region, credentials)
|
||||
.context("failed to initialise storage client")?;
|
||||
|
||||
let path_uuid = Uuid::new_v4();
|
||||
let epoch = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
|
||||
let epoch = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.context("failed to get current time")?
|
||||
.as_secs();
|
||||
|
||||
let path = format!("/records/{}-{}.json", epoch, path_uuid);
|
||||
|
||||
info!("writing record to '{}'", path);
|
||||
|
||||
let data = serde_json::json!({
|
||||
"ip": ip.to_string(),
|
||||
"record": record,
|
||||
});
|
||||
|
||||
let _response = bucket.put_object(path, data.to_string().as_bytes());
|
||||
let response = bucket
|
||||
.put_object(path, data.to_string().as_bytes())
|
||||
.context("failed to persist storage object")?;
|
||||
|
||||
debug!(
|
||||
"Object Storage response: ({}) {}",
|
||||
response.status_code(),
|
||||
response.as_str().unwrap_or("<unprintable>")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_submit(req: &Request) -> Result<Response> {
|
||||
let submitted: FrontendReq = rouille::input::json::json_input(req)?;
|
||||
let submitted: FrontendReq =
|
||||
rouille::input::json::json_input(req).context("failed to deserialise frontend request")?;
|
||||
|
||||
if !submitted.record.validate() {
|
||||
bail!("invalid record: {:?}", submitted.record);
|
||||
}
|
||||
|
||||
persist_record(req.remote_addr(), &submitted.record)?;
|
||||
persist_record(req.remote_addr(), &submitted.record).context("failed to persist record")?;
|
||||
|
||||
Ok(Response::text("success"))
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
log::set_logger(&yandex_log::YANDEX_CLOUD_LOGGER)
|
||||
.map(|()| log::set_max_level(LevelFilter::Info))
|
||||
.expect("log configuration must succeed");
|
||||
let port = env::var("PORT").unwrap_or_else(|_| /* rihb = */ "7442".to_string());
|
||||
let listen = format!("0.0.0.0:{port}");
|
||||
|
||||
info!("launching rih-backend on: {}", listen);
|
||||
|
||||
rouille::start_server(&listen, move |request| {
|
||||
if request.url() == "/submit" {
|
||||
if request.method() == "POST" && request.url() == "/submit" {
|
||||
info!("handling submit request from {}", request.remote_addr());
|
||||
match handle_submit(request) {
|
||||
Ok(response) => response,
|
||||
Err(_err) => Response::empty_400(), // TODO
|
||||
Ok(response) => {
|
||||
info!("submit handled successfully");
|
||||
response
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to handle submit: {}", err);
|
||||
Response::empty_400()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"no matching route for request: {} {}",
|
||||
request.method(),
|
||||
request.url()
|
||||
);
|
||||
|
||||
Response::empty_404()
|
||||
}
|
||||
});
|
||||
|
|
47
corp/rih/backend/src/yandex_log.rs
Normal file
47
corp/rih/backend/src/yandex_log.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
//! Implements a `log::Log` logger that adheres to the structure
|
||||
//! expected by Yandex Cloud Serverless logs.
|
||||
//!
|
||||
//! https://cloud.yandex.ru/docs/serverless-containers/concepts/logs
|
||||
|
||||
use log::{Level, Log};
|
||||
use serde_json::json;
|
||||
|
||||
pub struct YandexCloudLogger;
|
||||
|
||||
pub const YANDEX_CLOUD_LOGGER: YandexCloudLogger = YandexCloudLogger;
|
||||
|
||||
fn level_map(level: &Level) -> &'static str {
|
||||
match level {
|
||||
Level::Error => "ERROR",
|
||||
Level::Warn => "WARN",
|
||||
Level::Info => "INFO",
|
||||
Level::Debug => "DEBUG",
|
||||
Level::Trace => "TRACE",
|
||||
}
|
||||
}
|
||||
|
||||
impl Log for YandexCloudLogger {
|
||||
fn enabled(&self, _: &log::Metadata<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record<'_>) {
|
||||
if !self.enabled(record.metadata()) {
|
||||
return;
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
json!({
|
||||
"level": level_map(&record.level()),
|
||||
"message": record.args().to_string(),
|
||||
"target": record.target(),
|
||||
"module": record.module_path(),
|
||||
"file": record.file(),
|
||||
"line": record.line(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
Loading…
Reference in a new issue