如何记录异步 Thin+sinatra+rack 请求?

发布于 2024-11-16 09:01:57 字数 786 浏览 1 评论 0原文

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

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

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

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

I'm writing my first Sinatra-based web app as a frontend to another TCP-based service, using EventMachine and async_sinatra to process incoming HTTP requests asynchronously. When I'm testing my app, all requests to synchronous routes are logged to stdout in common log format, but asynchronous requests are not.

I've read through bits of the source code to async_sinatra, Sinatra, Thin, and Rack, and it looks like logging of synchronous requests is done through CommonLogger#call. However, I can't find anywhere in the asynchronous code in async_sinatra or Thin that seems to pass asynchronous requests through the logging middleware (I'm looking at Sinatra::Helpers#body in async_sinatra and at Thin::Connection.post_process which is written into env['.async_callback'] in Thin's connection.rb:68 and request.rb:132).

I'm experienced with C but relatively new to Ruby, so if I've used some terminology or notation incorrectly, please correct me. Thanks in advance.

Edit: this also affects error handling. If an exception is raised in an asynchronous request, the request is never finished and the error is never logged.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

﹉夏雨初晴づ 2024-11-23 09:01:57

我最终发现将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 中间件进行日志记录。

I eventually found that using rack-async with async_sinatra was causing problems with 404 pages, exception handling, and logging:

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

Instead I used the following wrapper around aroute for logging:

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

This is for the same versions of async_sinatra, Thin, and Rack that I was using when I asked this question last year; newer versions may allow the use of common Rack middleware for logging.

我一直都在从未离去 2024-11-23 09:01:57

我在 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")记录。

I am running on sinatra-synchrony and therefore I have a slightly different core than you.
But basically I solved the same problem.
Here is an abstract of the solution:

  • I am not using Rack::CommonLogger, I use my own Logger
  • You need to buffer log output in an async aware storage
  • The buffered log output must be flushed at the end of the request

In my sinatra-synchrony application I am running the following middleware for logging:

# 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

Everywhere in the application I am able to use Logger.log("message") for logging.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文