about summary refs log tree commit diff
path: root/corp/rih/backend/src
diff options
context:
space:
mode:
Diffstat (limited to 'corp/rih/backend/src')
-rw-r--r--corp/rih/backend/src/main.rs59
-rw-r--r--corp/rih/backend/src/yandex_log.rs47
2 files changed, 97 insertions, 9 deletions
diff --git a/corp/rih/backend/src/main.rs b/corp/rih/backend/src/main.rs
index 168941e6f4c7..c696858da5a4 100644
--- a/corp/rih/backend/src/main.rs
+++ b/corp/rih/backend/src/main.rs
@@ -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()
         }
     });
diff --git a/corp/rih/backend/src/yandex_log.rs b/corp/rih/backend/src/yandex_log.rs
new file mode 100644
index 000000000000..64bb4ff97dc5
--- /dev/null
+++ b/corp/rih/backend/src/yandex_log.rs
@@ -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) {}
+}