From aefeb12c0c00640e0553e28ae6ec1aad3907aef5 Mon Sep 17 00:00:00 2001 From: AI Agent Date: Thu, 19 Mar 2026 18:04:32 -0600 Subject: [PATCH] Implement robust logging with flexi_logger and update CI to verify logs --- .gitea/workflows/smoke-tests.yml | 20 ++++ Cargo.lock | 174 ++++++++--------------------- Cargo.toml | 2 +- logs/mudserver_r00000.log | 11 ++ logs/mudserver_r00001.log | 11 ++ logs/mudserver_r00002.log | 11 ++ logs/mudserver_r00003.log | 33 ++++++ logs/mudserver_rCURRENT.log | 33 ++++++ manual_logs/mudserver_rCURRENT.log | 11 ++ mudserver.db.test | Bin 0 -> 4096 bytes mudserver.db.test-shm | Bin 0 -> 32768 bytes mudserver.db.test-wal | Bin 0 -> 547992 bytes src/admin.rs | 8 ++ src/combat.rs | 6 +- src/main.rs | 32 +++++- src/ssh.rs | 9 ++ 16 files changed, 232 insertions(+), 129 deletions(-) create mode 100644 logs/mudserver_r00000.log create mode 100644 logs/mudserver_r00001.log create mode 100644 logs/mudserver_r00002.log create mode 100644 logs/mudserver_r00003.log create mode 100644 logs/mudserver_rCURRENT.log create mode 100644 manual_logs/mudserver_rCURRENT.log create mode 100644 mudserver.db.test create mode 100644 mudserver.db.test-shm create mode 100644 mudserver.db.test-wal diff --git a/.gitea/workflows/smoke-tests.yml b/.gitea/workflows/smoke-tests.yml index e048653..3651e5f 100644 --- a/.gitea/workflows/smoke-tests.yml +++ b/.gitea/workflows/smoke-tests.yml @@ -170,3 +170,23 @@ jobs: grep -q '"shop"' rpc_resp.json rm rpc_resp.json ./target/debug/mudtool -d "$TEST_DB" players delete rpctest + + - name: Verify logging + run: | + if [ ! -d "logs" ]; then + echo "Error: logs directory not found" + exit 1 + fi + if ! ls logs/mudserver_*.log >/dev/null 2>&1; then + echo "Error: no log files found" + exit 1 + fi + LOG_FILE=$(ls -t logs/mudserver_*.log | head -n 1) + echo "Checking log file: $LOG_FILE" + grep -q "World '.*': .* rooms" "$LOG_FILE" + grep -q "MUD server listening on" "$LOG_FILE" + grep -q "New character created: smoketest" "$LOG_FILE" + grep -q "Admin action: registration setting updated: '.*'" "$LOG_FILE" + grep -q "Combat: Player 'smoketest' (ID .*) killed NPC 'Shadowy Thief'" "$LOG_FILE" + grep -q "New character created: rpctest" "$LOG_FILE" + grep -q "New JSON-RPC connection from" "$LOG_FILE" diff --git a/Cargo.lock b/Cargo.lock index d7e0910..dd9c3a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,56 +61,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - [[package]] name = "anyhow" version = "1.0.102" @@ -335,12 +285,6 @@ dependencies = [ "inout", ] -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - [[package]] name = "compact_str" version = "0.9.0" @@ -385,6 +329,30 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crossterm" version = "0.28.1" @@ -694,29 +662,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "env_filter" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -809,6 +754,21 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flexi_logger" +version = "0.29.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a5a6882b2e137c4f2664562995865084eb5a00611fba30c582ef10354c4ad8" +dependencies = [ + "chrono", + "crossbeam-channel", + "crossbeam-queue", + "log", + "nu-ansi-term", + "regex", + "thiserror 2.0.18", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1166,12 +1126,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itertools" version = "0.14.0" @@ -1187,30 +1141,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" -[[package]] -name = "jiff" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-static" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "js-sys" version = "0.3.91" @@ -1387,7 +1317,7 @@ name = "mudserver" version = "0.2.0" dependencies = [ "crossterm 0.28.1", - "env_logger", + "flexi_logger", "log", "rand", "ratatui", @@ -1423,6 +1353,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1512,12 +1451,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "opaque-debug" version = "0.3.1" @@ -1815,15 +1748,6 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" -[[package]] -name = "portable-atomic-util" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" -dependencies = [ - "portable-atomic", -] - [[package]] name = "powerfmt" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 457f726..5b45c07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,6 @@ rusqlite = { version = "0.35", features = ["bundled"] } ratatui = "0.30" crossterm = "0.28" log = "0.4" -env_logger = "0.11" +flexi_logger = { version = "0.29", features = ["async"] } regex = "1" rand = "0.8" diff --git a/logs/mudserver_r00000.log b/logs/mudserver_r00000.log new file mode 100644 index 0000000..bd71b6e --- /dev/null +++ b/logs/mudserver_r00000.log @@ -0,0 +1,11 @@ +INFO [mudserver] Loading world from: ./world +INFO [mudserver::world] Loading region: lawold +INFO [mudserver::world] Loading region: town +INFO [mudserver::world] Loading region: whispering_woods +INFO [mudserver::world] World 'The Shattered Realm': 74 rooms, 71 npcs, 14 objects, 8 races, 7 classes, 5 guilds, 19 spells +INFO [mudserver] Opening database: ./mudserver.db.test +INFO [mudserver::db] Database opened: ./mudserver.db.test +INFO [mudserver::tick] Tick engine started (interval=3000ms, regen every 5 ticks) +INFO [mudserver::jsonrpc] JSON-RPC server listening on 0.0.0.0:2223 +INFO [mudserver] MUD server listening on 0.0.0.0:2222 +INFO [mudserver] Connect with: ssh @localhost -p 2222 diff --git a/logs/mudserver_r00001.log b/logs/mudserver_r00001.log new file mode 100644 index 0000000..3ea42c8 --- /dev/null +++ b/logs/mudserver_r00001.log @@ -0,0 +1,11 @@ +INFO [mudserver] Loading world from: ./world +INFO [mudserver::world] Loading region: lawold +INFO [mudserver::world] Loading region: town +INFO [mudserver::world] Loading region: whispering_woods +INFO [mudserver::world] World 'The Shattered Realm': 74 rooms, 71 npcs, 14 objects, 8 races, 7 classes, 5 guilds, 19 spells +INFO [mudserver] Opening database: ./mudserver.db.test +INFO [mudserver::db] Database opened: ./mudserver.db.test +INFO [mudserver] MUD server listening on 0.0.0.0:2222 +INFO [mudserver] Connect with: ssh @localhost -p 2222 +INFO [mudserver::jsonrpc] JSON-RPC server listening on 0.0.0.0:2223 +INFO [mudserver::tick] Tick engine started (interval=3000ms, regen every 5 ticks) diff --git a/logs/mudserver_r00002.log b/logs/mudserver_r00002.log new file mode 100644 index 0000000..6bcec94 --- /dev/null +++ b/logs/mudserver_r00002.log @@ -0,0 +1,11 @@ +INFO [mudserver] Loading world from: ./world +INFO [mudserver::world] Loading region: lawold +INFO [mudserver::world] Loading region: town +INFO [mudserver::world] Loading region: whispering_woods +INFO [mudserver::world] World 'The Shattered Realm': 74 rooms, 71 npcs, 14 objects, 8 races, 7 classes, 5 guilds, 19 spells +INFO [mudserver] Opening database: ./mudserver.db.test +INFO [mudserver::db] Database opened: ./mudserver.db.test +INFO [mudserver] MUD server listening on 0.0.0.0:2222 +INFO [mudserver] Connect with: ssh @localhost -p 2222 +INFO [mudserver::tick] Tick engine started (interval=3000ms, regen every 5 ticks) +INFO [mudserver::jsonrpc] JSON-RPC server listening on 0.0.0.0:2223 diff --git a/logs/mudserver_r00003.log b/logs/mudserver_r00003.log new file mode 100644 index 0000000..28802ab --- /dev/null +++ b/logs/mudserver_r00003.log @@ -0,0 +1,33 @@ +INFO [mudserver] Loading world from: ./world +INFO [mudserver::world] Loading region: lawold +INFO [mudserver::world] Loading region: town +INFO [mudserver::world] Loading region: whispering_woods +INFO [mudserver::world] World 'The Shattered Realm': 74 rooms, 71 npcs, 14 objects, 8 races, 7 classes, 5 guilds, 19 spells +INFO [mudserver] Opening database: ./mudserver.db.test +INFO [mudserver::db] Database opened: ./mudserver.db.test +INFO [mudserver::tick] Tick engine started (interval=3000ms, regen every 5 ticks) +INFO [mudserver] MUD server listening on 0.0.0.0:2222 +INFO [mudserver::jsonrpc] JSON-RPC server listening on 0.0.0.0:2223 +INFO [mudserver] Connect with: ssh @localhost -p 2222 +INFO [mudserver::ssh] New connection (id=1) from Some(127.0.0.1:40662) +ERROR [mudserver::ssh] Session error: Disconnect +INFO [mudserver::ssh] New connection (id=2) from Some(127.0.0.1:40676) +INFO [mudserver::ssh] New character created: smoketest (Race: Dragon, Class: Cleric) +INFO [mudserver::ssh] smoketest disconnected (id=2) +INFO [mudserver::ssh] New connection (id=3) from Some(127.0.0.1:40688) +INFO [mudserver::ssh] Player 'smoketest' (id=3) logged in +INFO [mudserver::ssh] smoketest disconnected (id=3) +INFO [mudserver::ssh] New connection (id=4) from Some(127.0.0.1:40704) +INFO [mudserver::ssh] Player 'smoketest' (id=4) logged in +INFO [mudserver::admin] Admin action: registration setting updated: 'on' +INFO [mudserver::ssh] smoketest disconnected (id=4) +INFO [mudserver::ssh] New connection (id=5) from Some(127.0.0.1:40706) +INFO [mudserver::ssh] New connection (id=6) from Some(127.0.0.1:40716) +INFO [mudserver::ssh] New character created: smoketest (Race: Dragon, Class: Cleric) +INFO [mudserver::combat] Combat: Player 'smoketest' (ID 6) killed NPC 'Shadowy Thief' (town:thief) +INFO [mudserver::ssh] smoketest disconnected (id=6) +INFO [mudserver::ssh] New connection (id=7) from Some(127.0.0.1:40724) +INFO [mudserver::ssh] New character created: rpctest (Race: Dragon, Class: Cleric) +INFO [mudserver::ssh] rpctest disconnected (id=7) +INFO [mudserver::jsonrpc] New JSON-RPC connection from 127.0.0.1:53436 +INFO [mudserver::jsonrpc] New JSON-RPC connection from 127.0.0.1:53450 diff --git a/logs/mudserver_rCURRENT.log b/logs/mudserver_rCURRENT.log new file mode 100644 index 0000000..29db94c --- /dev/null +++ b/logs/mudserver_rCURRENT.log @@ -0,0 +1,33 @@ +INFO [mudserver] Loading world from: ./world +INFO [mudserver::world] Loading region: lawold +INFO [mudserver::world] Loading region: town +INFO [mudserver::world] Loading region: whispering_woods +INFO [mudserver::world] World 'The Shattered Realm': 74 rooms, 71 npcs, 14 objects, 8 races, 7 classes, 5 guilds, 19 spells +INFO [mudserver] Opening database: ./mudserver.db.test +INFO [mudserver::db] Database opened: ./mudserver.db.test +INFO [mudserver] MUD server listening on 0.0.0.0:2222 +INFO [mudserver] Connect with: ssh @localhost -p 2222 +INFO [mudserver::tick] Tick engine started (interval=3000ms, regen every 5 ticks) +INFO [mudserver::jsonrpc] JSON-RPC server listening on 0.0.0.0:2223 +INFO [mudserver::ssh] New connection (id=1) from Some(127.0.0.1:56156) +ERROR [mudserver::ssh] Session error: IO( + Os { + code: 104, + kind: ConnectionReset, + message: "Connection reset by peer", + }, +) +INFO [mudserver::ssh] New connection (id=2) from Some(127.0.0.1:56158) +INFO [mudserver::ssh] New character created: smoketest (Race: Dragon, Class: Cleric) +INFO [mudserver::ssh] smoketest disconnected (id=2) +INFO [mudserver::ssh] New connection (id=3) from Some(127.0.0.1:56174) +INFO [mudserver::ssh] Player 'smoketest' (id=3) logged in +INFO [mudserver::ssh] smoketest disconnected (id=3) +INFO [mudserver::ssh] New connection (id=4) from Some(127.0.0.1:56186) +INFO [mudserver::ssh] Player 'smoketest' (id=4) logged in +INFO [mudserver::admin] Admin action: registration setting updated: 'on' +INFO [mudserver::ssh] smoketest disconnected (id=4) +INFO [mudserver::ssh] New connection (id=5) from Some(127.0.0.1:56192) +INFO [mudserver::ssh] New connection (id=6) from Some(127.0.0.1:56208) +INFO [mudserver::ssh] New character created: smoketest (Race: Dragon, Class: Cleric) +INFO [mudserver::combat] Combat: Player 'smoketest' (ID 6) killed NPC 'Shadowy Thief' (town:thief) diff --git a/manual_logs/mudserver_rCURRENT.log b/manual_logs/mudserver_rCURRENT.log new file mode 100644 index 0000000..9958ba2 --- /dev/null +++ b/manual_logs/mudserver_rCURRENT.log @@ -0,0 +1,11 @@ +INFO [mudserver] Loading world from: ./world +INFO [mudserver::world] Loading region: lawold +INFO [mudserver::world] Loading region: town +INFO [mudserver::world] Loading region: whispering_woods +INFO [mudserver::world] World 'The Shattered Realm': 74 rooms, 71 npcs, 14 objects, 8 races, 7 classes, 5 guilds, 19 spells +INFO [mudserver] Opening database: ./manual.db +INFO [mudserver::db] Database opened: ./manual.db +INFO [mudserver] MUD server listening on 0.0.0.0:2222 +INFO [mudserver] Connect with: ssh @localhost -p 2222 +INFO [mudserver::jsonrpc] JSON-RPC server listening on 0.0.0.0:2223 +INFO [mudserver::tick] Tick engine started (interval=3000ms, regen every 5 ticks) diff --git a/mudserver.db.test b/mudserver.db.test new file mode 100644 index 0000000000000000000000000000000000000000..4e86411b5803e34b1e4767ce981907694f15c1ed GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYBBVY$KKou7ZUfBn(c^S$NEhlY2J z-S0p3)OUUCDE#C6uvBln4!^(ti0n2evq5+_l_9G_#&N%p{j>3aP^+_XO{lfmcwne; zuc@ZzV=a|womf+*XVMy}ru(JVDK#hVyZC;(PW5kV#{bfuQcd&I^Qor$u@+~kIem3+ zp*=$Z1r$&~0RuKA3}*x* z8O3M{jA0VfX<{a`n9UsK(ZXU@vYIulrImH8r;VNLVmEu(O9uxy#4*lrj!w>Vfs0(? zI(NCx10M2-$2_BpSH-XOT7mySpo#`2GlTgoUOk@Ka*~DfpbA_v1%O{6j=SrZq_pH-gwQRErWCHQL zTtk6;3JlI?+jgx$5Ex1wQ<%nF7P6G(Y-Kz9IKl}|ag*CT;U#aBQlKmahEdN{8fj(` z%UHoScCeqL;R^((xy2ox(oOvOfuVo`3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUg NfC36Apg`Xf_zihkPc8re literal 0 HcmV?d00001 diff --git a/mudserver.db.test-wal b/mudserver.db.test-wal new file mode 100644 index 0000000000000000000000000000000000000000..9ef58b89afeb6b26631899d59eb80ce68512e150 GIT binary patch literal 547992 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 literal 0 HcmV?d00001 diff --git a/src/admin.rs b/src/admin.rs index 01c4d2f..5249237 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -74,6 +74,7 @@ async fn admin_promote(target: &str, state: &SharedState) -> CommandResult { if target.is_empty() { return simple(&format!("{}\r\n", ansi::error_msg("Usage: admin promote "))); } + log::info!("Admin action: promote player '{}'", target); let st = state.lock().await; if st.db.set_admin(target, true) { // Also update in-memory if online @@ -124,6 +125,7 @@ async fn admin_demote(target: &str, state: &SharedState) -> CommandResult { if target.is_empty() { return simple(&format!("{}\r\n", ansi::error_msg("Usage: admin demote "))); } + log::info!("Admin action: demote player '{}'", target); let st = state.lock().await; if st.db.set_admin(target, false) { simple(&format!( @@ -142,6 +144,7 @@ async fn admin_kick(target: &str, player_id: usize, state: &SharedState) -> Comm if target.is_empty() { return simple(&format!("{}\r\n", ansi::error_msg("Usage: admin kick "))); } + log::info!("Admin action: kick player '{}'", target); let mut st = state.lock().await; let low = target.to_lowercase(); @@ -237,6 +240,7 @@ async fn admin_teleport(room_id: &str, player_id: usize, state: &SharedState) -> ansi::error_msg("Usage: admin teleport ") )); } + log::info!("Admin action: teleport player ID {} to '{}'", player_id, room_id); let mut st = state.lock().await; if st.world.get_room(room_id).is_none() { let rooms: Vec<&String> = st.world.rooms.keys().collect(); @@ -329,6 +333,7 @@ async fn admin_teleport(room_id: &str, player_id: usize, state: &SharedState) -> } async fn admin_registration(args: &str, state: &SharedState) -> CommandResult { + log::info!("Admin action: registration setting updated: '{}'", args); let st = state.lock().await; match args.to_lowercase().as_str() { "on" | "true" | "open" => { @@ -365,6 +370,7 @@ async fn admin_announce(msg: &str, player_id: usize, state: &SharedState) -> Com ansi::error_msg("Usage: admin announce ") )); } + log::info!("Admin action: announcement by player ID {}: '{}'", player_id, msg); let st = state.lock().await; let announcement = CryptoVec::from( format!( @@ -405,6 +411,7 @@ async fn admin_announce(msg: &str, player_id: usize, state: &SharedState) -> Com } async fn admin_heal(args: &str, player_id: usize, state: &SharedState) -> CommandResult { + log::info!("Admin action: heal player '{}' (empty means self)", args); let mut st = state.lock().await; if args.is_empty() { @@ -564,6 +571,7 @@ async fn admin_info(target: &str, state: &SharedState) -> CommandResult { } async fn admin_setattitude(args: &str, state: &SharedState) -> CommandResult { + log::info!("Admin action: setattitude '{}'", args); let parts: Vec<&str> = args.splitn(3, ' ').collect(); if parts.len() < 3 { return simple(&format!( diff --git a/src/combat.rs b/src/combat.rs index 1307ba4..61314c1 100644 --- a/src/combat.rs +++ b/src/combat.rs @@ -69,6 +69,8 @@ pub fn resolve_combat_tick( )); if new_npc_hp <= 0 { + let player_name = state.players.get(&player_id).map(|c| c.player.name.clone()).unwrap_or_else(|| "Unknown".into()); + log::info!("Combat: Player '{}' (ID {}) killed NPC '{}' ({})", player_name, player_id, npc_template.name, npc_id); if let Some(inst) = state.npc_instances.get_mut(&npc_id) { inst.alive = false; inst.hp = 0; @@ -351,7 +353,9 @@ pub fn player_death_respawn(player_id: usize, state: &mut GameState) -> String { .players .get(&player_id) .map(|c| c.player.name.clone()) - .unwrap_or_default(); + .unwrap_or_else(|| "Unknown".into()); + + log::info!("Combat: Player '{}' (ID {}) died and respawned at {}", player_name, player_id, spawn_room); if let Some(conn) = state.players.get_mut(&player_id) { conn.player.stats.hp = conn.player.stats.max_hp; diff --git a/src/main.rs b/src/main.rs index 41cf891..8b8e744 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::sync::Arc; use tokio::sync::Mutex; +use flexi_logger::{Cleanup, Criterion, Duplicate, FileSpec, Logger, Naming, WriteMode}; use russh::keys::ssh_key::rand_core::OsRng; use russh::server::Server as _; use tokio::net::TcpListener; @@ -18,12 +19,12 @@ const DEFAULT_DB_PATH: &str = "./mudserver.db"; #[tokio::main] async fn main() { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - let mut port = DEFAULT_PORT; let mut jsonrpc_port = 2223; let mut world_dir = PathBuf::from(DEFAULT_WORLD_DIR); let mut db_path = PathBuf::from(DEFAULT_DB_PATH); + let mut log_dir = "logs".to_string(); + let mut log_level = "info".to_string(); let args: Vec = std::env::args().collect(); let mut i = 1; @@ -51,12 +52,22 @@ async fn main() { i += 1; db_path = PathBuf::from(args.get(i).expect("--db requires a path")); } + "--log-dir" => { + i += 1; + log_dir = args.get(i).expect("--log-dir requires a path").to_string(); + } + "--log-level" => { + i += 1; + log_level = args.get(i).expect("--log-level requires a level").to_string(); + } "--help" => { eprintln!("Usage: mudserver [OPTIONS]"); eprintln!(" --port, -p SSH listen port (default: {DEFAULT_PORT})"); eprintln!(" --rpc-port JSON-RPC listen port (default: 2223)"); eprintln!(" --world, -w World directory (default: {DEFAULT_WORLD_DIR})"); eprintln!(" --db, -d Database path (default: {DEFAULT_DB_PATH})"); + eprintln!(" --log-dir Directory for log files (default: logs)"); + eprintln!(" --log-level Logging level (default: info)"); std::process::exit(0); } other => { @@ -67,6 +78,23 @@ async fn main() { i += 1; } + // Initialize logger + Logger::try_with_str(&log_level) + .unwrap() + .log_to_file(FileSpec::default().directory(&log_dir).basename("mudserver")) + .duplicate_to_stderr(Duplicate::All) + .rotate( + Criterion::Size(10_000_000), // 10 MB + Naming::Numbers, + Cleanup::KeepLogFiles(7), + ) + .write_mode(WriteMode::BufferAndFlush) + .start() + .unwrap_or_else(|e| { + eprintln!("Failed to initialize logger: {e}"); + std::process::exit(1); + }); + log::info!("Loading world from: {}", world_dir.display()); let loaded_world = world::World::load(&world_dir).unwrap_or_else(|e| { eprintln!("Failed to load world: {e}"); diff --git a/src/ssh.rs b/src/ssh.rs index 658ce4e..2751cde 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -83,6 +83,8 @@ impl MudHandler { state.load_existing_player(self.id, saved, Some(channel), Some(handle)); drop(state); + log::info!("Player '{}' (id={}) logged in", self.username, self.id); + let msg = format!( "{}\r\n", ansi::system_msg("Welcome back! Your character has been restored.") @@ -171,6 +173,13 @@ impl MudHandler { .map(|c| c.name.clone()) .unwrap_or_default(); + log::info!( + "New character created: {} (Race: {}, Class: {})", + self.username, + race_name, + class_name + ); + state.create_new_player( self.id, self.username.clone(),