第九章:日志系统与配置管理

博客v1.0系列教程(Rust)博客 v1.0 系列教程 (Rust)

9.1 Tracing 日志系统

使用 tracingtracing-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, &params.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, &params.name, &body).await?;
    Ok(Json(ApiResult::success(())))
}

下一章将实现测试、性能优化与生产部署。

rusttracingloggingconfigmanagement