diff --git a/Cargo.lock b/Cargo.lock index 2360c76..a289e0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" [[package]] name = "arbitrary" @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.13" +version = "1.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" dependencies = [ "jobserver", "libc", @@ -591,9 +591,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -650,7 +650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.4", + "miniz_oxide 0.8.5", ] [[package]] @@ -783,9 +783,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ "atomic-waker", "bytes", @@ -852,6 +852,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -1200,9 +1206,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] @@ -1317,9 +1323,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "lzma-rs" @@ -1369,9 +1375,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -1390,9 +1396,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", @@ -1495,9 +1501,9 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "openssl" -version = "0.10.70" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -1527,9 +1533,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.105" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", @@ -1663,6 +1669,52 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +dependencies = [ + "bitflags 2.8.0", + "hex", + "lazy_static", + "procfs-core", + "rustix", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.8.0", + "hex", +] + +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "libc", + "memchr", + "parking_lot", + "procfs", + "protobuf", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + [[package]] name = "quote" version = "1.0.38" @@ -1690,8 +1742,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.17", + "rand_core 0.9.1", + "zerocopy 0.8.20", ] [[package]] @@ -1711,7 +1763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.1", ] [[package]] @@ -1725,19 +1777,19 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.17", + "zerocopy 0.8.20", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags 2.8.0", ] @@ -1844,15 +1896,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "d34b5020fcdea098ef7d95e9f89ec15952123a4a039badd09fabebe9e963e839" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -1973,8 +2024,8 @@ dependencies = [ "dotenvy", "faster-hex", "indexmap 2.7.1", - "lazy_static", "notify", + "prometheus", "rand 0.9.0", "reqwest", "ring", @@ -2025,18 +2076,18 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -2054,9 +2105,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" dependencies = [ "itoa", "memchr", @@ -2159,9 +2210,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" @@ -2173,12 +2224,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2272,9 +2317,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.16.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", @@ -2441,9 +2486,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", @@ -2644,17 +2689,16 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ - "byteorder", "bytes", "data-encoding", "http", "httparse", "log", - "rand 0.8.5", + "rand 0.9.0", "sha1", "thiserror 2.0.11", "utf-8", @@ -2662,15 +2706,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "untrusted" @@ -2709,9 +2753,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" +checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1" dependencies = [ "serde", ] @@ -3015,9 +3059,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] @@ -3079,11 +3123,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.17" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" dependencies = [ - "zerocopy-derive 0.8.17", + "zerocopy-derive 0.8.20", ] [[package]] @@ -3099,9 +3143,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.17" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" dependencies = [ "proc-macro2", "quote", @@ -3216,27 +3260,27 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.14+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 1b9fd8e..002be6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "sculptor" authors = ["Shiroyashik "] version = "0.4.1-dev" -edition = "2021" +edition = "2024" publish = false [dependencies] @@ -31,7 +31,6 @@ semver = "1.0" walkdir = "2.5" indexmap = { version = "2.6", features = ["serde"] } zip = "2.2" -lazy_static = "1.5" notify = "8.0" # Crypto @@ -42,6 +41,7 @@ rand = "0.9" axum = { version = "0.8", features = ["ws", "macros", "http2"] } tower-http = { version = "0.6", features = ["trace"] } tokio = { version = "1.41", features = ["full"] } +prometheus = { version = "0.13.4", features = ["process"] } [dev-dependencies] cross = "0.2.5" diff --git a/Config.example.toml b/Config.example.toml index 9f367eb..a3531e2 100644 --- a/Config.example.toml +++ b/Config.example.toml @@ -4,8 +4,12 @@ listen = "0.0.0.0:6665" ## Don't touch if you don't know what you're doing # token = "" +## Enable Prometheus metrics +# metricsEnabled = true + ## Path to minecraft server folder ## Sculptor try to use ban list from it +## on Windows use double slash: "C:\\Servers\\1.20.1" # mcFolder = "~/minecraft_server" ## Can't work without at least one provider! diff --git a/Dockerfile b/Dockerfile index 56e214a..e40b8aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ## Chef # FROM clux/muslrust:stable AS chef -FROM rust:1.84-alpine3.21 AS chef +FROM rust:1.85-alpine3.21 AS chef USER root RUN apk add --no-cache musl-dev libressl-dev RUN cargo install cargo-chef diff --git a/src/api/figura/auth.rs b/src/api/figura/auth.rs index 67f4976..8dce942 100644 --- a/src/api/figura/auth.rs +++ b/src/api/figura/auth.rs @@ -1,7 +1,7 @@ -use axum::{debug_handler, extract::{Query, State}, response::{IntoResponse, Response}, routing::get, Router}; -use reqwest::StatusCode; +use axum::{extract::{Query, State}, http::HeaderMap, response::{IntoResponse, Response}, routing::get, Router}; +use reqwest::{header::USER_AGENT, StatusCode}; use ring::digest::{self, digest}; -use tracing::{error, info}; +use tracing::{error, info, instrument}; use crate::{auth::{has_joined, Userinfo}, utils::rand, AppState}; use super::types::auth::*; @@ -12,7 +12,6 @@ pub fn router() -> Router { .route("/verify", get(verify)) } -#[debug_handler] async fn id( // First stage of authentication Query(query): Query, @@ -25,10 +24,11 @@ async fn id( server_id } -#[debug_handler] +#[instrument(skip_all)] async fn verify( // Second stage of authentication Query(query): Query, + header: HeaderMap, State(state): State, ) -> Response { let server_id = query.id.clone(); @@ -47,17 +47,23 @@ async fn verify( if let Some((uuid, auth_provider)) = userinfo { let umanager = state.user_manager; if umanager.is_banned(&uuid) { - info!("[Authentication] {nickname} tried to log in, but was banned"); + info!("{nickname} tried to log in, but was banned"); return (StatusCode::BAD_REQUEST, "You're banned!".to_string()).into_response(); } - info!("[Authentication] {nickname} logged in using {}", auth_provider.name); - let userinfo = Userinfo { + let mut userinfo = Userinfo { nickname, uuid, token: Some(server_id.clone()), auth_provider, ..Default::default() }; + if let Some(agent) = header.get(USER_AGENT) { + if let Ok(agent) = agent.to_str() { + userinfo.version = agent.to_string(); + } + } + info!("{} logged in using {} with {}", userinfo.nickname, userinfo.auth_provider.name, userinfo.version); + match umanager.insert(uuid, server_id.clone(), userinfo.clone()) { Ok(_) => {}, Err(_) => { @@ -70,7 +76,7 @@ async fn verify( } (StatusCode::OK, server_id.to_string()).into_response() } else { - info!("[Authentication] failed to verify {nickname}"); + info!("failed to verify {nickname}"); (StatusCode::BAD_REQUEST, "failed to verify".to_string()).into_response() } } \ No newline at end of file diff --git a/src/api/figura/websocket/handler.rs b/src/api/figura/websocket/handler.rs index 72f9987..d7c5a4e 100644 --- a/src/api/figura/websocket/handler.rs +++ b/src/api/figura/websocket/handler.rs @@ -15,6 +15,7 @@ pub async fn initial( ws.on_upgrade(|socket| handle_socket(socket, state)) } +#[instrument(skip_all)] async fn handle_socket(mut ws: WebSocket, state: AppState) { // Trying authenticate & get user data or dropping connection match authenticate(&mut ws, &state).await { @@ -44,7 +45,7 @@ async fn handle_socket(mut ws: WebSocket, state: AppState) { // Starting main worker if let Err(kind) = main_worker(&mut session, &mut ws, &state).await { - tracing::error!("[WebSocket] Main worker halted due to {}.", kind) + tracing::info!(error = %kind, nickname = %session.user.nickname, "Main worker exited"); } for (_, handle) in session.sub_workers_aborthandles { @@ -56,7 +57,7 @@ async fn handle_socket(mut ws: WebSocket, state: AppState) { state.user_manager.remove(&user.uuid); }, Err(kind) => { - tracing::info!("[WebSocket] Can't authenticate: {}", kind); + tracing::info!(error = %kind, "Can't authenticate"); } } @@ -64,7 +65,7 @@ async fn handle_socket(mut ws: WebSocket, state: AppState) { if let Err(kind) = ws.send(Message::Close(None)).await { tracing::trace!("[WebSocket] Closing fault: {}", kind) } } -#[instrument(skip_all, fields(nickname = %session.user.nickname))] +#[instrument(skip_all, parent = None, fields(nickname = %session.user.nickname))] async fn main_worker(session: &mut WSSession, ws: &mut WebSocket, state: &AppState) -> anyhow::Result<()> { tracing::debug!("WebSocket control for {} is transferred to the main worker", session.user.nickname); loop { diff --git a/src/api/figura/websocket/types/c2s.rs b/src/api/figura/websocket/types/c2s.rs index 945c228..bc30eb1 100644 --- a/src/api/figura/websocket/types/c2s.rs +++ b/src/api/figura/websocket/types/c2s.rs @@ -89,6 +89,16 @@ impl From for Vec { a } } +impl C2SMessage { + pub fn name(&self) -> &'static str { + match self { + C2SMessage::Token(_) => "c2s>token", + C2SMessage::Ping(_, _, _) => "c2s>ping", + C2SMessage::Sub(_) => "c2s>sub", + C2SMessage::Unsub(_) => "c2s>unsub", + } + } +} // impl<'a> C2SMessage<'a> { // pub fn to_array(&self) -> Box<[u8]> { diff --git a/src/api/figura/websocket/types/mod.rs b/src/api/figura/websocket/types/mod.rs index fee43d8..24fa671 100644 --- a/src/api/figura/websocket/types/mod.rs +++ b/src/api/figura/websocket/types/mod.rs @@ -3,6 +3,8 @@ mod s2c; mod errors; mod session; +use std::time::Instant; + pub use session::*; pub use errors::*; pub use c2s::*; @@ -10,6 +12,8 @@ pub use s2c::*; use axum::extract::ws::{Message, WebSocket}; +use crate::{PINGS, PINGS_ERROR}; + pub trait RecvAndDecode { async fn recv_and_decode(&mut self) -> Result; } @@ -21,9 +25,17 @@ impl RecvAndDecode for WebSocket { if let Message::Close(frame) = msg { return Err(RADError::Close(frame.map(|f| format!("code: {}, reason: {}", f.code, f.reason)))); } + + let start = Instant::now(); let data = msg.into_data(); - C2SMessage::try_from(data.as_ref()) - .map_err(|e| RADError::DecodeError(e, faster_hex::hex_string(&data))) + let msg = C2SMessage::try_from(data.as_ref()) + .map_err(|e| { PINGS_ERROR.inc(); RADError::DecodeError(e, faster_hex::hex_string(&data)) }); + + let latency = start.elapsed().as_secs_f64(); + PINGS + .with_label_values(&[msg.as_ref().map(|m| m.name()).unwrap_or("error")]) + .observe(latency); + msg } } \ No newline at end of file diff --git a/src/api/figura/websocket/types/s2c.rs b/src/api/figura/websocket/types/s2c.rs index b17aeaa..d8d0129 100644 --- a/src/api/figura/websocket/types/s2c.rs +++ b/src/api/figura/websocket/types/s2c.rs @@ -85,6 +85,18 @@ impl From for Vec { } } } +impl S2CMessage { + pub fn name(&self) -> &'static str { + match self { + S2CMessage::Auth => "s2c>auth", + S2CMessage::Ping(_, _, _, _) => "s2c>ping", + S2CMessage::Event(_) => "s2c>event", + S2CMessage::Toast(_, _, _) => "s2c>toast", + S2CMessage::Chat(_) => "s2c>chat", + S2CMessage::Notice(_) => "s2c>notice", + } + } +} // impl<'a> S2CMessage<'a> { // pub fn to_array(&self) -> Box<[u8]> { diff --git a/src/api/sculptor/users.rs b/src/api/sculptor/users.rs index 5b39881..dc33202 100644 --- a/src/api/sculptor/users.rs +++ b/src/api/sculptor/users.rs @@ -2,10 +2,11 @@ use axum::{ extract::{Path, State}, Json }; +use dashmap::DashMap; use tracing::{debug, info}; use uuid::Uuid; -use crate::{api::errors::internal_and_log, auth::{Token, Userinfo}, ApiResult, AppState}; +use crate::{auth::{Token, Userinfo}, ApiResult, AppState}; pub(super) async fn create_user( Token(token): Token, @@ -50,17 +51,17 @@ pub(super) async fn unban( pub(super) async fn list( Token(token): Token, State(state): State, -) -> ApiResult { +) -> ApiResult>> { state.config.read().await.clone().verify_token(&token)?; - serde_json::to_string_pretty(&state.user_manager.get_all_registered()).map_err(|err| { internal_and_log(err) }) + Ok(Json(state.user_manager.get_all_registered())) } pub(super) async fn list_sessions( Token(token): Token, State(state): State, -) -> ApiResult { +) -> ApiResult>> { state.config.read().await.clone().verify_token(&token)?; - serde_json::to_string_pretty(&state.user_manager.get_all_authenticated()).map_err(|err| { internal_and_log(err) }) + Ok(Json(state.user_manager.get_all_authenticated())) } \ No newline at end of file diff --git a/src/auth/auth.rs b/src/auth/auth.rs index a115049..ab78b99 100644 --- a/src/auth/auth.rs +++ b/src/auth/auth.rs @@ -182,6 +182,12 @@ pub struct UManager { registered: Arc>, } +impl Default for UManager { + fn default() -> Self { + Self::new() + } +} + impl UManager { pub fn new() -> Self { Self { @@ -229,6 +235,7 @@ impl UManager { if userinfo.rank != Userinfo::default().rank { exist.rank = userinfo.rank }; if userinfo.token.is_some() { exist.token = userinfo.token }; if userinfo.version != Userinfo::default().version { exist.version = userinfo.version }; + exist.last_used = userinfo.last_used; }).or_insert(usercopy); } pub fn get( @@ -274,7 +281,6 @@ impl UManager { } // End of User manager -#[axum::debug_handler] #[instrument(skip_all)] pub async fn check_auth( token: Option, diff --git a/src/main.rs b/src/main.rs index 4e6003e..e120835 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,10 +6,9 @@ use axum::{ use dashmap::DashMap; use tracing_panic::panic_hook; use tracing_subscriber::{fmt::{self, time::ChronoLocal}, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; -use std::{path::PathBuf, sync::Arc, env::var}; +use std::{env::var, path::PathBuf, sync::{Arc, LazyLock}}; use tokio::{fs, sync::RwLock, time::Instant}; use tower_http::trace::TraceLayer; -use lazy_static::lazy_static; // Consts mod consts; @@ -18,12 +17,13 @@ pub use consts::*; // Errors pub use api::errors::{ApiResult, ApiError}; +// Metrics +mod metrics; +pub use metrics::*; + // API mod api; -use api::{ - figura::{ws, info as api_info, profile as api_profile, auth as api_auth, assets as api_assets}, - // v1::{}, -}; +use api::figura::{ws, info as api_info, profile as api_profile, auth as api_auth, assets as api_assets}; // Auth mod auth; @@ -37,23 +37,21 @@ use state::{Config, AppState}; mod utils; use utils::*; -lazy_static! { - pub static ref LOGGER_VAR: String = { - var(LOGGER_ENV).unwrap_or(String::from("info")) - }; - pub static ref CONFIG_VAR: String = { - var(CONFIG_ENV).unwrap_or(String::from("Config.toml")) - }; - pub static ref LOGS_VAR: String = { - var(LOGS_ENV).unwrap_or(String::from("logs")) - }; - pub static ref ASSETS_VAR: String = { - var(ASSETS_ENV).unwrap_or(String::from("data/assets")) - }; - pub static ref AVATARS_VAR: String = { - var(AVATARS_ENV).unwrap_or(String::from("data/avatars")) - }; -} +pub static LOGGER_VAR: LazyLock = LazyLock::new(|| { + var(LOGGER_ENV).unwrap_or(String::from("info")) +}); +pub static CONFIG_VAR: LazyLock = LazyLock::new(|| { + var(CONFIG_ENV).unwrap_or(String::from("Config.toml")) +}); +pub static LOGS_VAR: LazyLock = LazyLock::new(|| { + var(LOGS_ENV).unwrap_or(String::from("logs")) +}); +pub static ASSETS_VAR: LazyLock = LazyLock::new(|| { + var(ASSETS_ENV).unwrap_or(String::from("data/assets")) +}); +pub static AVATARS_VAR: LazyLock = LazyLock::new(|| { + var(AVATARS_ENV).unwrap_or(String::from("data/avatars")) +}); #[tokio::main] async fn main() -> Result<()> { @@ -128,11 +126,11 @@ async fn app() -> Result { } // Config - let config = Arc::new(RwLock::new(Config::parse(CONFIG_VAR.clone().into()))); - let listen = config.read().await.listen.clone(); - let limit = get_limit_as_bytes(config.read().await.limitations.max_avatar_size as usize); + let config = Config::parse(CONFIG_VAR.clone().into()); + let listen = config.listen.clone(); + let limit = get_limit_as_bytes(config.limitations.max_avatar_size as usize); - if config.read().await.assets_updater_enabled { + if config.assets_updater_enabled { // Force update assets if folder or hash file doesn't exists. if !(PathBuf::from(&*ASSETS_VAR).is_dir() && get_path_to_assets_hash().is_file()) { tracing::debug!("Removing broken assets..."); @@ -164,7 +162,7 @@ async fn app() -> Result { session: Arc::new(DashMap::new()), subscribes: Arc::new(DashMap::new()), figura_versions: Arc::new(RwLock::new(None)), - config, + config: Arc::new(RwLock::new(config.clone())), }; // Automatic update of configuration/ban list while the server is running @@ -175,7 +173,7 @@ async fn app() -> Result { Arc::clone(&state.config) )); // Blacklist auto update - if state.config.read().await.mc_folder.exists() { + if config.mc_folder.exists() { tokio::spawn(update_bans_from_minecraft( state.config.read().await.mc_folder.clone(), Arc::clone(&state.user_manager), @@ -200,8 +198,19 @@ async fn app() -> Result { .nest("/api", api) .route("/api/", get(check_auth)) .route("/ws", get(ws)) - .with_state(state) - .layer(TraceLayer::new_for_http().on_request(())) + .layer(TraceLayer::new_for_http() + // .on_request(|request: &axum::http::Request<_>, _span: &tracing::Span| { + // // only for developing purposes + // tracing::trace!(headers = ?request.headers(), "started processing request"); + // }) + .on_response(|response: &axum::http::Response<_>, latency: std::time::Duration, _span: &tracing::Span| { + tracing::trace!(latency = ?latency, status = ?response.status(), "finished processing request"); + }) + .on_request(()) + ) + .layer(axum::middleware::from_fn(track_metrics)) + .merge(metrics::metrics_router(config.metrics_enabled)) + .with_state(state) .route("/health", get(|| async { "ok" })); let listener = tokio::net::TcpListener::bind(listen).await?; diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 0000000..5e8f493 --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,71 @@ + +use std::{sync::LazyLock, time::Instant}; + +use axum::{body::Body, extract::State, http::{Request, Response}, middleware::Next, routing::get, Router}; +use prometheus::{proto::{Metric, MetricType}, register_histogram_vec, register_int_counter}; +use reqwest::StatusCode; + +use crate::state::AppState; + +pub fn metrics_router(enabled: bool) -> Router { + if !enabled { return Router::new(); } + tracing::info!("Metrics enabled! You can access them on /metrics"); + Router::new() + .route("/metrics", get(metrics)) +} + +async fn metrics(State(state): State) -> String { + let mut metric_families = prometheus::gather(); + + // Add new custom metrics + let players = { + let mut metric = prometheus::proto::Metric::default(); + metric.set_gauge(prometheus::proto::Gauge::default()); + metric.mut_gauge().set_value(state.session.len() as f64); + create_mf("players_count".to_string(), "Number of players".to_string(), MetricType::GAUGE, metric) + }; + + metric_families.push(players); + + prometheus::TextEncoder::new() + .encode_to_string(&metric_families) + .unwrap() +} + +#[inline] +fn create_mf(name: String, help: String, field_type: MetricType, metric: Metric) -> prometheus::proto::MetricFamily { + let mut mf = prometheus::proto::MetricFamily::default(); + mf.set_name(name); + mf.set_help(help); + mf.set_field_type(field_type); + mf.mut_metric().push(metric); + mf +} + +pub async fn track_metrics(req: Request, next: Next) -> Result, StatusCode> { + let start = Instant::now(); + let uri = req.uri().path().to_string(); + + // Call the next middleware or handler + let response = next.run(req).await; + + let latency = start.elapsed().as_secs_f64(); + + REQUESTS + .with_label_values(&[&uri, response.status().as_str()]) + .observe(latency); + + Ok(response) +} + +pub static PINGS_ERROR: LazyLock = LazyLock::new(|| { + register_int_counter!("pings_error", "Number of ping decoding errors").unwrap() +}); + +pub static REQUESTS: LazyLock = LazyLock::new(|| { + register_histogram_vec!("requests_count", "Number of requests", &["uri", "code"], vec![0.025, 0.250, 0.500]).unwrap() +}); + +pub static PINGS: LazyLock = LazyLock::new(|| { + register_histogram_vec!("pings_count", "Number of pings", &["type"], vec![0.000003, 0.00002, 0.0002]).unwrap() +}); \ No newline at end of file diff --git a/src/state/config.rs b/src/state/config.rs index a79d583..b32267d 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -10,6 +10,8 @@ use crate::auth::{default_authproviders, AuthProviders, Userinfo}; #[serde(rename_all = "camelCase")] pub struct Config { pub listen: String, + #[serde(default)] + pub metrics_enabled: bool, pub token: Option, pub assets_updater_enabled: bool, pub motd: CMotd,