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 头

客户端

服务端