lib/timber/integrations/rack/http_events.rb in timber-2.0.24 vs lib/timber/integrations/rack/http_events.rb in timber-2.1.0.rc1

- old
+ new

@@ -1,44 +1,194 @@ +require "set" + +require "timber/integrations/rack/middleware" + module Timber module Integrations module Rack - # Reponsible for capturing and logging HTTP server requests and response events. - class HTTPEvents - def initialize(app) - @app = app + # A Rack middleware that is reponsible for capturing and logging HTTP server requests and + # response events. The {Events::HTTPServerRequest} and {Events::HTTPServerResponse} events + # respectively. + class HTTPEvents < Middleware + class << self + # Allows you to capture the HTTP request body, default is off (false). + # + # Capturing HTTP bodies can be extremely helpful when debugging issues, + # but please proceed with caution: + # + # 1. Capturing HTTP bodies can use quite a bit of data (this can be mitigated, see below) + # 2. The {Events::ControllerCall} event captures the parsed parmaters sent to + # the controller. This is a parsed representation of the body, which is usually more + # helpful and redundant to the body captured here. + # + # If you opt to capture bodies, you can also truncate the size to reduce the data + # captured. See {Events::HTTPServerRequest}. + # + # @example + # Timber::Integrations::Rack::HTTPEvents.capture_request_body = true + def capture_request_body=(value) + @capture_request_body = value + end + + # Accessor method for {#capture_request_body=} + def capture_request_body? + @capture_request_body == true + end + + # Just like {#capture_request_body=} but for the {Events::HTTPServerResponse} event. + # Please see {#capture_request_body=} for more details. The documentation there also + # applies here. + def capture_response_body=(value) + @capture_response_body = value + end + + # Accessor method for {#capture_response_body=} + def capture_response_body? + @capture_response_body == true + end + + # Collapse both the HTTP request and response events into a single log line event. + # While we don't recommend this, it can help to reduce log volume if desired. + # The reason we don't recommend this, is because the logging service you use should + # not be so expensive that you need to strip out useful logs. It should also provide + # the tools necessary to properly search your logs and reduce noise. Such as viewing + # logs for a specific request. + # + # To provide an example. This setting turns this: + # + # Started GET "/" for 127.0.0.1 at 2012-03-10 14:28:14 +0100 + # Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) + # + # Into this: + # + # Get "/" sent 200 OK in 79ms + # + # The single event is still a {Timber::Events::HTTPServerResponse} event. Because + # we capture HTTP context, you still get the HTTP details, but you will not get + # all of the request details that the {Timber::Events::HTTPServerRequest} event would + # provide. + # + # @example + # Timber::Integrations::Rack::HTTPEvents.collapse_into_single_event = true + def collapse_into_single_event=(value) + @collapse_into_single_event = value + end + + # Accessor method for {#collapse_into_single_event=}. + def collapse_into_single_event? + @collapse_into_single_event == true + end + + # This setting allows you to silence requests based on any conditions you desire. + # We require a block because it gives you complete control over how you want to + # silence requests. The first parameter being the traditional Rack env hash, the + # second being a [Rack Request](http://www.rubydoc.info/gems/rack/Rack/Request) object. + # + # @example + # Integrations::Rack::HTTPEvents.silence_request = lambda do |rack_env, rack_request| + # rack_request.path == "/_health" + # end + def silence_request=(proc) + if proc && !proc.is_a?(Proc) + raise ArgumentError.new("The value passed to #silence_request must be a Proc") + end + + @silence_request = proc + end + + # Accessor method for {#silence_request=} + def silence_request + @silence_request + end end def call(env) - start = Time.now request = Util::Request.new(env) - Config.instance.logger.info do - Events::HTTPServerRequest.new( - headers: request.headers, - host: request.host, - method: request.request_method, - path: request.path, - port: request.port, - query_string: request.query_string, - request_id: request.request_id, # we insert this middleware after ActionDispatch::RequestId - scheme: request.scheme - ) + if silenced?(env, request) + Config.instance.logger.silence do + @app.call(env) + end + + elsif collapse_into_single_event? + start = Time.now + + status, headers, body = @app.call(env) + + Config.instance.logger.info do + http_context_key = Contexts::HTTP.keyspace + http_context = CurrentContext.fetch(http_context_key) + time_ms = (Time.now - start) * 1000.0 + + Events::HTTPServerResponse.new( + headers: headers, + http_context: http_context, + request_id: request.request_id, + status: status, + time_ms: time_ms + ) + end + + [status, headers, body] + + else + start = Time.now + + Config.instance.logger.info do + event_body = capture_request_body? ? request.body_content : nil + + Events::HTTPServerRequest.new( + body: event_body, + headers: request.headers, + host: request.host, + method: request.request_method, + path: request.path, + port: request.port, + query_string: request.query_string, + request_id: request.request_id, # we insert this middleware after ActionDispatch::RequestId + scheme: request.scheme + ) + end + + status, headers, body = @app.call(env) + + Config.instance.logger.info do + time_ms = (Time.now - start) * 1000.0 + event_body = capture_response_body? ? body : nil + + Events::HTTPServerResponse.new( + body: event_body, + headers: headers, + request_id: request.request_id, + status: status, + time_ms: time_ms + ) + end + + [status, headers, body] end + end - status, headers, body = @app.call(env) + private + def capture_request_body? + self.class.capture_request_body? + end - Config.instance.logger.info do - time_ms = (Time.now - start) * 1000.0 - Events::HTTPServerResponse.new( - headers: headers, - request_id: request.request_id, - status: status, - time_ms: time_ms - ) + def capture_response_body? + self.class.capture_response_body? end - [status, headers, body] - end + def collapse_into_single_event? + self.class.collapse_into_single_event? + end + + def silenced?(env, request) + if !self.class.silence_request.nil? + self.class.silence_request.call(env, request) + else + false + end + end end end end end \ No newline at end of file