Pan*_*nos 5 ruby ruby-on-rails
我有一个 RoR Rest API,我想发出一个指标,其中包含我的 API 的每个响应的状态。我设法在所有情况下都做到了这一点,除了那些控制器崩溃的情况。
例如在 ApplicationController 中使用以下代码:
require 'statsd-ruby'
class ApplicationController < ActionController::API
after_action :push_status_metric
def push_status_metric
statsd = Statsd.new ENV['STATSD_LOCATION'], ENV['STATSD_PORT']
puts normalize_status_metric(response.status)
statsd.increment('ds.status.' + normalize_status_metric(response.status).to_s + '.int') unless request.fullpath == '/health'
end
private
def normalize_status_metric(status)
return 100 if status >= 100 && status < 200
return 200 if status >= 200 && status < 300
return 300 if status >= 300 && status < 400
return 400 if status >= 400 && status < 500
return 500 if status >= 500 && status < 600
0
end
end
Run Code Online (Sandbox Code Playgroud)
但是此解决方案不会捕获诸如 ActionController::RoutingError 和 ActiveRecord::RecordNotFound 之类的错误。
我尝试了以下代码:
rescue_from StandardError do |exception|
statsd = Statsd.new ENV['STATSD_LOCATION'], ENV['STATSD_PORT']
statsd.increment('ds.status.' + normalize_status_metric(response.status).to_s + '.int') unless request.fullpath == '/health'
raise exception
end
Run Code Online (Sandbox Code Playgroud)
但是当这个回调被执行时,response.status 的值总是 200(好像到目前为止框架还没有设置它)。
知道 rails 记录器设法做到这一点,我们可以看看它的类,ActionController::LogSubscriber以及那个process_action方法。因此,该事件中的状态可能是nil,如果存在异常,我们可以看到它们如何将异常转换为状态:
status = payload[:status]
if status.nil? && payload[:exception].present?
exception_class_name = payload[:exception].first
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
Run Code Online (Sandbox Code Playgroud)
所以现在,我们可以通过创建初始化程序,使用Active Support Instrumentation自己订阅此事件来做类似的事情:
ActiveSupport::Notifications.subscribe 'process_action.action_controller' do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
# opening a file here is probably a performance nightmare, but you'd be doing something with statsd, not a file, anyway
open('metrics.txt', 'a') do |f|
# get the action status, this code is from the ActionController::LogSubscriber
status = event.payload[:status]
if status.nil? && event.payload[:exception].present?
exception_class_name = event.payload[:exception].first
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
f.puts "process_action.action_controller controller: #{event.payload[:controller]} - action: #{event.payload[:action]} - path: #{event.payload[:path]} - status: #{status}"
end
end
Run Code Online (Sandbox Code Playgroud)
并用一个简单的控制器点击几次:
class HomeController < ApplicationController
def non_standard_status
render html: 'This is not fine', status: :forbidden
end
def error
raise ActiveRecord::RecordNotFound, 'No Records Found'
end
def another_error
raise ArgumentError, 'Some Argument is wrong'
end
def this_is_fine
render html: 'This is fine'
end
end
Run Code Online (Sandbox Code Playgroud)
产生一个文件:
process_action.action_controller controller: HomeController - action: non_standard_status - path: /forbidden - status: 403
process_action.action_controller controller: HomeController - action: error - path: /error - status: 404
process_action.action_controller controller: HomeController - action: another_error - path: /error2 - status: 500
process_action.action_controller controller: HomeController - action: this_is_fine - path: /fine - status: 200
Run Code Online (Sandbox Code Playgroud)