第九章:日志系统与配置管理
博客v1.0系列教程(Rust)博客 v1.0 系列教程 (Rust)
9.1 Tracing 日志系统
使用 tracing 和 tracing-subscriber 实现结构化日志:
// main.rs
use tracing_subscriber::EnvFilter;
fn init_logging() {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info")),
)
.with_target(true)
.with_thread_ids(true)
.with_file(true)
.with_line_number(true)
.init();
}
日志级别
// 在代码中使用
tracing::trace!("Trace level - very detailed");
tracing::debug!("Debug level - development info");
tracing::info!("Info level - normal operations");
tracing::warn!("Warning level - something unexpected");
tracing::error!("Error level - something went wrong");
结构化字段
tracing::info!(
user_id = %user.id,
action = "login",
ip = %client_ip,
"User logged in"
);
9.2 操作日志
pub async fn record_operation_log(
pool: &PgPool,
user_id: i64,
action: &str,
detail: &str,
ip: &str,
) -> Result<(), sqlx::Error> {
sqlx::query(
"INSERT INTO log_models (type, level, message, user_id, ip)
VALUES ($1, $2, $3, $4, $5)"
)
.bind("operation")
.bind("info")
.bind(format!("[{}] {}", action, detail))
.bind(user_id)
.bind(ip)
.execute(pool)
.await?;
Ok(())
}
9.3 审计日志查询
pub async fn query_logs(
pool: &PgPool,
page: u64,
page_size: u64,
log_type: Option<&str>,
level: Option<&str>,
) -> Result<PageResult<Log>, sqlx::Error> {
let offset = (page - 1) * page_size;
let logs = sqlx::query_as::<_, Log>(
"SELECT * FROM log_models
WHERE ($1::text IS NULL OR type = $1)
AND ($2::text IS NULL OR level = $2)
ORDER BY create_time DESC
LIMIT $3 OFFSET $4"
)
.bind(log_type)
.bind(level)
.bind(page_size as i64)
.bind(offset as i64)
.fetch_all(pool)
.await?;
let total = sqlx::query_scalar::<_, i64>(
"SELECT COUNT(*) FROM log_models
WHERE ($1::text IS NULL OR type = $1)
AND ($2::text IS NULL OR level = $2)"
)
.bind(log_type)
.bind(level)
.fetch_one(pool)
.await?;
Ok(PageResult {
items: logs,
total: total as u64,
page,
page_size,
})
}
9.4 运行时配置管理
站点配置以 JSON 格式持久化到数据库:
pub async fn get_site_config<T: serde::de::DeserializeOwned>(
pool: &PgPool,
name: &str,
) -> Result<Option<T>, AppError> {
let config = sqlx::query_as::<SiteConfig>(
"SELECT * FROM site_configs WHERE name = $1"
)
.bind(name)
.fetch_optional(pool)
.await?;
match config {
Some(c) => {
let value: T = serde_json::from_str(&c.config_json)
.map_err(|e| AppError::Internal(format!("Config parse error: {}", e)))?;
Ok(Some(value))
}
None => Ok(None),
}
}
pub async fn update_site_config<T: serde::Serialize>(
pool: &PgPool,
name: &str,
config: &T,
) -> Result<(), AppError> {
let json = serde_json::to_string_pretty(config)
.map_err(|e| AppError::Internal(format!("Config serialize error: {}", e)))?;
sqlx::query(
"INSERT INTO site_configs (name, config_json)
VALUES ($1, $2)
ON CONFLICT (name) DO UPDATE SET config_json = $2"
)
.bind(name)
.bind(&json)
.execute(pool)
.await?;
Ok(())
}
9.5 配置模型
#[derive(Debug, Serialize, Deserialize)]
pub struct SiteSettings {
pub title: String,
pub description: String,
pub keywords: Vec<String>,
pub favicon: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct EmailConfig {
pub smtp_host: String,
pub smtp_port: u16,
pub username: String,
pub password: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QiNiuConfig {
pub enabled: bool,
pub access_key: String,
pub secret_key: String,
pub bucket: String,
pub domain: String,
}
9.6 配置 API
async fn get_site_info(
State(pool): State<PgPool>,
Query(params): Query<ConfigQuery>,
) -> Result<Json<ApiResult<serde_json::Value>>, AppError> {
let config = get_site_config::<serde_json::Value>(&pool, ¶ms.name).await?;
match config {
Some(c) => Ok(Json(ApiResult::success(c))),
None => Ok(Json(ApiResult::error(ErrorCode::NOT_FOUND, "Config not found"))),
}
}
async fn update_site_info(
State(pool): State<PgPool>,
Query(params): Query<ConfigQuery>,
Json(body): Json<serde_json::Value>,
) -> Result<Json<ApiResult<()>>, AppError> {
update_site_config(&pool, ¶ms.name, &body).await?;
Ok(Json(ApiResult::success(())))
}
下一章将实现测试、性能优化与生产部署。
rusttracingloggingconfigmanagement