导读:本期聚焦于毕达哥创作的《如何在Ruby中实现gRPC拦截器实现认证、日志与链路追踪?》,敬请观看详情。gRPC的拦截器机制类似于Web框架中的中间件,可以在请求进入业务逻辑之前统一处理认证鉴权、访问日志和链路追踪这类横切关注点。本文围绕Ruby版本的grpc gem,详细讲解ServerInterceptor与ClientInterceptor的编写方式,包括如何从元数据中提取token并校验、如何在拦截器中记录请求耗时与方法名,以及如何结合OpenTelemetry为每次调用注入trace上下文。文中给出可直接复用的完整代码示例,并分析注册多个拦截器时的执行顺序、异常处理和常见坑位,帮助你在Rails或纯Ruby服务中快速落地一套通用的gRPC治理逻辑。

在微服务架构里,gRPC已经成为内部服务通信的主流选择之一。Ruby官方的grpc gem虽然不像Go或Java那样开箱提供丰富的中间件生态,但它提供了GRPC::ServerInterceptor和GRPC::ClientInterceptor两个基类,只要掌握正确的写法,就能把认证、日志、链路追踪这些与业务无关的逻辑统一收敛到拦截器中,让业务代码保持干净。本文将从拦截器的基本原理讲起,逐步实现三个可复用的拦截器,并讨论多拦截器的执行顺序与异常处理。

如何在Ruby中实现gRPC拦截器实现认证、日志与链路追踪?

一、Ruby中gRPC拦截器的工作原理

grpc gem中的拦截器设计借鉴了Rack中间件的思想。服务端拦截器继承GRPC::ServerInterceptor,重写request_response、client_streamer、server_streamer和bidi_streamer四个方法,分别对应一元调用、客户端流、服务端流和双向流四种RPC模式。每个方法内部通过yield把控制权交给真正的RPC实现,因此你可以在yield之前做前置处理,在yield之后做后置处理。

客户端拦截器的思路类似,继承GRPC::ClientInterceptor后重写request_response等方法,通过yield触发实际的远程调用。需要注意的是,元数据(metadata)是在调用时传入的字典,拦截器可以在这里注入认证头或trace上下文。服务端注册拦截器的写法是在GRPC::RpcServer初始化时传入interceptors数组,客户端则在构造stub时通过interceptors:参数传入。

一个容易踩的坑是:如果只重写了request_response,流式RPC将完全绕过你的拦截逻辑。所以在编写通用拦截器时,建议把四个方法都委托到同一个处理函数,确保任何调用模式都不会遗漏。

二、实现认证拦截器

认证是最常见的横切逻辑。客户端在每次调用时把token放进metadata,服务端拦截器提取并校验,校验失败直接抛出GRPC::BadStatus的子类,比如GRPC::PermissionDenied,调用方会收到标准的状态码,无需感知服务端的实现细节。

先看客户端拦截器,它负责在每次调用前注入认证头:

require 'grpc'

class AuthClientInterceptor < GRPC::ClientInterceptor
  def initialize(token:, **kwargs)
    @token = token
    super(**kwargs)
  end

  def request_response(call:, method:, request:, metadata: {})
    metadata['authorization'] = "Bearer #{@token}"
    yield
  end

  def client_streamer(call:, method:, request:, metadata: {})
    metadata['authorization'] = "Bearer #{@token}"
    yield
  end

  def server_streamer(call:, method:, request:, metadata: {})
    metadata['authorization'] = "Bearer #{@token}"
    yield
  end

  def bidi_streamer(call:, method:, requests:, metadata: {})
    metadata['authorization'] = "Bearer #{@token}"
    yield
  end
end

服务端拦截器则提取token并做校验,这里以一个简单的HMAC签名校验为例:

require 'grpc'

class AuthServerInterceptor < GRPC::ServerInterceptor
  def initialize(secret:)
    @secret = secret
    super()
  end

  def request_response(call:)
    authenticate(call)
    yield
  end

  # 其余三种模式同样调用 authenticate 后 yield

  private

  def authenticate(call)
    token = call.metadata['authorization'].to_s.sub(/\ABearer\s+/, '')
    raise GRPC::Unauthenticated.new('invalid token') unless valid_token?(token)
  end

  def valid_token?(token)
    expected = OpenSSL::HMAC.hexdigest('SHA256', @secret, 'grpc-demo')
    ActiveSupport::SecurityUtils.secure_compare(token, expected)
  end
end

校验失败抛出GRPC::Unauthenticated后,grpc会自动终止本次调用并返回标准的INVALID_ARGUMENT语义错误码,客户端拿到的就是一个普通的GRPC::BadStatus异常,处理方式与业务异常保持一致。如果服务里存在公开方法(比如健康检查),可以在拦截器内维护一个白名单,根据call.method路径跳过校验。

三、实现日志拦截器

日志拦截器的目标是在不侵入业务代码的前提下记录方法名、对端信息、耗时和结果状态。关键点是利用Ruby的Process.clock_gettime计算单调时钟耗时,避免使用Time.now受系统时间跳变影响。

require 'grpc'
require 'json'
require 'time'

class LoggingInterceptor < GRPC::ServerInterceptor
  def initialize(logger:)
    @logger = logger
    super()
  end

  %i[request_response client_streamer server_streamer bidi_streamer].each do |mode|
    define_method(mode) do |call:, **kwargs, &block|
      started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
      begin
        result = block.call
        status = 'ok'
        result
      rescue GRPC::BadStatus => e
        status = e.code.to_s
        raise
      ensure
        elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000).round(2)
        @logger.info(JSON.generate(
          rpc: call.method,
          peer: call.peer,
          status: status,
          duration_ms: elapsed_ms
        ))
      end
    end
  end
end

这里用define_method配合元编程把四种RPC模式统一处理,代码更紧凑。日志输出采用JSON格式,方便接入ELK或Loki等日志平台做结构化检索。要注意ensure块中必须返回result或者保证异常继续向上抛出,否则可能吞掉异常或者返回nil,导致客户端收到莫名其妙的空响应。

四、实现链路追踪拦截器

链路追踪的核心是上下文传播:客户端拦截器把当前trace上下文序列化后写入metadata的traceparent字段,服务端拦截器解析该字段并继续当前span,从而把跨服务的调用串联成一条完整链路。借助OpenTelemetry的Ruby SDK,实现并不复杂。

require 'grpc'
require 'opentelemetry-api'
require 'opentelemetry/sdk'

# 客户端:注入 trace 上下文
class TraceClientInterceptor < GRPC::ClientInterceptor
  def request_response(call:, method:, request:, metadata:)
    OpenTelemetry.trace.with_span(
      OpenTelemetry.tracer_provider.tracer('grpc-client').start_span(method)
    ) do |span|
      OpenTelemetry.propagation.inject(metadata)
      yield
    end
  end
end

# 服务端:提取 trace 上下文并延续链路
class TraceServerInterceptor < GRPC::ServerInterceptor
  def request_response(call:)
    ctx = OpenTelemetry.propagation.extract(call.metadata)
    OpenTelemetry.context.with_context(ctx) do
      span = OpenTelemetry.tracer_provider.tracer('grpc-server')
                                .start_span(call.method, kind: :server)
      begin
        yield
      ensure
        span.finish
      end
    end
  end
end

配置好exporter之后,在Jaeger或Zipkin的界面上就能看到客户端span与服务端span通过同一个trace_id串联。如果不想引入完整的OpenTelemetry依赖,也可以手动生成一个UUID塞进x-request-id,日志拦截器读取同一个字段并在日志中输出,这是一种轻量级的准链路追踪方案,适合起步阶段。

五、注册多个拦截器与执行顺序

实际项目中往往需要同时挂载多个拦截器。服务端的注册方式如下:

server = GRPC::RpcServer.new(
  interceptors: [
    TraceServerInterceptor.new,
    AuthServerInterceptor.new(secret: ENV['HMAC_SECRET']),
    LoggingInterceptor.new(logger: Rails.logger)
  ]
)

拦截器按数组顺序依次包裹,效果类似洋葱模型:排在前面的拦截器外层逻辑先执行,后置逻辑后执行。所以上面的顺序意味着trace最先开始记录(能覆盖认证失败的场景),认证在业务日志之前完成(日志能区分鉴权失败的请求)。建议把追踪放在最外层、认证放中间、日志放最内层靠近业务代码,这样观测数据的完整性最好。

另一个实践建议是给拦截器写单元测试。grpc提供了GRPC::Testing相关工具,也可以直接在进程内起一个本地server配合stub做集成测试,验证认证失败、正常调用、流式调用等场景下拦截器的行为。只要保持拦截器职责单一、通过metadata传递上下文,这套模式就能平滑地扩展到限流、幂等控制等其他治理逻辑上。

RubygRPC拦截器链路追踪修改时间:2026-09-16 16:26:43

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0916/58052.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。