diff --git a/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md b/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md index 26ac9b06..23b24b41 100644 --- a/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md +++ b/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md @@ -94,7 +94,8 @@ 交付物:[../server-rs/apps/api-server/src/main.rs](../server-rs/apps/api-server/src/main.rs) - [x] 接入统一配置加载 交付物:[../server-rs/apps/api-server/src/config.rs](../server-rs/apps/api-server/src/config.rs) -- [ ] 接入统一日志与 tracing +- [x] 接入统一日志与 tracing + 交付物:[../server-rs/apps/api-server/src/logging.rs](../server-rs/apps/api-server/src/logging.rs)、[../server-rs/apps/api-server/src/app.rs](../server-rs/apps/api-server/src/app.rs)、[../server-rs/apps/api-server/src/main.rs](../server-rs/apps/api-server/src/main.rs) - [ ] 接入 `request_id` 中间件 - [ ] 接入统一错误处理中间件 - [ ] 接入当前项目兼容的 response envelope diff --git a/server-rs/Cargo.lock b/server-rs/Cargo.lock index a0c31918..e0111c81 100644 --- a/server-rs/Cargo.lock +++ b/server-rs/Cargo.lock @@ -2,12 +2,24 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "api-server" version = "0.1.0" dependencies = [ "axum", "tokio", + "tower-http", + "tracing", + "tracing-subscriber", ] [[package]] @@ -68,12 +80,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -202,6 +226,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[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" @@ -214,6 +244,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 = "matchit" version = "0.8.4" @@ -243,6 +282,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -279,6 +327,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "ryu" version = "1.0.23" @@ -350,6 +415,15 @@ dependencies = [ "serde", ] +[[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 = "slab" version = "0.4.12" @@ -389,6 +463,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[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.1" @@ -430,6 +513,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "http", + "http-body", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -450,9 +549,21 @@ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "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" @@ -460,6 +571,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]] @@ -468,6 +609,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/server-rs/apps/api-server/Cargo.toml b/server-rs/apps/api-server/Cargo.toml index 52f6578d..972d07eb 100644 --- a/server-rs/apps/api-server/Cargo.toml +++ b/server-rs/apps/api-server/Cargo.toml @@ -7,3 +7,6 @@ license.workspace = true [dependencies] axum = "0.8" tokio = { version = "1", features = ["macros", "rt-multi-thread", "net"] } +tower-http = { version = "0.6", features = ["trace"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } diff --git a/server-rs/apps/api-server/README.md b/server-rs/apps/api-server/README.md index 35ff7b5e..eaea891e 100644 --- a/server-rs/apps/api-server/README.md +++ b/server-rs/apps/api-server/README.md @@ -22,14 +22,22 @@ 4. `src/app.rs` 5. `src/state.rs` 6. `src/config.rs` +7. `src/logging.rs` +8. 基础 `TraceLayer` 挂载与 `tracing subscriber` 初始化 后续与本 package 直接相关的任务包括: -1. 接入统一日志与 tracing -2. 接入 `request_id` -3. 接入统一错误处理中间件 -4. 接入 response envelope -5. 接入 `/healthz` +1. [x] 接入统一日志与 tracing +2. [ ] 接入 `request_id` +3. [ ] 接入统一错误处理中间件 +4. [ ] 接入 response envelope +5. [ ] 接入 `/healthz` + +当前 tracing 约定: + +1. 进程启动时统一初始化 `tracing subscriber`。 +2. 默认日志过滤器来自 `GENARRATIVE_API_LOG`,未提供时回落到 `info,tower_http=info`。 +3. HTTP 访问日志统一通过 Axum 路由层的 `TraceLayer` 输出,后续 `request_id`、响应头与错误中间件继续在同一层扩展。 ## 3. 边界约束 diff --git a/server-rs/apps/api-server/src/app.rs b/server-rs/apps/api-server/src/app.rs index f63bf81d..55a8d29c 100644 --- a/server-rs/apps/api-server/src/app.rs +++ b/server-rs/apps/api-server/src/app.rs @@ -1,8 +1,19 @@ use axum::Router; +use tower_http::trace::{DefaultMakeSpan, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse, TraceLayer}; +use tracing::Level; use crate::state::AppState; // 统一由这里构造 Axum 路由树,后续再逐项挂接中间件与业务路由。 pub fn build_router(state: AppState) -> Router { - Router::new().with_state(state) + Router::new() + // 当前阶段先统一挂接 HTTP tracing,后续 request_id、响应头与错误中间件继续在这里扩展。 + .layer( + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) + .on_request(DefaultOnRequest::new().level(Level::INFO)) + .on_response(DefaultOnResponse::new().level(Level::INFO)) + .on_failure(DefaultOnFailure::new().level(Level::ERROR)), + ) + .with_state(state) } diff --git a/server-rs/apps/api-server/src/config.rs b/server-rs/apps/api-server/src/config.rs index 2807ce31..df637ad9 100644 --- a/server-rs/apps/api-server/src/config.rs +++ b/server-rs/apps/api-server/src/config.rs @@ -5,6 +5,7 @@ use std::{env, net::SocketAddr}; pub struct AppConfig { pub bind_host: String, pub bind_port: u16, + pub log_filter: String, } impl Default for AppConfig { @@ -12,6 +13,7 @@ impl Default for AppConfig { Self { bind_host: "127.0.0.1".to_string(), bind_port: 3000, + log_filter: "info,tower_http=info".to_string(), } } } @@ -32,6 +34,12 @@ impl AppConfig { } } + if let Ok(log_filter) = env::var("GENARRATIVE_API_LOG") { + if !log_filter.trim().is_empty() { + config.log_filter = log_filter; + } + } + config } diff --git a/server-rs/apps/api-server/src/logging.rs b/server-rs/apps/api-server/src/logging.rs new file mode 100644 index 00000000..f1acce0c --- /dev/null +++ b/server-rs/apps/api-server/src/logging.rs @@ -0,0 +1,19 @@ +use std::io; + +use tracing_subscriber::{fmt, EnvFilter}; + +use crate::config::AppConfig; + +// 统一在独立模块初始化 tracing,避免入口层和后续测试入口重复散落 subscriber 配置。 +pub fn init_tracing(config: &AppConfig) -> Result<(), io::Error> { + let env_filter = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new(config.log_filter.as_str())) + .unwrap_or_else(|_| EnvFilter::new("info")); + + fmt() + .with_env_filter(env_filter) + .with_target(true) + .compact() + .try_init() + .map_err(|error| io::Error::other(format!("初始化 tracing subscriber 失败:{error}"))) +} diff --git a/server-rs/apps/api-server/src/main.rs b/server-rs/apps/api-server/src/main.rs index 0cab5b62..b2a3d240 100644 --- a/server-rs/apps/api-server/src/main.rs +++ b/server-rs/apps/api-server/src/main.rs @@ -1,20 +1,26 @@ mod app; mod config; +mod logging; mod state; use tokio::net::TcpListener; +use tracing::info; -use crate::{app::build_router, config::AppConfig, state::AppState}; +use crate::{app::build_router, config::AppConfig, logging::init_tracing, state::AppState}; #[tokio::main] async fn main() -> Result<(), std::io::Error> { // 统一先从配置对象读取监听地址,避免后续把环境变量读取散落到入口和路由层。 let config = AppConfig::from_env(); + init_tracing(&config)?; + let bind_address = config.bind_socket_addr(); let listener = TcpListener::bind(bind_address).await?; let state = AppState::new(config); let router = build_router(state); + info!(%bind_address, "api-server 已完成 tracing 初始化并开始监听"); + axum::serve(listener, router).await }