diff --git a/Cargo.lock b/Cargo.lock index 5458cb0..5cae829 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.71" @@ -155,6 +170,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + [[package]] name = "cookie" version = "0.16.2" @@ -227,6 +255,41 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "encoding_rs" version = "0.8.32" @@ -236,6 +299,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -377,7 +446,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -390,6 +459,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" + [[package]] name = "heck" version = "0.4.1" @@ -402,6 +477,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.9" @@ -495,6 +576,35 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.2.3" @@ -533,7 +643,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", ] [[package]] @@ -617,6 +739,7 @@ dependencies = [ "lazy-regex", "serde", "serde_json", + "serde_with", "strum_macros", "tokio", "tower-cookies", @@ -724,6 +847,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -1142,6 +1274,35 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.2", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1186,6 +1347,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum_macros" version = "0.25.3" @@ -1619,6 +1786,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.42.0" diff --git a/Cargo.toml b/Cargo.toml index 330aaa8..5956b28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ tokio = { version = "1.32.0", features = ["full"] } # Serde / json serde = { version = "1.0", features = ["derive"] } serde_json = "1" +serde_with = "3" # Axum axum = { version = "0.6.20" } tower-http = { version = "0.4.4", features = ["fs"] } diff --git a/src/error.rs b/src/error.rs index 8452bab..e414b15 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,11 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; +use serde::Serialize; pub type Result = core::result::Result; -#[derive(Clone, Debug, strum_macros::AsRefStr)] +#[derive(Clone, Debug, Serialize, strum_macros::AsRefStr)] +#[serde(tag = "type", content = "data")] pub enum Error { LoginFail, diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..eef3b00 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,65 @@ +use std::time::SystemTime; + +use crate::{Error, Result}; +use crate::ctx::Ctx; +use crate::error::ClientError; +use axum::http::{Method, Uri}; +use serde::Serialize; +use serde_json::{json, Value}; +use serde_with::skip_serializing_none; +use uuid::Uuid; + +pub async fn log_request( + uuid: Uuid, + req_method: Method, + uri: Uri, + ctx: Option, + service_error: Option<&Error>, + client_error: Option, + ) -> Result<()> { + let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis(); + + let error_type = service_error.map(|se| se.as_ref().to_string()); + let error_data = serde_json::to_value(service_error).ok().and_then(|mut v| v.get_mut("data").map(|v| v.take())); + + // Create the RequestLogLine. + let log_line = RequestLogLine { + uuid: uuid.to_string(), + timestamp: timestamp.to_string(), + + req_path: uri.path().to_string(), + req_method: req_method.to_string(), + + user_id: ctx.map(|ctx| ctx.user_id()), + + client_error_type: client_error.map(|e| e.as_ref().to_string()), + + error_type, + error_data, + }; + + println!(" ->> log request: \n{}", json!(log_line)); + + // TODO: Send to cloud-watch or something. + + Ok(()) +} + +#[skip_serializing_none] +#[derive(Serialize)] +struct RequestLogLine { + uuid: String, // uuid string formatted + timestamp: String, // (should be iso8601) + + // -- User and context attributes. + user_id: Option, + + // -- http request attributes. + req_path: String, + req_method: String, + + // -- Errors attributes. + client_error_type: Option, + error_type: Option, + error_data: Option, +} diff --git a/src/main.rs b/src/main.rs index b8eb052..5f1c582 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use crate::model::ModelController; pub use self::error::{Error, Result}; +use self::ctx::Ctx; +use self::log::log_request; use std::net::SocketAddr; @@ -20,6 +22,7 @@ use uuid::Uuid; mod ctx; mod error; +mod log; mod web; mod model; @@ -57,7 +60,7 @@ async fn main() -> Result<()>{ /// Map the response to add headers, etc. /// /// * `res`: the response to map -async fn main_response_mapper(res: Response) -> Response { +async fn main_response_mapper(ctx: Option, uri: Uri, req_method: Method, res: Response) -> Response { println!("->> {:<12} - main_response_mapper", "RES_MAPPER"); let uuid = Uuid::new_v4(); @@ -82,8 +85,9 @@ async fn main_response_mapper(res: Response) -> Response { (*status_code, Json(client_error_body)).into_response() }); - // -- TODO: Build and log the server log line. - println!(" ->> server log line - {uuid} - Error: {service_error:?}"); + // -- Build and log the server log line. + let client_error = client_status_error.unzip().1; + log_request(uuid, req_method, uri, ctx, service_error, client_error).await; println!(); error_response.unwrap_or(res)