class Timber::Integrations::Rack::HTTPEvents

A Rack middleware that is reponsible for capturing and logging HTTP server requests and response events. The {Events::HTTPRequest} and {Events::HTTPResponse} events respectively.

Constants

CONTENT_LENGTH_KEY

Public Class Methods

capture_request_body=(value) click to toggle source

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)

If you opt to capture bodies, you can also truncate the size to reduce the data captured. See {Events::HTTPRequest}.

@example

Timber::Integrations::Rack::HTTPEvents.capture_request_body = true
# File lib/timber-rack/http_events.rb, line 30
def capture_request_body=(value)
  @capture_request_body = value
end
capture_request_body?() click to toggle source

Accessor method for {#capture_request_body=}

# File lib/timber-rack/http_events.rb, line 35
def capture_request_body?
  @capture_request_body == true
end
capture_response_body=(value) click to toggle source

Just like {#capture_request_body=} but for the {Events::HTTPResponse} event. Please see {#capture_request_body=} for more details. The documentation there also applies here.

# File lib/timber-rack/http_events.rb, line 42
def capture_response_body=(value)
  @capture_response_body = value
end
capture_response_body?() click to toggle source

Accessor method for {#capture_response_body=}

# File lib/timber-rack/http_events.rb, line 47
def capture_response_body?
  @capture_response_body == true
end
collapse_into_single_event=(value) click to toggle source

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::HTTPResponse} 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::HTTPRequest} event would provide.

@example

Timber::Integrations::Rack::HTTPEvents.collapse_into_single_event = true
# File lib/timber-rack/http_events.rb, line 74
def collapse_into_single_event=(value)
  @collapse_into_single_event = value
end
collapse_into_single_event?() click to toggle source

Accessor method for {#collapse_into_single_event=}.

# File lib/timber-rack/http_events.rb, line 79
def collapse_into_single_event?
  @collapse_into_single_event == true
end
http_body_limit() click to toggle source

Accessor method for {#http_body_limit=}

# File lib/timber-rack/http_events.rb, line 110
def http_body_limit
  @http_body_limit
end
http_body_limit=(value) click to toggle source
# File lib/timber-rack/http_events.rb, line 105
def http_body_limit=(value)
  @http_body_limit = value
end
http_header_filters() click to toggle source

Accessor method for {#http_header_filters=}

# File lib/timber-rack/http_events.rb, line 119
def http_header_filters
  @http_header_filters
end
http_header_filters=(value) click to toggle source
# File lib/timber-rack/http_events.rb, line 114
def http_header_filters=(value)
  @http_header_filters = value
end
silence_request() click to toggle source

Accessor method for {#silence_request=}

# File lib/timber-rack/http_events.rb, line 101
def silence_request
  @silence_request
end
silence_request=(proc) click to toggle source

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](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
# File lib/timber-rack/http_events.rb, line 92
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

Public Instance Methods

call(env) click to toggle source
# File lib/timber-rack/http_events.rb, line 126
def call(env)
  request = Util::Request.new(env)

  if silenced?(env, request)
    if Config.instance.logger.respond_to?(:silence)
      Config.instance.logger.silence do
        @app.call(env)
      end
    else
      @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 = CurrentContext.fetch(:http)
      content_length = headers[CONTENT_LENGTH_KEY]
      duration_ms = (Time.now - start) * 1000.0

      http_response = HTTPResponse.new(
        content_length: content_length,
        headers: headers,
        http_context: http_context,
        request_id: request.request_id,
        status: status,
        duration_ms: duration_ms,
        body_limit: self.class.http_body_limit,
        headers_to_sanitize: self.class.http_header_filters,
      )

      {
        message: http_response.message,
        event: {
          http_response_sent: {
            body: http_response.body,
            content_length: http_response.content_length,
            headers_json: http_response.headers_json,
            request_id: http_response.request_id,
            service_name: http_response.service_name,
            status: http_response.status,
            duration_ms: http_response.duration_ms,
          }
        }
      }
    end

    [status, headers, body]
  else
    start = Time.now

    Config.instance.logger.info do
      event_body = capture_request_body? ? request.body_content : nil
      http_request = HTTPRequest.new(
        body: event_body,
        content_length: request.content_length,
        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,
        scheme: request.scheme,
        body_limit: self.class.http_body_limit,
        headers_to_sanitize: self.class.http_header_filters,
      )

      {
        message: http_request.message,
        event: {
          http_request_received: {
            body: http_request.body,
            content_length: http_request.content_length,
            headers_json: http_request.headers_json,
            host: http_request.host,
            method: http_request.method,
            path: http_request.path,
            port: http_request.port,
            query_string: http_request.query_string,
            request_id: http_request.request_id,
            scheme: http_request.scheme,
            service_name: http_request.service_name,
          }
        }
      }
    end

    status, headers, body = @app.call(env)

    Config.instance.logger.info do
      event_body = capture_response_body? ? body : nil
      content_length = headers[CONTENT_LENGTH_KEY]
      duration_ms = (Time.now - start) * 1000.0

      http_response = HTTPResponse.new(
        body: event_body,
        content_length: content_length,
        headers: headers,
        request_id: request.request_id,
        status: status,
        duration_ms: duration_ms,
        body_limit: self.class.http_body_limit,
        headers_to_sanitize: self.class.http_header_filters,
      )

      {
        message: http_response.message,
        event: {
          http_response_sent: {
            body: http_response.body,
            content_length: http_response.content_length,
            headers_json: http_response.headers_json,
            request_id: http_response.request_id,
            service_name: http_response.service_name,
            status: http_response.status,
            duration_ms: http_response.duration_ms,
          }
        }
      }
    end

    [status, headers, body]
  end
end

Private Instance Methods

capture_request_body?() click to toggle source
# File lib/timber-rack/http_events.rb, line 255
def capture_request_body?
  self.class.capture_request_body?
end
capture_response_body?() click to toggle source
# File lib/timber-rack/http_events.rb, line 259
def capture_response_body?
  self.class.capture_response_body?
end
collapse_into_single_event?() click to toggle source
# File lib/timber-rack/http_events.rb, line 263
def collapse_into_single_event?
  self.class.collapse_into_single_event?
end
silenced?(env, request) click to toggle source
# File lib/timber-rack/http_events.rb, line 267
def silenced?(env, request)
  if !self.class.silence_request.nil?
    self.class.silence_request.call(env, request)
  else
    false
  end
end