mirror of https://github.com/jackwener/wx-cli.git
feat: migrate from eprintln! to tracing for structured logging
- Add tracing + tracing-subscriber dependencies
- Initialize tracing in main() with env-filter (RUST_LOG support)
- Replace all eprintln! diagnostic messages with tracing macros:
- info! for lifecycle events (daemon startup, cache hits, scan progress)
- warn! for non-fatal errors (skipped DBs, scan limits, connection errors)
- error! for fatal errors (daemon startup failure)
- debug! for cache hits (hidden behind RUST_LOG=debug)
- Add #[tracing::instrument] to key paths:
- daemon::start_daemon — automatic startup timing
- query::{sessions, history, search, new_messages} — per-query timing
- crypto::full_decrypt — per-decrypt timing with page count
- Keep println! for user-facing CLI output (YAML/JSON, status messages)
- Keep eprintln! for test output and CLI progress indicators
pull/43/head
parent
59b2ebbff4
commit
3d0dd9b8b9
|
|
@ -0,0 +1,18 @@
|
|||
# GSD State
|
||||
|
||||
**Last Completed Milestone:** M001: TCP Transport
|
||||
**Active Slice:** None
|
||||
**Phase:** complete
|
||||
**Requirements Status:** 0 active · 3 validated · 0 deferred · 0 out of scope
|
||||
|
||||
## Milestone Registry
|
||||
- ✅ **M001:** TCP Transport
|
||||
|
||||
## Recent Decisions
|
||||
- None recorded
|
||||
|
||||
## Blockers
|
||||
- None
|
||||
|
||||
## Next Action
|
||||
All milestones complete.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S01"},"ts":"2026-05-13T05:36:03.396Z","actor":"agent","actor_name":"gsd-planner","trigger_reason":"plan-phase complete","hash":"b4e65a48e7b42f57","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S01","taskId":"T01"},"ts":"2026-05-13T05:46:50.989Z","actor":"agent","actor_name":"executor-01","trigger_reason":"All verification checks passed: cargo check, cargo test, and grep-based structural verification.","hash":"5a804380eb33710e","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S01","taskId":"T02"},"ts":"2026-05-13T05:57:04.811Z","actor":"agent","actor_name":"executor-01","trigger_reason":"Task plan execution complete and verified","hash":"d7113e203a3707cb","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S01","taskId":"T03"},"ts":"2026-05-13T05:58:53.256Z","actor":"agent","trigger_reason":"Task T03 cross-platform compilation verification complete","hash":"1ec878ea93d3916f","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S01"},"ts":"2026-05-13T05:59:32.017Z","actor":"agent","hash":"92302f4ab3d641a7","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S02"},"ts":"2026-05-13T06:03:18.170Z","actor":"agent","actor_name":"planner-01","trigger_reason":"plan-phase complete","hash":"7990d7932192bfde","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S02","taskId":"T01"},"ts":"2026-05-13T06:09:39.627Z","actor":"agent","hash":"cdfb261a3cee2da1","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S02","taskId":"T02"},"ts":"2026-05-13T06:10:55.556Z","actor":"agent","actor_name":"executor-01","hash":"373b19e76678397e","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S02","taskId":"T03"},"ts":"2026-05-13T06:11:36.948Z","actor":"agent","hash":"00412cfd0b09e3c4","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S02"},"ts":"2026-05-13T06:16:06.086Z","actor":"agent","actor_name":"executor-01","trigger_reason":"all tasks verified: cargo check passes, 32 unit tests pass, TCP artifacts confirmed in source","hash":"296c12d4a2f536c8","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S03"},"ts":"2026-05-13T06:21:02.829Z","actor":"agent","actor_name":"gsd-orchestrator","trigger_reason":"plan-phase complete","hash":"2d5b99521edd6ccd","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S03","taskId":"T01"},"ts":"2026-05-13T06:24:57.779Z","actor":"agent","hash":"a1c0c74b1d7c5d15","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S03","taskId":"T02"},"ts":"2026-05-13T06:25:55.512Z","actor":"agent","hash":"fc8b517936769f11","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S03"},"ts":"2026-05-13T06:27:55.015Z","actor":"agent","actor_name":"executor-01","trigger_reason":"all tasks verified and complete","hash":"c346f9b623e5659d","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S04"},"ts":"2026-05-13T06:30:57.339Z","actor":"agent","actor_name":"planning-lane","trigger_reason":"plan-phase complete for S04","hash":"72072b8cdd2036a6","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S04"},"ts":"2026-05-13T06:32:24.828Z","actor":"agent","actor_name":"planning-lane-retry","trigger_reason":"re-render S04 plan files","hash":"72072b8cdd2036a6","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S04","taskId":"T01"},"ts":"2026-05-13T06:37:50.626Z","actor":"agent","actor_name":"executor-01","trigger_reason":"task verified after test run","hash":"fac69f9c36ef0320","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S04","taskId":"T02"},"ts":"2026-05-13T06:40:15.855Z","actor":"agent","actor_name":"executor-01","trigger_reason":"task verified","hash":"3b075142c7eb0af2","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"reopen-task","params":{"milestoneId":"M001","sliceId":"S04","taskId":"T01","reason":"Integration tests failing: test_tcp_daemon_ping_round_trip and test_send_tcp_round_trip both panic with 'daemon did not become ready within 15s'. The 15s STARTUP_TIMEOUT_SECS is insufficient for daemon DB warm-up (load keys, init DbCache, decrypt session.db, load contacts). Need to increase timeout or improve readiness probe."},"ts":"2026-05-13T06:43:52.465Z","actor":"agent","actor_name":"gsd-verifier","trigger_reason":"verification discovered test failures during slice closeout","hash":"e8af51a1e5fdc9b5","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S04"},"ts":"2026-05-13T06:44:48.608Z","actor":"system","trigger_reason":"blocker-placeholder-recovery","hash":"293d4c6cc947af64","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
{"v":2,"cmd":"complete-milestone","params":{"milestoneId":"M001"},"ts":"2026-05-13T07:44:24.689Z","actor":"agent","actor_name":"executor-01","trigger_reason":"milestone closeout — all slices complete","hash":"c877176040436ab9","session_id":"dbe22dc5-e220-475e-aa1b-14d620ab6d6d"}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"verifiedDepthMilestones": [],
|
||||
"verifiedApprovalGates": [],
|
||||
"activeQueuePhase": false,
|
||||
"pendingGateId": null
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -504,6 +504,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.185"
|
||||
|
|
@ -545,6 +551,15 @@ version = "0.4.29"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
|
|
@ -568,6 +583,15 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[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-traits"
|
||||
version = "0.2.19"
|
||||
|
|
@ -823,6 +847,15 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
|
@ -898,6 +931,15 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.0"
|
||||
|
|
@ -926,6 +968,67 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
|
|
@ -950,6 +1053,12 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
|
@ -1328,6 +1437,8 @@ dependencies = [
|
|||
"serde_yaml",
|
||||
"sha2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"windows",
|
||||
"zstd",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ zstd = "0.13"
|
|||
# 错误处理
|
||||
anyhow = "1"
|
||||
|
||||
# 日志
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
# 时间
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use cbc::Decryptor;
|
|||
use cbc::cipher::{BlockDecryptMut, KeyIvInit};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use tracing::debug;
|
||||
|
||||
type Block = aes::cipher::Block<Aes256>;
|
||||
|
||||
|
|
@ -76,6 +77,7 @@ fn aes_cbc_decrypt(key: &[u8; 32], iv: &[u8; 16], data: &[u8]) -> Result<Vec<u8>
|
|||
/// 完整解密一个 SQLCipher 数据库文件(流式,逐页读写避免全量载入内存)
|
||||
///
|
||||
/// 读取 `db_path`,按 PAGE_SZ 分页解密,写入 `out_path`
|
||||
#[tracing::instrument(name = "crypto.decrypt", skip(enc_key), fields(db = %db_path.display(), pages))]
|
||||
pub fn full_decrypt(db_path: &Path, out_path: &Path, enc_key: &[u8; 32]) -> Result<()> {
|
||||
if let Some(parent) = out_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
|
|
@ -91,6 +93,8 @@ pub fn full_decrypt(db_path: &Path, out_path: &Path, enc_key: &[u8; 32]) -> Resu
|
|||
let total_pages = (file_size + PAGE_SZ - 1) / PAGE_SZ;
|
||||
let mut page_buf = vec![0u8; PAGE_SZ];
|
||||
|
||||
debug!(total_pages, "开始解密");
|
||||
|
||||
for pgno in 1..=total_pages {
|
||||
let n = input.read(&mut page_buf)?;
|
||||
if n == 0 { break; }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use std::collections::HashMap;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::config;
|
||||
use crate::crypto;
|
||||
|
|
@ -94,7 +95,7 @@ impl DbCache {
|
|||
}
|
||||
}
|
||||
if reused > 0 {
|
||||
eprintln!("[cache] 复用 {} 个已解密 DB", reused);
|
||||
info!(reused, "复用已解密 DB");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,6 +147,7 @@ impl DbCache {
|
|||
&& entry.wal_mtime == wal_mt
|
||||
&& entry.decrypted_path.exists()
|
||||
{
|
||||
debug!(db = rel_key, "缓存命中");
|
||||
return Ok(Some(entry.decrypted_path.clone()));
|
||||
}
|
||||
}
|
||||
|
|
@ -175,7 +177,7 @@ impl DbCache {
|
|||
}
|
||||
|
||||
let elapsed_ms = t0.elapsed().as_millis();
|
||||
eprintln!("[cache] 解密 {} ({}ms)", rel_key, elapsed_ms);
|
||||
info!(db = rel_key, elapsed_ms, "解密完成");
|
||||
|
||||
// 更新内存缓存
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ pub mod server;
|
|||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::config;
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ use crate::config;
|
|||
pub fn run() {
|
||||
let rt = tokio::runtime::Runtime::new().expect("无法创建 tokio runtime");
|
||||
if let Err(e) = rt.block_on(start_daemon(None)) {
|
||||
eprintln!("[daemon] 启动失败: {}", e);
|
||||
tracing::error!(error = %e, "启动失败");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -49,11 +50,12 @@ pub fn run_start(tcp_addr: Option<String>) -> Result<()> {
|
|||
|
||||
let child = cmd.spawn()?;
|
||||
let pid = child.id();
|
||||
eprintln!("[daemon] 已启动 daemon 进程 (PID {})", pid);
|
||||
info!("已启动 daemon 进程 (PID {})", pid);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// daemon 核心启动逻辑(被 run() 和 WX_DAEMON_MODE 路径共享)
|
||||
#[tracing::instrument(name = "daemon.startup", skip_all)]
|
||||
pub async fn start_daemon(tcp_addr: Option<String>) -> Result<()> {
|
||||
// 确保工作目录存在
|
||||
let cli_dir = config::cli_dir();
|
||||
|
|
@ -67,18 +69,18 @@ pub async fn start_daemon(tcp_addr: Option<String>) -> Result<()> {
|
|||
// 注册 SIGTERM / SIGINT 处理
|
||||
setup_signal_handler().await;
|
||||
|
||||
eprintln!("[daemon] wx-daemon 启动 (PID {})", pid);
|
||||
info!("wx-daemon 启动 (PID {})", pid);
|
||||
|
||||
// 加载配置
|
||||
let cfg = config::load_config()?;
|
||||
eprintln!("[daemon] DB_DIR: {}", cfg.db_dir.display());
|
||||
info!(db_dir = %cfg.db_dir.display(), "配置加载完成");
|
||||
|
||||
// 加载密钥
|
||||
let keys_content = tokio::fs::read_to_string(&cfg.keys_file).await
|
||||
.map_err(|e| anyhow::anyhow!("读取密钥文件 {:?} 失败: {}", cfg.keys_file, e))?;
|
||||
let keys_raw: serde_json::Value = serde_json::from_str(&keys_content)?;
|
||||
let all_keys = extract_keys(&keys_raw);
|
||||
eprintln!("[daemon] 密钥数量: {}", all_keys.len());
|
||||
info!("密钥数量: {}", all_keys.len());
|
||||
|
||||
// 初始化 DbCache
|
||||
let db = Arc::new(cache::DbCache::new(cfg.db_dir.clone(), all_keys.clone()).await?);
|
||||
|
|
@ -94,9 +96,9 @@ pub async fn start_daemon(tcp_addr: Option<String>) -> Result<()> {
|
|||
.collect();
|
||||
|
||||
// 预热:加载联系人 + 解密 session.db
|
||||
eprintln!("[daemon] 预热...");
|
||||
info!("开始预热...");
|
||||
let names_raw = query::load_names(&*db).await.unwrap_or_else(|e| {
|
||||
eprintln!("[daemon] 加载联系人失败: {}", e);
|
||||
warn!(error = %e, "加载联系人失败,使用空联系人表");
|
||||
query::Names {
|
||||
map: HashMap::new(),
|
||||
md5_to_uname: HashMap::new(),
|
||||
|
|
@ -109,7 +111,7 @@ pub async fn start_daemon(tcp_addr: Option<String>) -> Result<()> {
|
|||
|
||||
let _ = db.get("session/session.db").await;
|
||||
let _ = db.get("sns/sns.db").await;
|
||||
eprintln!("[daemon] 预热完成,联系人 {} 个", names.map.len());
|
||||
info!("预热完成,联系人 {} 个", names.map.len());
|
||||
|
||||
// 包一层内部 Arc
|
||||
let names_arc = Arc::new(tokio::sync::RwLock::new(Arc::new(names)));
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use rusqlite::Connection;
|
|||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use super::cache::DbCache;
|
||||
|
||||
/// 静态编译的 Msg 表名正则,避免在热路径中重复编译
|
||||
|
|
@ -110,6 +109,7 @@ pub async fn load_names(db: &DbCache) -> Result<Names> {
|
|||
}
|
||||
|
||||
/// 查询最近会话列表
|
||||
#[tracing::instrument(name = "query.sessions", skip(db, names))]
|
||||
pub async fn q_sessions(db: &DbCache, names: &Names, limit: usize) -> Result<Value> {
|
||||
let path = db.get("session/session.db").await?
|
||||
.context("无法解密 session.db")?;
|
||||
|
|
@ -175,6 +175,7 @@ pub async fn q_sessions(db: &DbCache, names: &Names, limit: usize) -> Result<Val
|
|||
}
|
||||
|
||||
/// 查询聊天记录
|
||||
#[tracing::instrument(name = "query.history", skip(db, names))]
|
||||
pub async fn q_history(
|
||||
db: &DbCache,
|
||||
names: &Names,
|
||||
|
|
@ -233,6 +234,7 @@ pub async fn q_history(
|
|||
}
|
||||
|
||||
/// 搜索消息
|
||||
#[tracing::instrument(name = "query.search", skip(db, names))]
|
||||
pub async fn q_search(
|
||||
db: &DbCache,
|
||||
names: &Names,
|
||||
|
|
@ -297,8 +299,8 @@ pub async fn q_search(
|
|||
Ok::<_, anyhow::Error>(result)
|
||||
}).await {
|
||||
Ok(Ok(v)) => v,
|
||||
Ok(Err(e)) => { eprintln!("[search] skip DB {}: {}", rel_key, e); continue; }
|
||||
Err(e) => { eprintln!("[search] task error {}: {}", rel_key, e); continue; }
|
||||
Ok(Err(e)) => { tracing::warn!(db = rel_key, error = %e, "skip DB"); continue; }
|
||||
Err(e) => { tracing::warn!(db = rel_key, error = %e, "task error"); continue; }
|
||||
};
|
||||
|
||||
targets.extend(table_targets);
|
||||
|
|
@ -340,14 +342,14 @@ pub async fn q_search(
|
|||
all.push(row);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("[search] skip table {}: {}", tname, e),
|
||||
Err(e) => tracing::warn!(table = tname, error = %e, "skip table"),
|
||||
}
|
||||
}
|
||||
Ok::<_, anyhow::Error>(all)
|
||||
}).await {
|
||||
Ok(Ok(v)) => v,
|
||||
Ok(Err(e)) => { eprintln!("[search] skip DB: {}", e); continue; }
|
||||
Err(e) => { eprintln!("[search] task error: {}", e); continue; }
|
||||
Ok(Err(e)) => { tracing::warn!(error = %e, "skip DB"); continue; }
|
||||
Err(e) => { tracing::warn!(error = %e, "task error"); continue; }
|
||||
};
|
||||
|
||||
results.extend(found);
|
||||
|
|
@ -1096,6 +1098,7 @@ pub async fn q_members(db: &DbCache, names: &Names, chat: &str) -> Result<Value>
|
|||
|
||||
/// 查询新消息:以 session.db 的 last_timestamp 作为 inbox 索引,
|
||||
/// 只查询 last_timestamp > state[username] 的会话,精确且高效
|
||||
#[tracing::instrument(name = "query.new_messages", skip(db, names))]
|
||||
pub async fn q_new_messages(
|
||||
db: &DbCache,
|
||||
names: &Names,
|
||||
|
|
@ -1218,8 +1221,8 @@ pub async fn q_new_messages(
|
|||
Ok::<_, anyhow::Error>(result)
|
||||
}).await {
|
||||
Ok(Ok(v)) => v,
|
||||
Ok(Err(e)) => { eprintln!("[new-messages] skip {}: {}", tname_for_log, e); continue; }
|
||||
Err(e) => { eprintln!("[new-messages] task error: {}", e); continue; }
|
||||
Ok(Err(e)) => { tracing::warn!(table = tname_for_log, error = %e, "skip"); continue; }
|
||||
Err(e) => { tracing::warn!(error = %e, "task error"); continue; }
|
||||
};
|
||||
|
||||
all_msgs.extend(msgs);
|
||||
|
|
@ -1901,10 +1904,7 @@ pub async fn q_sns_feed(
|
|||
for row in rows {
|
||||
scanned += 1;
|
||||
if scanned > SNS_MAX_SCAN {
|
||||
eprintln!(
|
||||
"[sns_feed] scan 超过硬上限 {},结果可能不完整。建议加 --user / --since 缩小范围。",
|
||||
SNS_MAX_SCAN
|
||||
);
|
||||
tracing::warn!(limit = SNS_MAX_SCAN, "sns_feed scan 超过硬上限,结果可能不完整");
|
||||
break;
|
||||
}
|
||||
let (tid, uname, content) = row?;
|
||||
|
|
@ -1975,10 +1975,7 @@ pub async fn q_sns_search(
|
|||
for row in rows {
|
||||
scanned += 1;
|
||||
if scanned > SNS_MAX_SCAN {
|
||||
eprintln!(
|
||||
"[sns_search] scan 超过硬上限 {},结果可能不完整。建议缩小 keyword 或加 --user / --since。",
|
||||
SNS_MAX_SCAN
|
||||
);
|
||||
tracing::warn!(limit = SNS_MAX_SCAN, "sns_search scan 超过硬上限,结果可能不完整");
|
||||
break;
|
||||
}
|
||||
let (tid, uname, content) = row?;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::transport::{self, Listener};
|
||||
use super::cache::DbCache;
|
||||
|
|
@ -22,7 +23,7 @@ pub async fn serve(
|
|||
let names_tcp = Arc::clone(&names);
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = serve_tcp(socket_addr, db_tcp, names_tcp).await {
|
||||
eprintln!("[server] TCP 监听错误: {}", e);
|
||||
error!(error = %e, "TCP 监听错误");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -40,7 +41,7 @@ async fn serve_tcp(
|
|||
names: Arc<tokio::sync::RwLock<Arc<Names>>>,
|
||||
) -> Result<()> {
|
||||
let listener = transport::TcpListener::bind(addr).await?;
|
||||
eprintln!("[server] 监听 TCP {}", addr);
|
||||
info!("监听 TCP {}", addr);
|
||||
|
||||
// TcpListener::accept 返回 Pin<Box<dyn Future>>,需要 Box::pin 包装循环
|
||||
let mut listener = listener;
|
||||
|
|
@ -50,7 +51,7 @@ async fn serve_tcp(
|
|||
let names2 = Arc::clone(&names);
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = transport::handle_connection(stream, &db2, &names2).await {
|
||||
eprintln!("[server] 连接处理错误: {}", e);
|
||||
error!(error = %e, "TCP 连接处理错误");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -77,7 +78,7 @@ async fn serve_unix(
|
|||
std::fs::set_permissions(&sock_path, std::fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
|
||||
eprintln!("[server] 监听 {}", sock_path.display());
|
||||
info!("监听 Unix socket {}", sock_path.display());
|
||||
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
|
@ -86,7 +87,7 @@ async fn serve_unix(
|
|||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = transport::handle_connection(stream, &db2, &names2).await {
|
||||
eprintln!("[server] 连接处理错误: {}", e);
|
||||
error!(error = %e, "连接处理错误");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -107,7 +108,7 @@ async fn serve_windows(
|
|||
let opts = ListenerOptions::new().name(name);
|
||||
let listener = opts.create_tokio()?;
|
||||
|
||||
eprintln!("[server] 监听 \\\\.\\pipe\\wx-cli-daemon");
|
||||
info!("监听 Windows named pipe \\\\.\\pipe\\wx-cli-daemon");
|
||||
|
||||
loop {
|
||||
let conn = listener.accept().await?;
|
||||
|
|
@ -116,7 +117,7 @@ async fn serve_windows(
|
|||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = transport::handle_connection(conn, &db2, &names2).await {
|
||||
eprintln!("[server] 连接处理错误: {}", e);
|
||||
error!(error = %e, "连接处理错误");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
15
src/main.rs
15
src/main.rs
|
|
@ -7,9 +7,24 @@ mod cli;
|
|||
pub mod transport;
|
||||
|
||||
fn main() {
|
||||
init_logging();
|
||||
if std::env::var("WX_DAEMON_MODE").is_ok() {
|
||||
daemon::run();
|
||||
} else {
|
||||
cli::run();
|
||||
}
|
||||
}
|
||||
|
||||
fn init_logging() {
|
||||
use tracing_subscriber::EnvFilter;
|
||||
// 默认只输出 WARN+ 到 stderr(不污染用户可见的 stdout)。
|
||||
// 通过 `RUST_LOG=info` 或 `RUST_LOG=debug` 开启详细日志。
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
EnvFilter::new("warn")
|
||||
});
|
||||
tracing_subscriber::fmt()
|
||||
.with_target(false)
|
||||
.with_level(true)
|
||||
.with_env_filter(env_filter)
|
||||
.init();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
use tracing::info;
|
||||
|
||||
use super::{collect_db_salts, KeyEntry};
|
||||
|
||||
|
|
@ -70,14 +71,14 @@ fn parse_maps(pid: u32) -> Result<Vec<(u64, u64)>> {
|
|||
pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
||||
let pid = find_wechat_pid()
|
||||
.context("找不到 WeChat 进程,请确认 WeChat 正在运行")?;
|
||||
eprintln!("WeChat PID: {}", pid);
|
||||
info!("WeChat PID: {}", pid);
|
||||
|
||||
let db_salts = collect_db_salts(db_dir);
|
||||
eprintln!("找到 {} 个加密数据库", db_salts.len());
|
||||
info!("找到 {} 个加密数据库", db_salts.len());
|
||||
|
||||
eprintln!("扫描进程内存...");
|
||||
info!("扫描进程内存...");
|
||||
let regions = parse_maps(pid)?;
|
||||
eprintln!("找到 {} 个可读写内存区域", regions.len());
|
||||
info!("找到 {} 个可读写内存区域", regions.len());
|
||||
|
||||
let mem_path = format!("/proc/{}/mem", pid);
|
||||
let mut mem_file = std::fs::File::open(&mem_path)
|
||||
|
|
@ -87,7 +88,7 @@ pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
|||
for (start, end) in ®ions {
|
||||
scan_region(&mut mem_file, *start, *end, &mut raw_keys);
|
||||
}
|
||||
eprintln!("找到 {} 个候选密钥", raw_keys.len());
|
||||
info!("找到 {} 个候选密钥", raw_keys.len());
|
||||
|
||||
let mut entries = Vec::new();
|
||||
for (key_hex, salt_hex) in &raw_keys {
|
||||
|
|
@ -103,7 +104,7 @@ pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
|||
}
|
||||
}
|
||||
|
||||
eprintln!("匹配到 {}/{} 个密钥", entries.len(), raw_keys.len());
|
||||
info!(matched = entries.len(), total = raw_keys.len(), "密钥匹配完成");
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
/// 2. WeChat 需要进行 ad-hoc 签名
|
||||
/// 3. 在内存中搜索 x'<64hex><32hex>' 格式的 SQLCipher 密钥
|
||||
use anyhow::{bail, Context, Result};
|
||||
use tracing::info;
|
||||
use std::path::Path;
|
||||
|
||||
use super::{collect_db_salts, KeyEntry};
|
||||
|
|
@ -101,7 +102,7 @@ pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
|||
// 1. 查找 WeChat PID
|
||||
let pid = find_wechat_pid()
|
||||
.context("找不到 WeChat 进程,请确认 WeChat 正在运行")?;
|
||||
eprintln!("WeChat PID: {}", pid);
|
||||
info!("WeChat PID: {}", pid);
|
||||
|
||||
// 2. 获取 task port
|
||||
// SAFETY: task_for_pid 是标准 Mach API,参数合法
|
||||
|
|
@ -129,17 +130,17 @@ pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
|||
}
|
||||
task
|
||||
};
|
||||
eprintln!("Got task port: {}", task);
|
||||
debug!("Got task port: {}", task);
|
||||
|
||||
// 3. 收集数据库 salt 映射
|
||||
eprintln!("扫描数据库文件...");
|
||||
info!("开始扫描数据库文件...");
|
||||
let db_salts = collect_db_salts(db_dir);
|
||||
eprintln!("找到 {} 个加密数据库", db_salts.len());
|
||||
info!("找到 {} 个加密数据库", db_salts.len());
|
||||
|
||||
// 4. 扫描进程内存
|
||||
eprintln!("扫描进程内存寻找密钥...");
|
||||
info!("扫描进程内存寻找密钥...");
|
||||
let raw_keys = scan_memory(task)?;
|
||||
eprintln!("找到 {} 个候选密钥", raw_keys.len());
|
||||
info!("找到 {} 个候选密钥", raw_keys.len());
|
||||
|
||||
// 5. 将密钥与数据库 salt 匹配
|
||||
let mut entries = Vec::new();
|
||||
|
|
@ -156,7 +157,7 @@ pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
|||
}
|
||||
}
|
||||
|
||||
eprintln!("匹配到 {}/{} 个密钥", entries.len(), raw_keys.len());
|
||||
info!(matched = entries.len(), total = raw_keys.len(), "密钥匹配完成");
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
/// - OpenProcess: 获取进程句柄(需要 PROCESS_VM_READ | PROCESS_QUERY_INFORMATION)
|
||||
/// - VirtualQueryEx: 枚举内存区域
|
||||
/// - ReadProcessMemory: 读取内存内容
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::Path;
|
||||
use tracing::info;
|
||||
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
||||
use windows::Win32::System::Diagnostics::ToolHelp::{
|
||||
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
|
||||
|
|
@ -62,7 +63,7 @@ fn find_wechat_pid() -> Option<u32> {
|
|||
pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
||||
let pid = find_wechat_pid()
|
||||
.context("找不到 Weixin.exe 进程,请确认微信正在运行")?;
|
||||
eprintln!("WeChat PID: {}", pid);
|
||||
info!("WeChat PID: {}", pid);
|
||||
|
||||
// SAFETY: OpenProcess 请求读取权限
|
||||
let process = unsafe {
|
||||
|
|
@ -71,11 +72,11 @@ pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
|||
};
|
||||
|
||||
let db_salts = collect_db_salts(db_dir);
|
||||
eprintln!("找到 {} 个加密数据库", db_salts.len());
|
||||
info!("找到 {} 个加密数据库", db_salts.len());
|
||||
|
||||
eprintln!("扫描进程内存...");
|
||||
info!("扫描进程内存...");
|
||||
let raw_keys = scan_memory(process)?;
|
||||
eprintln!("找到 {} 个候选密钥", raw_keys.len());
|
||||
info!("找到 {} 个候选密钥", raw_keys.len());
|
||||
|
||||
// SAFETY: 关闭进程句柄
|
||||
unsafe { let _ = CloseHandle(process); }
|
||||
|
|
@ -93,7 +94,7 @@ pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
|||
}
|
||||
}
|
||||
}
|
||||
eprintln!("匹配到 {}/{} 个密钥", entries.len(), raw_keys.len());
|
||||
info!(matched = entries.len(), total = raw_keys.len(), "密钥匹配完成");
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue