Skip to content
This repository has been archived by the owner on Dec 8, 2020. It is now read-only.

Capture all HTTP headers and bodies #59

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/timber/events.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require "timber/events/controller_call"
require "timber/events/custom"
require "timber/events/exception"
require "timber/events/http_client_request"
require "timber/events/http_client_response"
require "timber/events/http_server_request"
require "timber/events/http_server_response"
require "timber/events/sql_query"
Expand Down
60 changes: 60 additions & 0 deletions lib/timber/events/http_client_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module Timber
module Events
# The HTTP client request event tracks *outgoing* HTTP requests giving you structured insight
# into communication with external services.
#
# @note This event should be installed automatically through probes,
# such as the {Probes::NetHTTP} probe.
class HTTPClientRequest < Timber::Event
attr_reader :body, :headers, :host, :method, :path, :port, :query_string, :request_id,
:scheme, :service_name

def initialize(attributes)
@body = Util::HTTPEvent.normalize_body(attributes[:body])
@headers = Util::HTTPEvent.normalize_headers(attributes[:headers])
@host = attributes[:host] || raise(ArgumentError.new(":host is required"))
@method = Util::HTTPEvent.normalize_method(attributes[:method]) || raise(ArgumentError.new(":method is required"))
@path = attributes[:path] || raise(ArgumentError.new(":path is required"))
@port = attributes[:port]
@query_string = Util::HTTPEvent.normalize_query_string(attributes[:query_string])
@request_id = attributes[:request_id]
@scheme = attributes[:scheme] || raise(ArgumentError.new(":scheme is required"))
@service_name = attributes[:service_name]
end

def to_hash
{body: body, headers: headers, host: host, method: method, path: path, port: port,
query_string: query_string, request_id: request_id, scheme: scheme,
service_name: service_name}
end
alias to_h to_hash

def as_json(_options = {})
{:server_side_app => {:http_client_request => to_hash}}
end

def message
message = 'Outgoing HTTP request to '

if service_name
mesage << " #{service_name} [#{method}] #{full_path}"
else
message << " [#{method}] #{full_url}"
end
end

def status_description
Rack::Utils::HTTP_STATUS_CODES[status]
end

private
def full_path
Util::HTTPEvent.full_path(path, query_string)
end

def full_url
"#{scheme}#{host}#{full_path}"
end
end
end
end
46 changes: 46 additions & 0 deletions lib/timber/events/http_client_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Timber
module Events
# The HTTP client response event tracks responses for *outgoing* HTTP *requests*.
# This gives you structured insight into communication with external services.
#
# @note This event should be installed automatically through probes,
# such as the {Probes::NetHTTP} probe.
class HTTPClientResponse < Timber::Event
attr_reader :body, :headers, :request_id, :service_name, :status, :time_ms

def initialize(attributes)
@body = Util::HTTPEvent.normalize_body(attributes[:body])
@headers = Util::HTTPEvent.normalize_headers(attributes[:headers])
@request_id = attributes[:request_id]
@service_name = attributes[:service_name]
@status = attributes[:status] || raise(ArgumentError.new(":status is required"))
@time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
@time_ms = @time_ms.round(6)
end

def to_hash
{body: body, headers: headers, request_id: request_id, service_name: service_name,
status: status, time_ms: time_ms}
end
alias to_h to_hash

def as_json(_options = {})
{:server_side_app => {:http_client_response => to_hash}}
end

def message
message = "Outgoing HTTP response"

if service_name
message << " from #{service_name}"
end

message << " #{status_description} in #{time_ms}ms"
end

def status_description
Rack::Utils::HTTP_STATUS_CODES[status]
end
end
end
end
33 changes: 20 additions & 13 deletions lib/timber/events/http_server_request.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
module Timber
module Events
# The HTTP request event tracks incoming HTTP requests.
# The HTTP server request event tracks incoming HTTP requests to your HTTP server.
# Such as unicorn, webrick, puma, etc.
#
# @note This event should be installed automatically through probes,
# such as the {Probes::ActionControllerLogSubscriber} probe.
class HTTPServerRequest < Timber::Event
attr_reader :host, :method, :path, :port, :query_string, :content_type,
:remote_addr, :referrer, :request_id, :scheme, :user_agent
attr_reader :body, :headers, :host, :method, :path, :port, :query_string, :request_id,
:scheme

def initialize(attributes)
@body = Util::HTTPEvent.normalize_body(attributes[:body])
@headers = Util::HTTPEvent.normalize_headers(attributes[:headers])
@host = attributes[:host] || raise(ArgumentError.new(":host is required"))
@method = attributes[:method] || raise(ArgumentError.new(":method is required"))
@method = Util::HTTPEvent.normalize_method(attributes[:method]) || raise(ArgumentError.new(":method is required"))
@path = attributes[:path] || raise(ArgumentError.new(":path is required"))
@port = attributes[:port]
@query_string = attributes[:query_string]
@content_type = attributes[:content_type]
@remote_addr = attributes[:remote_addr]
@referrer = attributes[:referrer]
@request_id = attributes[:request_id]
@query_string = Util::HTTPEvent.normalize_query_string(attributes[:query_string])
@scheme = attributes[:scheme] || raise(ArgumentError.new(":scheme is required"))
@user_agent = attributes[:user_agent]
@request_id = attributes[:request_id]
end

def to_hash
{host: host, method: method, path: path, port: port, query_string: query_string,
headers: {content_type: content_type, remote_addr: remote_addr, referrer: referrer,
request_id: request_id, scheme: scheme, user_agent: user_agent}}
{body: body, headers: headers, host: host, method: method, path: path, port: port,
query_string: query_string, request_id: request_id, scheme: scheme}
end
alias to_h to_hash

Expand All @@ -43,6 +41,15 @@ def message
def status_description
Rack::Utils::HTTP_STATUS_CODES[status]
end

private
def truncate_body(body)
if body.is_a?(String) && body.length > 2000
body.truncate(2000)
else
body
end
end
end
end
end
10 changes: 7 additions & 3 deletions lib/timber/events/http_server_response.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
module Timber
module Events
# The HTTP response event tracks outgoing HTTP request responses.
# The HTTP server response event tracks outgoing HTTP responses that you send
# to clients.
#
# @note This event should be installed automatically through probes,
# such as the {Probes::ActionControllerLogSubscriber} probe.
class HTTPServerResponse < Timber::Event
attr_reader :status, :time_ms, :additions
attr_reader :body, :headers, :request_id, :status, :time_ms, :additions

def initialize(attributes)
@body = Util::HTTPEvent.normalize_body(attributes[:body])
@headers = Util::HTTPEvent.normalize_headers(attributes[:headers])
@request_id = attributes[:request_id]
@status = attributes[:status] || raise(ArgumentError.new(":status is required"))
@time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
@time_ms = @time_ms.round(6)
@additions = attributes[:additions]
end

def to_hash
{status: status, time_ms: time_ms}
{body: body, headers: headers, request_id: request_id, status: status, time_ms: time_ms}
end
alias to_h to_hash

Expand Down
25 changes: 25 additions & 0 deletions lib/timber/util/http_event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Timber
module Util
module HTTPEvent
def self.full_path(path, query_string)
if query_string
"#{path}?#{query_string}"
else
path
end
end

def self.normalize_body(body)
if body.is_a?(String) && body.length > 2000
body.truncate(2000)
else
body
end
end

def normalize_headers(headers)

end
end
end
end