actix-web 日志组件

0x00 目标

在actix-web提供的api服务中,打印请求id和时耗信息。
输出目标为控制台输出,便于在K8s部署时采集到其他日志平台。

0x01 代码

  • Cargo.toml
[package]
name = "trace_poc"
version = "0.1.0"
edition = "2024"

[dependencies]
trace = "0.1.7"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
log = "0.4.28"
tracing = "0.1.41"
actix-web = "4.11.0"
futures-util = "0.3.31"
uuid = { version = "1.18.1", features = ["v4"] }
tracing-appender = "0.2.3"
  • main.rs
    日志初始化,需要在程序开始时执行
mod middleware_log;

use crate::middleware_log::SayHi;
use actix_web::{App, HttpServer, web};
use log::info;
use tracing::{event, span};
use tracing_appender::non_blocking;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, fmt};

async fn manual_hello() -> &'static str {
    info!("in request : hello");
    "Hello world!"
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let (non_blocking_stdout, _guard) = non_blocking(std::io::stdout());

    tracing_subscriber::registry()
        .with(
            fmt::layer()
                .with_writer(non_blocking_stdout)
                .with_file(true)
                .with_line_number(true),
        )
        .with(EnvFilter::new("info".to_string()))
        .init();

    HttpServer::new(|| {
        App::new()
            .wrap(SayHi)
            .route("/hey", web::get().to(manual_hello))
    })
    .bind(("127.0.0.1", 8088))?
    .run()
    .await
}

  • middleware_log.rs
    基于官方中间件样例代码,配置日志输出格式
    1、针对每一个请求,配置一个span(包含特定的请求id)
    2、执行完成后,打印处理耗时。
    3、把请求id返回给响应头
use actix_web::http::header::HeaderName;
use actix_web::{
    Error, HttpMessage,
    dev::{Service, ServiceRequest, ServiceResponse, Transform, forward_ready},
};
use futures_util::future::LocalBoxFuture;
use log::info;
use std::future::{Ready, ready};
use std::str::FromStr;
use std::time::Instant;
use tracing::info_span;
use uuid::uuid;

// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
//    next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct SayHi;

// Middleware factory is `Transform` trait
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for SayHi
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = SayHiMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(SayHiMiddleware { service }))
    }
}

pub struct SayHiMiddleware<S> {
    service: S,
}

impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        // 优先从请求头获取 x-request-id,如果没有则生成 UUID
        let span_id = req
            .headers()
            .get("x-request-id")
            .and_then(|h| h.to_str().ok())
            .map(|s| s.to_string())
            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());

        let span = info_span!("span",
            request_id = % span_id,
            uri = %req.uri(),
            method = %req.method(),
            remote_ip = %req.connection_info().peer_addr().unwrap_or("<unknown>"),
        );

        let start_time = Instant::now();
        let fut = self.service.call(req);
        Box::pin(async move {
            let _enter = span.enter();
            let mut res = fut.await?;
            res.headers_mut().insert(
                HeaderName::from_str("x-request-id").unwrap(),
                span_id.parse().unwrap(),
            );

            let duration = start_time.elapsed();
            info!("处理时长: {:?}微秒", duration.as_micros());
            Ok(res)
        })
    }
}

0x02 效果图

实现两个请求效果:

  • 请求时不带 x-request-id ,则服务端生成一个。
  • 使用请求侧自定义的 x-request-id 头
客户端
服务端
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容