You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Tonic服务器的Tower Layer中监控请求响应字节大小?

Tonic服务器添加端点收发字节数监控的实现问题

我有一个Tonic服务器,已经实现了用于监控请求延迟的Tower Layer(代码摘要如下)。现在需要给每个端点添加接收/发送字节数的监控。

RequestBody最终会实现prost::Message,因此具备encoded_len方法,但我不确定该方法是否需要消费消息才能获取长度。我考虑过几种方案,但都有问题:

  • 理论上可以修改tonic::Request::into_inner()来记录长度,但这并非最优解;
  • 也可以在客户端添加日志,但我希望避免这种方式;
  • 我期望在同一个Layer中同时记录请求和响应大小,但不知道如何借助现有的延迟监控工具实现。

现有延迟监控Layer代码

impl<T, RequestBody, ResponseBody: 'static> Service<tonic::codegen::http::Request<RequestBody>>
    for EndpointTimer<T>
where
    T: Service<
            tonic::codegen::http::Request<RequestBody>,
            Response = tonic::codegen::http::Response<ResponseBody>,
        > + Send,
    T::Future: Send + 'static,
    T::Error: Into<Box<dyn Error + Send + Sync>> + 'static,
    T::Response: 'static,
{
    type Response = T::Response;
    type Error = Box<dyn Error + Send + Sync>;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

    fn poll_ready(...) { ... }

    fn call(&mut self, req: tonic::codegen::http::Request<RequestBody>) -> Self::Future {
        let start = Instant::now();
        let future = self.inner.call(req);
        let timed_future = async move {
            let res = future.await;
            // ... 计时并记录延迟时长
            res.map_err(|err| err.into())
        };
        Box::pin(timed_future)
    }
}

// 用于包装服务的EndpointTimer层
#[derive(Clone)]
pub struct EndpointTimerLayer();

impl EndpointTimerLayer {
    pub fn new() -> Self {
        EndpointTimerLayer()
    }
}

impl<S> Layer<S> for EndpointTimerLayer {
    type Service = EndpointTimer<S>;

    fn layer(&self, service: S) -> EndpointTimer<S> {
        EndpointTimer::new(service)
    }
}

编辑补充

我进一步研究后认为这可能无法实现。生成的服务器代码(位于target目录)如下:

impl<T, B> tonic::codegen::Service<http::Request<B>> for MyServer<T>
    where [...]
    {
        [...]
        fn call(&mut self, req: http::Request<B>) -> Self::Future {
            [...]
            match req.uri().path() {
                "/my_service.MyServiceProvider/MyEndpoint" => {
                    [...]
                    let fut = async move {
                        [...]
                        let res = grpc.unary(method, req).await;
                        Ok(res)
                    };
                    Box::pin(fut)
                }

我曾考虑重载http::Request的into_parts方法,将请求大小发送到Prometheus,因为此时请求体是tonic::Request,它具备into_inner方法,可返回拥有encoded_len()的prost::Message。但因http::Request::into_parts和tonic::Request::into_inner都不是trait实现,我认为这不可行。


内容的提问来源于stack exchange,提问作者gmoss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 19:45:31