如何记录异步瘦+ sinatra +机架请求?

时间:2011-06-21 14:33:49

标签: asynchronous sinatra rack thin

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

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

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

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

2 个答案:

答案 0 :(得分:2)

我最终发现使用与async_sinatra的rack-async会导致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中间件进行日志记录。

答案 1 :(得分: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")进行日志记录。