this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add event logger with database logging and macros

Introduce an EventLogger that logs to stdout and optionally to the database.
Replace println!/eprintln! in client_communication.rs and database.rs with
logging macros (info_elog!, warn_elog!, error_elog!, etc). Add logs table
creation for both SQLite and Postgres. Update dependencies for chrono.

+567 -99
+129
Cargo.lock
··· 27 27 ] 28 28 29 29 [[package]] 30 + name = "android-tzdata" 31 + version = "0.1.1" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 + 35 + [[package]] 36 + name = "android_system_properties" 37 + version = "0.1.5" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 + dependencies = [ 41 + "libc", 42 + ] 43 + 44 + [[package]] 30 45 name = "async-stream" 31 46 version = "0.3.6" 32 47 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 191 206 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 192 207 193 208 [[package]] 209 + name = "chrono" 210 + version = "0.4.41" 211 + source = "registry+https://github.com/rust-lang/crates.io-index" 212 + checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 213 + dependencies = [ 214 + "android-tzdata", 215 + "iana-time-zone", 216 + "js-sys", 217 + "num-traits", 218 + "wasm-bindgen", 219 + "windows-link", 220 + ] 221 + 222 + [[package]] 194 223 name = "cipher" 195 224 version = "0.4.4" 196 225 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 212 241 ] 213 242 214 243 [[package]] 244 + name = "core-foundation-sys" 245 + version = "0.8.7" 246 + source = "registry+https://github.com/rust-lang/crates.io-index" 247 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 248 + 249 + [[package]] 215 250 name = "cpufeatures" 216 251 version = "0.2.17" 217 252 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 686 721 ] 687 722 688 723 [[package]] 724 + name = "iana-time-zone" 725 + version = "0.1.63" 726 + source = "registry+https://github.com/rust-lang/crates.io-index" 727 + checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 728 + dependencies = [ 729 + "android_system_properties", 730 + "core-foundation-sys", 731 + "iana-time-zone-haiku", 732 + "js-sys", 733 + "log", 734 + "wasm-bindgen", 735 + "windows-core", 736 + ] 737 + 738 + [[package]] 739 + name = "iana-time-zone-haiku" 740 + version = "0.1.2" 741 + source = "registry+https://github.com/rust-lang/crates.io-index" 742 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 743 + dependencies = [ 744 + "cc", 745 + ] 746 + 747 + [[package]] 689 748 name = "icu_collections" 690 749 version = "1.5.0" 691 750 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 948 1007 version = "0.1.0" 949 1008 dependencies = [ 950 1009 "bcrypt", 1010 + "chrono", 951 1011 "cynthia_con", 952 1012 "dotenv", 953 1013 "fastbloom", ··· 1051 1111 checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1052 1112 1053 1113 [[package]] 1114 + name = "num-traits" 1115 + version = "0.2.19" 1116 + source = "registry+https://github.com/rust-lang/crates.io-index" 1117 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1118 + dependencies = [ 1119 + "autocfg", 1120 + ] 1121 + 1122 + [[package]] 1054 1123 name = "num_cpus" 1055 1124 version = "1.16.0" 1056 1125 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2300 2369 dependencies = [ 2301 2370 "cfg-if", 2302 2371 "once_cell", 2372 + "rustversion", 2303 2373 "wasm-bindgen-macro", 2304 2374 ] 2305 2375 ··· 2409 2479 checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" 2410 2480 dependencies = [ 2411 2481 "windows-targets 0.48.5", 2482 + ] 2483 + 2484 + [[package]] 2485 + name = "windows-core" 2486 + version = "0.61.2" 2487 + source = "registry+https://github.com/rust-lang/crates.io-index" 2488 + checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 2489 + dependencies = [ 2490 + "windows-implement", 2491 + "windows-interface", 2492 + "windows-link", 2493 + "windows-result", 2494 + "windows-strings", 2495 + ] 2496 + 2497 + [[package]] 2498 + name = "windows-implement" 2499 + version = "0.60.0" 2500 + source = "registry+https://github.com/rust-lang/crates.io-index" 2501 + checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 2502 + dependencies = [ 2503 + "proc-macro2", 2504 + "quote", 2505 + "syn", 2506 + ] 2507 + 2508 + [[package]] 2509 + name = "windows-interface" 2510 + version = "0.59.1" 2511 + source = "registry+https://github.com/rust-lang/crates.io-index" 2512 + checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 2513 + dependencies = [ 2514 + "proc-macro2", 2515 + "quote", 2516 + "syn", 2517 + ] 2518 + 2519 + [[package]] 2520 + name = "windows-link" 2521 + version = "0.1.3" 2522 + source = "registry+https://github.com/rust-lang/crates.io-index" 2523 + checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 2524 + 2525 + [[package]] 2526 + name = "windows-result" 2527 + version = "0.3.4" 2528 + source = "registry+https://github.com/rust-lang/crates.io-index" 2529 + checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 2530 + dependencies = [ 2531 + "windows-link", 2532 + ] 2533 + 2534 + [[package]] 2535 + name = "windows-strings" 2536 + version = "0.4.2" 2537 + source = "registry+https://github.com/rust-lang/crates.io-index" 2538 + checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 2539 + dependencies = [ 2540 + "windows-link", 2412 2541 ] 2413 2542 2414 2543 [[package]]
+1
server/Cargo.toml
··· 24 24 tabled = "0.18.0" 25 25 regex = "1.10.5" 26 26 fastbloom = "0.9.0" 27 + chrono = "0.4.41"
+42 -34
server/src/client_communication.rs
··· 1 1 use crate::user::User; 2 - use crate::{AppState, LuminaError, helpers}; 2 + use crate::{ 3 + AppState, LuminaError, error_elog, helpers, incoming_elog, info_elog, registration_error_elog, 4 + warn_elog, 5 + }; 3 6 use cynthia_con::{CynthiaColors, CynthiaStyles}; 4 7 extern crate rocket; 5 8 use rocket::State; ··· 8 11 #[get("/connection")] 9 12 pub(crate) fn wsconnection<'k>(ws: ws::WebSocket, state: &'k State<AppState>) -> ws::Channel<'k> { 10 13 use rocket::futures::{SinkExt, StreamExt}; 11 - let (info, warn, error, _success, _failure, _log, incoming, registrationerror) = 12 - helpers::message_prefixes(); 14 + 13 15 ws.channel(move |mut stream| { 14 16 Box::pin(async move { 15 17 let mut client_session_data: SessionData = SessionData { 16 18 client_type: None, 17 19 user: None, 18 20 }; 21 + let ev_log = { 22 + let appstate = state.0.clone(); 23 + let db = &appstate.1.lock().await; 24 + helpers::events::EventLogger::from_db(db).await 25 + }; 19 26 while let Some(message) = stream.next().await { 20 27 match message? { 21 28 ws::Message::Text(msg) => { ··· 38 45 let db = &appstate.1.lock().await; 39 46 match User::revive_session_from_token(token.clone(), db).await { 40 47 Ok(user) => { 41 - println!( 42 - "{incoming} Session revived for user: {}", 48 + incoming_elog!( 49 + ev_log 50 + , 51 + "Session revived for user: {}", 43 52 user.clone().username.color_bright_cyan() 44 53 ); 45 54 client_session_data.user = Some(user.clone()); ··· 55 64 LuminaError::Postgres(postgres_error) => { 56 65 // Check if it's a "no rows returned" type error 57 66 if postgres_error.to_string().contains("no rows") { 58 - println!("{info} Session revival failed: token not found or expired."); 67 + info_elog!( ev_log,"Session revival failed: token not found or expired."); 59 68 } else { 60 - println!("{info} Session revival failed: database error: {:?}", postgres_error); 69 + info_elog!(ev_log,"Session revival failed: database error: {:?}", postgres_error); 61 70 } 62 71 } 63 72 LuminaError::Sqlite(sqlite_error) => { 64 73 match sqlite_error { 65 74 r2d2_sqlite::rusqlite::Error::QueryReturnedNoRows => { 66 75 // No rows returned - session not found or expired 67 - println!("{info} Session revival failed: token not found or expired."); 76 + info_elog!(ev_log,"Session revival failed: token not found or expired."); 68 77 } 69 78 _ => { 70 - println!("{info} Session revival failed: database error: {:?}", sqlite_error); 79 + info_elog!(ev_log,"Session revival failed: database error: {:?}", sqlite_error); 71 80 } 72 81 } 73 82 } 74 83 _ => { 75 - println!("{info} Session revival failed: {:?}", e); 84 + info_elog!(ev_log,"Session revival failed: {:?}", e); 76 85 } 77 86 } 78 87 let _ = stream ··· 94 103 username, 95 104 password, 96 105 }) => { 97 - println!( 98 - "{incoming} Register request: {} {}", 106 + incoming_elog!( 107 + ev_log, 108 + "Register request: {} {}", 99 109 email.clone().color_orange(), 100 110 username.clone().color_bright_cyan() 101 111 ); ··· 107 117 match User::create_user(email.clone(), username.clone(), password, db).await 108 118 { 109 119 Ok(user) => { 110 - println!( 111 - "{info} User created: {}", 120 + info_elog!( 121 + ev_log, 122 + "User created: {}", 112 123 user.clone().username.color_bright_cyan() 113 124 ); 114 125 match User::create_session(user, db).await { 115 126 Ok((session_reference, user)) => { 116 127 client_session_data.user = 117 128 Some(user.clone()); 118 - println!( 119 - "{incoming} User {} authenticated.", 129 + incoming_elog!(ev_log,"User {} authenticated.", 120 130 user.clone().username.color_bright_cyan() 121 131 ); 122 132 let _ = stream ··· 131 141 Err(e) => { 132 142 match e { 133 143 LuminaError::Postgres(e) => 134 - println!("{error} While creating session token: {:?}", e), 144 + error_elog!(ev_log,"While creating session token: {:?}", e), 135 145 LuminaError::SqlitePool(e) => 136 - println!("{warn} There was an error creating session token: {:?}", e), 146 + warn_elog!(ev_log,"There was an error creating session token: {:?}", e), 137 147 LuminaError::Sqlite(e) => 138 - println!("{warn} There was an error creating session token: {:?}", e), 148 + warn_elog!(ev_log,"There was an error creating session token: {:?}", e), 139 149 _ => {} 140 150 } 141 151 // I would return a more specific error message ··· 155 165 Err(e) => { 156 166 match e { 157 167 LuminaError::RegisterUsernameInUse => { 158 - println!( 159 - "{registrationerror} User {} already exists", 168 + registration_error_elog!(ev_log, "User {} already exists", 160 169 username.clone().color_bright_cyan() 161 170 ); 162 171 } 163 172 LuminaError::RegisterEmailNotValid => { 164 - println!( 165 - "{registrationerror} Email {} is not valid", 173 + registration_error_elog!(ev_log, "Email {} is not valid", 166 174 email.clone().color_bright_cyan() 167 175 ); 168 176 } 169 177 LuminaError::RegisterUsernameInvalid(why) => { 170 - println!( 171 - "{registrationerror} Username '{}' is not valid: {}", 178 + registration_error_elog!(ev_log, "Username '{}' is not valid: {}", 172 179 username.clone().color_bright_cyan(), 173 180 why 174 181 ); 175 182 } 176 183 LuminaError::RegisterPasswordNotValid(why) => { 177 - println!( 178 - "{registrationerror} Password is not valid: {}", 184 + registration_error_elog!(ev_log, "Password is not valid: {}", 179 185 why 180 186 ); 181 187 } 182 188 e => { 183 - println!( 184 - "{registrationerror} Error creating user: {:?}", 189 + registration_error_elog!(ev_log, "Error creating user: {:?}", 185 190 e 186 191 ); 187 192 } ··· 250 255 let db = &appstate.1.lock().await; 251 256 let msgback = match User::authenticate(email_username.clone(), password, db).await { 252 257 Ok((session_reference, user)) => { 253 - println!("{incoming} User {} authenticated to session with id {}.\n{incoming} {}", user.username.clone().color_bright_cyan(), session_reference.session_id.to_string().color_pink(), format!("(User id: {})", user.id.to_string()).style_dim()); 258 + incoming_elog!(ev_log,"User {} authenticated to session with id {}.\n{}", user.username.clone().color_bright_cyan(), session_reference.session_id.to_string().color_pink(), format!("(User id: {})", user.id.to_string()).style_dim()); 254 259 client_session_data.user = Some(user.clone()); 255 260 Message::AuthSuccess {token: session_reference.token, username: user.username } 256 261 } ··· 258 263 Err(s) => { 259 264 match s { 260 265 LuminaError::AuthenticationWrongPassword => { 261 - println!("{registrationerror} User {} {} authenticated: Incorrect credentials", email_username.color_bright_cyan(), "not".color_red()); 266 + registration_error_elog!(ev_log,"User {} {} authenticated: Incorrect credentials", email_username.color_bright_cyan(), "not".color_red()); 262 267 } 263 268 LuminaError::AuthenticationUserNotFound => { 264 - println!("{registrationerror} User {} {} authenticated: User not found", email_username.color_bright_cyan(), "not".color_red()); 269 + registration_error_elog!(ev_log,"User {} {} authenticated: User not found", email_username.color_bright_cyan(), "not".color_red()); 265 270 } 266 271 _ => { 267 - println!("{registrationerror} User {} {} authenticated: {:?}", email_username.color_bright_cyan(), "not".color_red(), s); 272 + registration_error_elog!(ev_log,"User {} {} authenticated: {:?}", email_username.color_bright_cyan(), "not".color_red(), s); 268 273 } 269 274 } 270 275 Message::AuthFailure ··· 293 298 } 294 299 } 295 300 } 296 - Ok(Message::TimelineRequest { by_id }) => todo!("Fetching timeline content not yet implemented."), 301 + Ok(Message::TimelineRequest { by_id: _ }) => { 302 + error_elog!(ev_log,"Not yet implemented: Message::TimelineRequest") 303 + 304 + }, 297 305 // Responding variants are not supposed to ever arrive here. 298 306 Ok(Message::ClientInit { .. }) | 299 307 Ok(Message::Greeting { .. }) | Ok(Message::SerialisationError {..} )
+54 -20
server/src/database.rs
··· 1 1 use crate::errors::LuminaError::{self, ConfMissing}; 2 - use crate::helpers; 2 + use crate::helpers::events::EventLogger; 3 + use crate::{info_elog, warn_elog}; 3 4 use cynthia_con::{CynthiaColors, CynthiaStyles}; 4 5 use r2d2::Pool; 5 6 use r2d2_sqlite::SqliteConnectionManager; ··· 8 9 use tokio_postgres::{Client, Connection, Socket}; 9 10 10 11 pub(crate) async fn setup() -> Result<DbConn, LuminaError> { 11 - let (info, warn, _error, _success, _failure, _log, _incoming, _registrationerror) = 12 - helpers::message_prefixes(); 12 + let ev_log = EventLogger::new(&None).await; 13 13 match (std::env::var("LUMINA_DB_TYPE") 14 14 .map_err(|_| ConfMissing("LUMINA_DB_TYPE".to_string())) 15 15 .unwrap_or(String::from("sqlite"))) ··· 18 18 "sqlite" => { 19 19 let db_path = 20 20 std::env::var("LUMINA_SQLITE_FILE").unwrap_or("instance.sqlite".to_string()); 21 - println!( 22 - "{info} Using SQLite database at path: {}", 21 + info_elog!( 22 + ev_log, 23 + "Using SQLite database at path: {}", 23 24 db_path.clone().color_bright_cyan().style_bold() 24 25 ); 25 26 let manager = SqliteConnectionManager::file(db_path); ··· 40 41 user_id TEXT NOT NULL, 41 42 session_key TEXT NOT NULL, 42 43 created_at INT NOT NULL)", 44 + [], 45 + ); 46 + let _ = conn.execute( 47 + "CREATE TABLE IF NOT EXISTS logs (\ 48 + type TEXT NOT NULL, \ 49 + message TEXT NOT NULL, \ 50 + timestamp TEXT NOT NULL\ 51 + )", 43 52 [], 44 53 ); 45 54 let _ = conn.execute("PRAGMA journal_mode = WAL;", []); ··· 50 59 Ok(DbConn::SqliteConnectionPool(pool)) 51 60 } 52 61 "postgres" => { 53 - let pg_config = { 62 + let pg_config: tokio_postgres::Config = { 54 63 let mut uuu = ( 55 64 "unspecified database".to_string(), 56 65 "unspecified host".to_string(), ··· 65 74 .map_err(|_| ConfMissing("LUMINA_POSTGRES_DATABASE".to_string()))?; 66 75 uuu.0 = dbname.clone(); 67 76 pg_config.dbname(&dbname); 68 - let port = std::env::var("LUMINA_POSTGRES_PORT").unwrap_or_else(|_| { 69 - eprintln!("{warn} No Postgres database port provided under environment variable 'LUMINA_POSTGRES_PORT'. Using default value '5432'."); 70 - "5432".to_string() 71 - }); 77 + let port = match std::env::var("LUMINA_POSTGRES_PORT") { 78 + Err(..) => { 79 + warn_elog!( 80 + ev_log, 81 + "No Postgres database port provided under environment variable 'LUMINA_POSTGRES_PORT'. Using default value '5432'." 82 + ); 83 + "5432".to_string() 84 + } 85 + Ok(c) => c, 86 + }; 72 87 uuu.2 = port.clone(); 73 88 // Parse the port as u16, if it fails, return an error 74 89 pg_config.port(port.parse::<u16>().map_err(|_| { ··· 82 97 pg_config.host(&val); 83 98 } 84 99 Err(_) => { 85 - eprintln!( 86 - "{warn} No Postgres database host provided under environment variable 'LUMINA_POSTGRES_HOST'. Using default value 'localhost'." 100 + warn_elog!( 101 + ev_log, 102 + "No Postgres database host provided under environment variable 'LUMINA_POSTGRES_HOST'. Using default value 'localhost'." 87 103 ); 88 104 // Default to localhost if not set 89 105 uuu.1 = "localhost".to_string(); ··· 95 111 pg_config.password(&val); 96 112 } 97 113 Err(_) => { 98 - println!( 99 - "{warn} No Postgres database password provided under environment variable 'LUMINA_POSTGRES_PASSWORD'. Trying passwordless authentication." 114 + warn_elog!( 115 + ev_log, 116 + "No Postgres database password provided under environment variable 'LUMINA_POSTGRES_PASSWORD'. Trying passwordless authentication." 100 117 ); 101 118 } 102 119 }; 103 - println!( 104 - "{info} Using Postgres database at: {} on host: {} at port: {}", 120 + info_elog!( 121 + ev_log, 122 + "Using Postgres database at: {} on host: {} at port: {}", 105 123 uuu.0.color_bright_cyan().style_bold(), 106 124 uuu.1.color_bright_cyan().style_bold(), 107 125 uuu.2.color_bright_cyan().style_bold(), ··· 152 170 ) 153 171 .await 154 172 .map_err(LuminaError::Postgres)?; 173 + let _ = conn 174 + .0 175 + .execute( 176 + "CREATE TABLE IF NOT EXISTS logs (\ 177 + type VARCHAR NOT NULL, \ 178 + message TEXT NOT NULL, \ 179 + timestamp TIMESTAMP NOT NULL\ 180 + )", 181 + &[], 182 + ) 183 + .await 184 + .map_err(LuminaError::Postgres)?; 155 185 }; 156 - let _ = tokio::spawn(maintain(DbConn::PgsqlConnection(conn_two.0))); 157 - Ok(DbConn::PgsqlConnection(conn.0)) 186 + let _ = tokio::spawn(maintain(DbConn::PgsqlConnection( 187 + conn_two.0, 188 + pg_config.clone(), 189 + ))); 190 + Ok(DbConn::PgsqlConnection(conn.0, pg_config)) 158 191 } 159 192 160 193 c => { ··· 170 203 // This will be an enum containing either a pgsql connection or a sqlite connection 171 204 #[derive()] 172 205 pub enum DbConn { 173 - PgsqlConnection(postgres::Client), 206 + // The config is also shared, so that for example the logger can set up it's own connection, use this sparingly. 207 + PgsqlConnection(postgres::Client, tokio_postgres::Config), 174 208 SqliteConnectionPool(Pool<SqliteConnectionManager>), 175 209 } 176 210 177 211 // This function will be used to maintain the database, such as deleting old sessions 178 212 pub async fn maintain(db: DbConn) { 179 213 match db { 180 - DbConn::PgsqlConnection(client) => { 214 + DbConn::PgsqlConnection(client, _) => { 181 215 let mut interval = tokio::time::interval(std::time::Duration::from_secs(60)); 182 216 loop { 183 217 interval.tick().await;
+271
server/src/helpers/events.rs
··· 1 + use crate::database::DbConn; 2 + use crate::helpers::message_prefixes; 3 + use crate::{LuminaError, database}; 4 + use chrono::Utc; 5 + 6 + /// Levels of logging supported by the Logger. 7 + #[derive(Debug)] 8 + pub enum EventType { 9 + Info, 10 + Warn, 11 + Error, 12 + Success, 13 + Failure, 14 + Log, 15 + Incoming, 16 + RegistrationError, 17 + SoftError, 18 + } 19 + 20 + /// A reusable logger that logs messages to stdout with colored prefixes 21 + /// and, when available, also logs entries into the database. 22 + /// 23 + /// The database log entry is simple, with the log type, the message, and a timestamp. 24 + pub enum EventLogger { 25 + /// Variant created when logger has a database, and the database nor environment have any settings blocking database logging. 26 + WithDatabase { db: DbConn }, 27 + /// Only log to stdout 28 + OnlyStdout, 29 + } 30 + 31 + impl EventLogger { 32 + /// Creates a new logger instance. 33 + /// The `db` parameter can be `None` if the database isn't connected. 34 + pub async fn new(db: &Option<DbConn>) -> Self { 35 + // For quick implementation we'll just check if not none and that's all. 36 + match db { 37 + Some(d) => Self::from_db(d).await, 38 + None => Self::OnlyStdout, 39 + } 40 + } 41 + 42 + pub async fn from_db(db: &DbConn) -> Self { 43 + match db { 44 + DbConn::PgsqlConnection(_, pg_config) => { 45 + match pg_config 46 + .connect(tokio_postgres::tls::NoTls) 47 + .await 48 + .map_err(LuminaError::Postgres) 49 + { 50 + Ok((client, _)) => { 51 + let new_dbconn = 52 + database::DbConn::PgsqlConnection(client, pg_config.clone()); 53 + Self::WithDatabase { db: new_dbconn } 54 + } 55 + 56 + Err(error) => { 57 + let n = Self::OnlyStdout; 58 + n.error( 59 + format!("Could not connect the logger to the database! {:?}", error) 60 + .as_str(), 61 + ) 62 + .await; 63 + n 64 + } 65 + } 66 + } 67 + DbConn::SqliteConnectionPool(pool) => { 68 + let new_dbconn = database::DbConn::SqliteConnectionPool(pool.clone()); 69 + Self::WithDatabase { db: new_dbconn } 70 + } 71 + } 72 + } 73 + /// Logs a message with the specified log level. 74 + /// This method prints to stdout with a colored prefix and, if a database connection is available, 75 + /// asynchronously inserts a log entry in the logs table. 76 + pub async fn log(&self, level: EventType, message: &str) { 77 + // Get the current timestamp. 78 + let now = Utc::now(); 79 + 80 + // Determine the appropriate prefix for stdout. 81 + let (info, warn, error, success, failure, log, incoming, registrationerror) = 82 + message_prefixes(); 83 + let (prefix, use_eprintln) = match level { 84 + EventType::Info => (info, false), 85 + EventType::Warn => (warn, false), 86 + EventType::Error => (error, true), 87 + EventType::SoftError => (error, false), 88 + EventType::Success => (success, false), 89 + EventType::Failure => (failure, false), 90 + EventType::Log => (log, false), 91 + EventType::Incoming => (incoming, false), 92 + EventType::RegistrationError => (registrationerror, true), 93 + }; 94 + 95 + let stdoutmsg = 96 + format!("{prefix} {message}").replace("\n", format!("\n{prefix} ").as_str()); 97 + 98 + // Log to the database if a connection is available. 99 + match self { 100 + EventLogger::WithDatabase { db: db_conn } => { 101 + // Log to stdout with the prefix. 102 + if use_eprintln { 103 + eprintln!("{stdoutmsg}"); 104 + } else { 105 + println!("{stdoutmsg}"); 106 + } 107 + // Prepare the basic values for the log entry. 108 + let level_str = match level { 109 + EventType::Info => "INFO", 110 + EventType::Warn => "WARN", 111 + EventType::SoftError | EventType::Error => "ERROR", 112 + EventType::Success => "SUCCESS", 113 + EventType::Failure => "FAILURE", 114 + EventType::Log => "LOG", 115 + EventType::Incoming => "INCOMING", 116 + EventType::RegistrationError => "REGISTRATION_ERROR", 117 + } 118 + .to_string(); 119 + let ansi_regex = regex::Regex::new(r"\x1B\[[0-?]*[ -/]*[@-~]").unwrap(); 120 + 121 + let message_db: String = ansi_regex 122 + .replace_all(message, "") 123 + .to_string() 124 + .chars() 125 + .filter(|c| c.is_alphanumeric() || c.is_whitespace()) 126 + .collect(); 127 + let ts = now.to_rfc3339(); 128 + 129 + match db_conn { 130 + crate::database::DbConn::PgsqlConnection(client, _) => { 131 + let _ = client 132 + .execute( 133 + "INSERT INTO logs (type, message, timestamp) VALUES ($1, $2, $3)", 134 + &[&level_str, &message_db, &ts], 135 + ) 136 + .await; 137 + } 138 + crate::database::DbConn::SqliteConnectionPool(pool) => { 139 + if let Ok(conn) = pool.get() { 140 + let _ = conn.execute( 141 + "INSERT INTO logs (type, message, timestamp) VALUES (?1, ?2, ?3)", 142 + r2d2_sqlite::rusqlite::params![level_str, message_db, ts], 143 + ); 144 + } 145 + } 146 + } 147 + } 148 + EventLogger::OnlyStdout { .. } => { 149 + // Log to stdout with the prefix. 150 + if use_eprintln { 151 + eprintln!("{stdoutmsg}"); 152 + } else { 153 + println!("{stdoutmsg}"); 154 + } 155 + } 156 + } 157 + } 158 + 159 + /// Convenience method to log an informational message. 160 + pub async fn info(&self, message: &str) { 161 + self.log(EventType::Info, message).await 162 + } 163 + 164 + /// Convenience method to log a warning message. 165 + pub async fn warn(&self, message: &str) { 166 + self.log(EventType::Warn, message).await 167 + } 168 + 169 + /// Convenience method to log an error message. 170 + pub async fn error(&self, message: &str) { 171 + self.log(EventType::Error, message).await 172 + } 173 + /// Convenience method to log a soft error message. 174 + pub async fn s_error(&self, message: &str) { 175 + self.log(EventType::Error, message).await 176 + } 177 + /// Convenience method to log a success message. 178 + pub async fn success(&self, message: &str) { 179 + self.log(EventType::Success, message).await 180 + } 181 + 182 + /// Convenience method to log a failure message. 183 + pub async fn failure(&self, message: &str) { 184 + self.log(EventType::Failure, message).await 185 + } 186 + 187 + /// Convenience method to log a plain message without a specific log level. 188 + pub async fn log_plain(&self, message: &str) { 189 + self.log(EventType::Log, message).await 190 + } 191 + 192 + /// Convenience method to log an incoming message. 193 + pub async fn incoming(&self, message: &str) { 194 + self.log(EventType::Incoming, message).await 195 + } 196 + 197 + /// Convenience method to log a registration error message. 198 + pub async fn registration_error(&self, message: &str) { 199 + self.log(EventType::RegistrationError, message).await 200 + } 201 + } 202 + #[macro_export] 203 + macro_rules! info_elog { 204 + ($logger:expr, $($arg:tt)*) => { 205 + $logger.info(&format!($($arg)*)).await 206 + }; 207 + } 208 + 209 + #[macro_export] 210 + /// Takes an event log object and then runs .warn on it, formatting using the other arguments. 211 + macro_rules! warn_elog { 212 + ($logger:expr, $($arg:tt)*) => { 213 + $logger.warn(&format!($($arg)*)).await 214 + }; 215 + } 216 + 217 + #[macro_export] 218 + /// Takes an event log object and then runs .error on it, formatting using the other arguments. 219 + macro_rules! error_elog { 220 + ($logger:expr, $($arg:tt)*) => { 221 + $logger.error(&format!($($arg)*)).await 222 + }; 223 + } 224 + 225 + #[macro_export] 226 + /// Takes an event log object and then runs .s_error on it, formatting using the other arguments. 227 + macro_rules! soft_error_elog { 228 + ($logger:expr, $($arg:tt)*) => { 229 + $logger.s_error(&format!($($arg)*)).await 230 + }; 231 + } 232 + 233 + #[macro_export] 234 + /// Takes an event log object and then runs .success on it, formatting using the other arguments. 235 + macro_rules! success_elog { 236 + ($logger:expr, $($arg:tt)*) => { 237 + $logger.success(&format!($($arg)*)).await 238 + }; 239 + } 240 + 241 + /// Takes an event log object and then runs .faillure on it, formatting using the other arguments. 242 + #[macro_export] 243 + macro_rules! fail_elog { 244 + ($logger:expr, $($arg:tt)*) => { 245 + $logger.failure(&format!($($arg)*)).await 246 + }; 247 + } 248 + 249 + /// Takes an event log object and then runs .log_plain on it, formatting using the other arguments. 250 + #[macro_export] 251 + macro_rules! elog { 252 + ($logger:expr, $($arg:tt)*) => { 253 + $logger.log_plain(&format!($($arg)*)).await 254 + }; 255 + } 256 + 257 + #[macro_export] 258 + /// Takes an event log object and then runs .incoming on it, formatting using the other arguments. 259 + macro_rules! incoming_elog { 260 + ($logger:expr, $($arg:tt)*) => { 261 + $logger.incoming(&format!($($arg)*)).await 262 + }; 263 + } 264 + 265 + #[macro_export] 266 + /// Takes an event log object and then runs .registration_error on it, formatting using the other arguments. 267 + macro_rules! registration_error_elog { 268 + ($logger:expr, $($arg:tt)*) => { 269 + $logger.registration_error(&format!($($arg)*)).await 270 + }; 271 + }
+2
server/src/helpers/mod.rs
··· 1 + pub mod events; 2 + 1 3 use cynthia_con::{CynthiaColors, CynthiaStyles}; 2 4 3 5 /// Message prefixes for different types of messages.
+62 -39
server/src/main.rs
··· 9 9 mod database; 10 10 mod tests; 11 11 12 + use helpers::events::EventLogger; 13 + use helpers::message_prefixes; 14 + use rocket::config::LogLevel; 12 15 use std::io::ErrorKind; 13 16 use std::{net::IpAddr, process, sync::Arc}; 14 17 use tokio::sync::Mutex; ··· 44 47 45 48 #[rocket::main] 46 49 async fn main() { 47 - let (info, warn, error, success, _failure, _log, _incoming, _registrationerror) = 48 - helpers::message_prefixes(); 49 50 let me = format!("Lumina Server, version {}", env!("CARGO_PKG_VERSION")); 51 + let ev_log: EventLogger = EventLogger::new(&None).await; 50 52 let args: Vec<String> = std::env::args().skip(1).collect(); 51 53 match ( 52 54 args.is_empty(), ··· 54 56 ) { 55 57 (true, _) | (false, "start") | (false, "") => { 56 58 dotenv().ok(); 57 - println!("{info} Starting {}.", me.clone().color_lightblue()); 59 + info_elog!(ev_log, "Starting {}.", me.clone().color_lightblue()); 58 60 println!( 59 - "{info} {} and contributors, licenced under {}.", 61 + "{} {} and contributors, licenced under {}.", 62 + message_prefixes().0, 60 63 "MLC Bloeiman".color_pink(), 61 64 "BSD-3".color_blue() 62 65 ); 63 66 println!("{}", cynthia_con::horizline()); 64 - println!( 65 - "{warn} Lumina is still in early development, and should not be used in production in any way. Please use at your own risk." 67 + warn_elog!( 68 + ev_log, 69 + "Lumina is still in early development, and should not be used in production in any way. Please use at your own risk." 66 70 ); 67 71 match config_get() { 68 72 Ok(config) => { 69 73 let mut interval = 70 74 tokio::time::interval(std::time::Duration::from_millis(3000)); 71 75 let mut db_mut: Option<DbConn> = None; 76 + let mut ev_log: EventLogger = EventLogger::new(&db_mut).await; 77 + 72 78 let mut db_tries: usize = 0; 73 79 while db_mut.is_none() { 74 80 interval.tick().await; 75 81 db_mut = match database::setup().await { 76 82 Ok(db) => Some(db), 77 83 Err(LuminaError::ConfMissing(a)) => { 78 - eprintln!( 79 - "{error} Missing environment variable {}, which is required to continue. Please make sure it is set, or change other variables to make it redundant, if possible.", 84 + error_elog!( 85 + ev_log, 86 + "Missing environment variable {}, which is required to continue. Please make sure it is set, or change other variables to make it redundant, if possible.", 80 87 a.color_bright_orange() 81 88 ); 82 89 None 83 90 } 84 91 Err(LuminaError::ConfInvalid(a)) => { 85 - eprintln!( 86 - "{error} Invalid environment variable: {}", 92 + error_elog!( 93 + ev_log, 94 + "Invalid environment variable: {}", 87 95 a.color_bright_orange() 88 96 ); 89 97 None 90 98 } 91 99 Err(LuminaError::Sqlite(a)) => { 92 - eprintln!("{error} While opening sqlite database: {}", a); 100 + error_elog!(ev_log, "While opening sqlite database: {}", a); 93 101 None 94 102 } 95 103 Err(LuminaError::Postgres(a)) => { 96 - eprintln!("{error} While connecting to postgres database: {}", a); 104 + error_elog!(ev_log, "While connecting to postgres database: {}", a); 97 105 None 98 106 } 99 107 Err(_) => { 100 - eprintln!( 101 - "{error} Unknown error: could not setup database connection.", 108 + error_elog!( 109 + ev_log, 110 + "Unknown error: could not setup database connection.", 102 111 ); 103 112 None 104 113 } ··· 106 115 if db_mut.is_none() { 107 116 if db_tries < 4 { 108 117 db_tries += 1; 109 - println!( 110 - "{warn} Retrying database connection in 3 seconds. (try {})", 118 + warn_elog!( 119 + ev_log, 120 + "Retrying database connection in 3 seconds. (try {})", 111 121 db_tries 112 122 ) 113 123 } else { 114 - println!("{error} Failed to connect to database, not retrying."); 124 + error_elog!(ev_log, "Failed to connect to database, not retrying."); 115 125 process::exit(1); 116 126 } 117 127 } else { 118 - println!("{success} Database connected.") 128 + // update ev_log, since clearly, it's no longer a question 129 + ev_log = EventLogger::new(&db_mut).await; 130 + 131 + success_elog!(ev_log, "Database connected.") 119 132 } 120 133 } 134 + let ev_log = EventLogger::new(&db_mut).await; 121 135 let db = db_mut.unwrap(); 136 + 122 137 let appstate = AppState(Arc::from((config.clone(), Mutex::from(db)))); 138 + 123 139 let def = rocket::Config { 124 140 port: config.port, 125 141 address: config.host, 126 - ident: rocket::config::Ident::try_new(me.clone()).unwrap_or_default(), 142 + // TODO: Use Lumina's logging instead, no logging is bad practise. 143 + log_level: LogLevel::Critical, 127 144 ..rocket::Config::default() 128 145 }; 129 146 ··· 149 166 Ok(_) => {} 150 167 Err(LuminaError::RocketFaillure(e)) => { 151 168 // This handling should slowly expand as I run into newer ones, the 'defh' (default handling) is good enough, but for the most-bumped into errors, I'd like to give more human responses. 152 - let defh = || eprintln!("{error} Error starting server: {:?}", e); 169 + let defh = 170 + async || error_elog!(ev_log, "Error starting server: {:?}", e); 153 171 match e.kind() { 154 172 rocket::error::ErrorKind::Bind(e) => match e.kind() { 155 173 ErrorKind::AddrInUse => { 156 - println!( 157 - "{error} Another program or instance is running on this port or adress." 174 + error_elog!( 175 + ev_log, 176 + "Another program or instance is running on this port or adress." 158 177 ); 159 - println!( 160 - "{error} Make sure you have not double-started Lumina, or have a different program serving on this port!" 178 + soft_error_elog!( 179 + ev_log, 180 + "Make sure you have not double-started Lumina, or have a different program serving on this port!" 161 181 ); 162 - println!( 163 - "{error} {}", 182 + soft_error_elog!( 183 + ev_log, 184 + "{}", 164 185 format!("Technical explanation: {}", e).style_dim() 165 186 ); 166 187 } 167 - _ => defh(), 188 + _ => defh().await, 168 189 }, 169 - _ => defh(), 190 + _ => defh().await, 170 191 } 171 192 process::exit(1); 172 193 } 173 194 Err(_) => { 174 - eprintln!("{error} Unknown error starting server.",); 195 + error_elog!(ev_log, "Unknown error starting server.",); 175 196 } 176 197 } 177 198 } 178 199 Err(LuminaError::ConfMissing(a)) => { 179 - eprintln!( 180 - "{error} Missing environment variable {}, which is required to continue. Please make sure it is set, or change other variables to make it redundant, if possible.", 200 + error_elog!( 201 + ev_log, 202 + "Missing environment variable {}, which is required to continue. Please make sure it is set, or change other variables to make it redundant, if possible.", 181 203 a.color_bright_orange() 182 204 ); 183 205 process::exit(1); 184 206 } 185 207 Err(LuminaError::ConfInvalid(a)) => { 186 - eprintln!( 187 - "{} Invalid environment variable: {}", 188 - "[ERROR]".color_error_red().style_bold(), 208 + error_elog!( 209 + ev_log, 210 + "Invalid environment variable: {}", 189 211 a.color_bright_orange() 190 212 ); 191 213 process::exit(1); 192 214 } 193 215 Err(_) => { 194 - eprintln!( 195 - "{} Unknown error: could not setup server configuration.", 196 - "[ERROR]".color_error_red().style_bold() 216 + error_elog!( 217 + ev_log, 218 + "Unknown error: could not setup server configuration.", 197 219 ); 198 220 process::exit(1); 199 221 } ··· 348 370 } 349 371 } 350 372 (false, unknown) => { 351 - println!( 352 - "{error} Unknown subcommand, '{}', use '{}' for available commands.'", 373 + soft_error_elog!( 374 + ev_log, 375 + "Unknown subcommand, '{}', use '{}' for available commands.'", 353 376 unknown.color_blue().style_italic(), 354 377 "help".color_lightblue().style_italic() 355 378 )
+6 -6
server/src/user.rs
··· 54 54 ) 55 55 .map_err(LuminaError::Sqlite) 56 56 } 57 - DbConn::PgsqlConnection(client) => { 57 + DbConn::PgsqlConnection(client, _) => { 58 58 let row = client 59 59 .query_one("SELECT password FROM users WHERE id = $1", &[&self.id]) 60 60 .await ··· 76 76 let password = 77 77 bcrypt::hash(password, bcrypt::DEFAULT_COST).map_err(LuminaError::BcryptError)?; 78 78 match db { 79 - DbConn::PgsqlConnection(client) => { 79 + DbConn::PgsqlConnection(client, _) => { 80 80 // Some username and email validation should be done here 81 81 // Check if the email is already in use 82 82 let email_exists = client ··· 151 151 "username" 152 152 }; 153 153 match db { 154 - DbConn::PgsqlConnection(client) => { 154 + DbConn::PgsqlConnection(client, _) => { 155 155 let user = client 156 156 .query_one( 157 157 &format!("SELECT * FROM users WHERE {} = $1", identifyer_type), ··· 193 193 let user = self; 194 194 let user_id = user.id; 195 195 match db { 196 - DbConn::PgsqlConnection(client) => { 196 + DbConn::PgsqlConnection(client, _) => { 197 197 let session_key = Uuid::new_v4().to_string(); 198 198 let id = client 199 199 .query_one( ··· 245 245 db: &DbConn, 246 246 ) -> Result<User, LuminaError> { 247 247 match db { 248 - DbConn::PgsqlConnection(client) => { 248 + DbConn::PgsqlConnection(client, _) => { 249 249 let user = client 250 250 .query_one("SELECT users.id, users.email, users.username FROM users JOIN sessions ON users.id = sessions.user_id WHERE sessions.session_key = $1", &[&token]) 251 251 .await ··· 283 283 // Check if the email or username is already in use 284 284 match db { 285 285 // TODO: Bloom filter for username and email checks using fast-bloom 286 - DbConn::PgsqlConnection(client) => { 286 + DbConn::PgsqlConnection(client, _) => { 287 287 // Some username and email validation should be done here 288 288 // Check if the email is already in use 289 289 let email_exists = client