Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions lib/logtail-rack/http_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ module Rack
# response events. The {Events::HTTPRequest} and {Events::HTTPResponse} events
# respectively.
class HTTPEvents < Middleware
DEFAULT_HTTP_HEADER_FILTERS = ["Authorization", "Proxy-Authorization", "Cookie", "Set-Cookie"].freeze

class << self
# Allows you to capture the HTTP request body, default is off (false).
#
Expand Down Expand Up @@ -107,8 +109,11 @@ def silence_request
#
# Filtered HTTP header values will be sent to Better Stack as "[FILTERED]"
#
# {DEFAULT_HTTP_HEADER_FILTERS} are filtered out of the box. Setting this replaces
# the whole list, pass an empty list to log every header.
#
# @example
# Logtail::Integrations::Rack::HTTPEvents.http_header_filters = ["Authorization"]
# Logtail::Integrations::Rack::HTTPEvents.http_header_filters = Logtail::Integrations::Rack::HTTPEvents::DEFAULT_HTTP_HEADER_FILTERS + ["X-Api-Key"]
def http_header_filters=(value)
@http_header_filters = value.map { |header_name| normalize_header_name(header_name) }
end
Expand All @@ -123,6 +128,8 @@ def normalize_header_name(name)
end
end

self.http_header_filters = DEFAULT_HTTP_HEADER_FILTERS

CONTENT_LENGTH_KEY = 'Content-Length'.freeze

def call(env)
Expand Down Expand Up @@ -271,7 +278,7 @@ def silenced?(env, request)
def filter_http_headers(headers)
headers.map do |name, value|
normalized_name = self.class.normalize_header_name(name)
is_filtered = self.class.http_header_filters&.include?(normalized_name)
is_filtered = self.class.http_header_filters.include?(normalized_name)
[name, is_filtered ? "[FILTERED]" : value]
end.to_h
end
Expand Down
30 changes: 27 additions & 3 deletions spec/logtail-rack/http_events_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,33 @@
expect(logs.map { |log| log['message'] }).to match(['Started GET "/test-page"', /Completed 200 OK in \d+\.\d+ms/])
end

it "log HTTP request headers" do
it "log HTTP request headers, filtering the Authorization header by default" do
logs = capture_logs { middleware.call mock_request }

request_headers_json = logs.first["event"]["http_request_received"]["headers_json"]
expect(JSON.parse(request_headers_json)).to eq({"Authorization" => "[FILTERED]", "Content_Type" => "text/plain"})
end

it "filter credential headers in the request and the response by default" do
app = ->(env) { [200, { "Content-Type" => "text/plain", "Set-Cookie" => "session=abc" }, "app"] }
request = Rack::MockRequest.env_for('https://example.com/test-page', {
'HTTP_AUTHORIZATION' => 'Bearer secret_token',
'HTTP_PROXY_AUTHORIZATION' => 'Basic cHJveHk6c2VjcmV0',
'HTTP_COOKIE' => 'session=abc',
'HTTP_CONTENT_TYPE' => 'text/plain',
})

logs = capture_logs { described_class.new(app).call request }

request_headers_json = logs.first["event"]["http_request_received"]["headers_json"]
expect(JSON.parse(request_headers_json)).to eq({"Authorization" => "[FILTERED]", "Proxy_Authorization" => "[FILTERED]", "Cookie" => "[FILTERED]", "Content_Type" => "text/plain"})
response_headers_json = logs.last["event"]["http_response_sent"]["headers_json"]
expect(JSON.parse(response_headers_json)).to eq({"Content-Type" => "text/plain", "Set-Cookie" => "[FILTERED]"})
end

it "log every header when http_header_filters is set to an empty list" do
logs = capture_logs { with_http_header_filters([]) { middleware.call mock_request } }

request_headers_json = logs.first["event"]["http_request_received"]["headers_json"]
expect(JSON.parse(request_headers_json)).to eq({"Authorization" => "Bearer secret_token", "Content_Type" => "text/plain"})
end
Expand Down Expand Up @@ -61,10 +85,10 @@ def capture_logs(&blk)
end

def with_http_header_filters(headers, &blk)
previous_http_header_filters = Logtail::Integrations::Rack::HTTPEvents.http_header_filters = headers
Logtail::Integrations::Rack::HTTPEvents.http_header_filters = headers

blk.call
ensure
Logtail::Integrations::Rack::HTTPEvents.http_header_filters = previous_http_header_filters
Logtail::Integrations::Rack::HTTPEvents.http_header_filters = Logtail::Integrations::Rack::HTTPEvents::DEFAULT_HTTP_HEADER_FILTERS
end
end
Loading