【问题标题】:How do I log asynchronous thin+sinatra+rack requests?如何记录异步瘦+sinatra+机架请求?
【发布时间】:2011-06-21 14:33:48
【问题描述】:
我正在编写我的第一个基于 Sinatra 的 Web 应用程序作为另一个基于 TCP 的服务的前端,使用 EventMachine 和 async_sinatra 异步处理传入的 HTTP 请求。当我测试我的应用程序时,对同步路由的所有请求都以通用日志格式记录到标准输出,但异步请求不是。
我已经阅读了 async_sinatra、Sinatra、Thin 和 Rack 的源代码,看起来同步请求的日志记录是通过 CommonLogger#call 完成的。但是,我在 async_sinatra 或 Thin 的异步代码中找不到任何似乎通过日志中间件传递异步请求的地方(我正在查看 async_sinatra 中的 Sinatra::Helpers#body 和写入 env['. async_callback'] 在 Thin 的 connection.rb:68 和 request.rb:132)。
我有 C 经验,但对 Ruby 比较陌生,所以如果我使用了一些不正确的术语或符号,请纠正我。提前致谢。
编辑:这也会影响错误处理。如果在异步请求中引发异常,则该请求永远不会完成并且永远不会记录错误。
【问题讨论】:
标签:
asynchronous
sinatra
rack
thin
【解决方案1】:
我最终发现将 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 中间件进行日志记录。
【解决方案2】:
我在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") 进行日志记录。