4

我正在编写我的第一个基于 Sinatra 的 Web 应用程序作为另一个基于 TCP 的服务的前端,使用 EventMachine 和 async_sinatra 异步处理传入的 HTTP 请求。当我测试我的应用程序时,对同步路由的所有请求都以通用日志格式记录到标准输出,但异步请求不是。

我已经阅读了 async_sinatra、Sinatra、Thin 和 Rack 的源代码,看起来同步请求的日志记录是通过 CommonLogger#call 完成的。但是,我在 async_sinatra 或 Thin 的异步代码中找不到任何似乎通过日志中间件传递异步请求的地方(我正在查看async_sinatra 和Thin::Connection.post_process中的Sinatra::Helpers#body和 Thin::Connection.post_process写入 Thin 的 connection.rb:68 和 request.rb:132 中的 env['.async_callback'] 中)。

我对 C 有经验,但对 Ruby 比较陌生,所以如果我错误地使用了一些术语或符号,请纠正我。提前致谢。

编辑:这也会影响错误处理。如果在异步请求中引发异常,则请求永远不会完成,并且永远不会记录错误。

4

2 回答 2

2

我最终发现将 rack-async 与 async_sinatra 一起使用会导致 404 页面、异常处理和日志记录出现问题:

!! Unexpected error while processing request: undefined method `bytesize' for nil:NilClass

相反,我使用以下包装器aroute进行日志记录:

module Sinatra::Async
    alias :oldaroute :aroute
    def aroute verb, path, opts = {}, &block
        # Based on aroute from async_sinatra

        run_method = :"RunA#{verb} #{path} #{opts.hash}"
        define_method run_method, &block

        log_method = :"LogA#{verb} #{path} #{opts.hash}"
        define_method(log_method) { |*a|
            puts "#{request.ip} - #{status} #{verb} #{path}"
        }

        oldaroute verb, path, opts do |*a|
            oldcb = request.env['async.callback']
            request.env['async.callback'] = proc { |*args|
                async_runner(log_method, *a)
                oldcb[*args]
            }
            async_runner(run_method, *a)
        end
    end
end

这适用于我去年问这个问题时使用的相同版本的 async_sinatra、Thin 和 Rack;较新的版本可能允许使用通用 Rack 中间件进行日志记录。

于 2012-11-11T12:41:16.380 回答
1

我正在运行sinatra-synchrony,因此我的核心与你略有不同。但基本上我解决了同样的问题。以下是解决方案的摘要:

  • 我没有使用Rack::CommonLogger,我使用自己的 Logger
  • 您需要在异步感知存储中缓冲日志输出
  • 缓冲的日志输出必须在请求结束时刷新

在我的sinatra-synchrony应用程序中,我正在运行以下中间件进行日志记录:

# in app.rb I register Logger::Middleware as the first middleware
use Logger::Middleware
# in logger.rb
module Logger
  attr_accessor :messages

  def log(message)
    stack << message
  end

  def stack
    # This is the important async awareness
    # It stores messages for each fiber separately
    messages[Fiber.current.object_id] ||= []
  end

  def flush
    STDERR.puts stack.join("\n") unless stack.empty?
    messages.delete Fiber.current.object_id
  end
  extend self

  class Middleware
    def initialize(app)
      @app = app
    end

    def call(env)
      # before the request
      Logger.log "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']}"
      result = @app.call(env)
      # after the request
      Logger.flush
      result
    end
  end
end
Logger.messages = {} # initialize the message storage

应用程序中的任何地方我都可以Logger.log("message")用于日志记录。

于 2011-12-20T13:03:55.723 回答