Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log request headers #1839

Merged
merged 9 commits into from
Feb 25, 2023
Merged
12 changes: 8 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

## dev

Upcoming version removes Distributed Tracing warnings from agent logs when using Sidekiq.
Upcoming version removes Distributed Tracing warnings from agent logs when using Sidekiq and fixes a bug regarding logged request headers.


- **Removes Distributed Tracing related warnings from agent logs when headers are not present in Sidekiq**
- **Bugfix: Removes Distributed Tracing related warnings from agent logs when headers are not present in Sidekiq**

Previously, the agent would log a warning to `newrelic_agent.log` every time it attempted to accept empty Distributed Tracing headers from Sidekiq jobs which could result in an excessive number of warnings. Now the agent will no longer create these warnings when using Sidekiq. [PR#1834](https://github.com/newrelic/newrelic-ruby-agent/pull/1834)

- **Bugfix: Log request headers in debug-level logs instead of human-readable Objects**

Previously, the agent sometimes received children of the `NewRelic::Agent::HTTPClients::AbstractRequest` class as an argument when `NewRelic::Agent::Transaction::DistributedTracers#log_request_headers` was called. This caused debug-level log messages that print the request headers to show human-readable Objects (ex. `#<NewRelic::Agent::HTTPClients::HTTPClientRequest:0x00007fd0dda983e0>`) instead of the request headers. Now, the hash of the request headers should always be logged. [PR#1839](https://github.com/newrelic/newrelic-ruby-agent/pull/1839)

## v9.0.0

Version 9.0.0 of the agent removes several deprecated configuration options and API methods, enables Thread tracing by default, adds Fiber instrumentation, removes support for Ruby versions 2.2 and 2.3, removes instrumentation for several deprecated gems, changes how the API method `set_transaction_name` works, and updates `rails_defer_initialization` to be an environment variable only configuration option.
Expand Down Expand Up @@ -101,8 +105,8 @@
- HttpClient: 2.2.0 - 2.8.0
- HttpRb: 0.9.9 - 2.2.1
- Typhoeus: 0.5.3 - 1.2.x
- Bunny: 2.0.x - 2.6.x
- ActiveMerchant: 1.25.0 - 1.64.x
- Bunny: 2.0.x - 2.6.x
- ActiveMerchant: 1.25.0 - 1.64.x


- **Updated API method `set_transaction_name`**
Expand Down
26 changes: 7 additions & 19 deletions lib/new_relic/agent/http_clients/abstract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,16 @@ module HTTPClients
#
# @api public
class AbstractRequest
def []
raise NotImplementedError, MUST_IMPLEMENT_ERROR % [self.class, __method__]
end

def []=
raise NotImplementedError, MUST_IMPLEMENT_ERROR % [self.class, __method__]
end

def type
raise NotImplementedError, MUST_IMPLEMENT_ERROR % [self.class, __method__]
end

def host_from_header
raise NotImplementedError, MUST_IMPLEMENT_ERROR % [self.class, __method__]
%i[[] []= type host_from_header host method headers uri].each do |name|
define_method(name) do
not_implemented(name)
end
end

def host
raise NotImplementedError, MUST_IMPLEMENT_ERROR % [self.class, __method__]
end
private

def method
raise NotImplementedError, MUST_IMPLEMENT_ERROR % [self.class, __method__]
def not_implemented(method_name)
raise NotImplementedError, MUST_IMPLEMENT_ERROR % [self.class, method_name]
end
kaylareopelle marked this conversation as resolved.
Show resolved Hide resolved
end

Expand Down
8 changes: 6 additions & 2 deletions lib/new_relic/agent/http_clients/curb_wrappers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,20 @@ def method
end

def [](key)
@curlobj.headers[key]
headers[key]
end

def []=(key, value)
@curlobj.headers[key] = value
headers[key] = value
end

def uri
@uri ||= URIUtil.parse_and_normalize_url(@curlobj.url)
end

def headers
@curlobj.headers
end
end

class CurbResponse < AbstractResponse
Expand Down
10 changes: 6 additions & 4 deletions lib/new_relic/agent/http_clients/excon_wrappers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def type
end

def host_from_header
headers = @datum[:headers]
if hostname = (headers[LHOST] || headers[UHOST])
hostname.split(COLON).first
end
Expand All @@ -76,18 +75,21 @@ def host
end

def [](key)
@datum[:headers][key]
headers[key]
end

def []=(key, value)
@datum[:headers] ||= {}
@datum[:headers][key] = value
headers[key] = value
end

def uri
url = "#{@scheme}://#{host}:#{@port}#{@path}"
URIUtil.parse_and_normalize_url(url)
end

def headers
@datum[:headers]
end
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/new_relic/agent/http_clients/http_rb_wrappers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def [](key)
def []=(key, value)
@wrapped_request.headers[key] = value
end

def headers
@wrapped_request.headers.to_hash
end
end
end
end
Expand Down
6 changes: 5 additions & 1 deletion lib/new_relic/agent/http_clients/httpclient_wrappers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,16 @@ def uri
end

def [](key)
request.headers[key]
headers[key]
end

def []=(key, value)
request.http_header[key] = value
kaylareopelle marked this conversation as resolved.
Show resolved Hide resolved
end

def headers
request.headers
end
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/new_relic/agent/http_clients/net_http_wrappers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def uri
)
end
end

def headers
@request.instance_variable_get(:@header)
end
end
end
end
Expand Down
11 changes: 7 additions & 4 deletions lib/new_relic/agent/http_clients/typhoeus_wrappers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,17 @@ def method
end

def [](key)
return nil unless @request.options && @request.options[:headers]
return nil unless @request.options && headers

@request.options[:headers][key]
headers[key]
end

def []=(key, value)
@request.options[:headers] ||= {}
@request.options[:headers][key] = value
headers[key] = value
end

def headers
@request.options[:headers] || {}
end

def uri
Expand Down
3 changes: 2 additions & 1 deletion lib/new_relic/agent/transaction/distributed_tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def append_payload(payload)
end

def log_request_headers(headers, direction = "OUTGOING")
NewRelic::Agent.logger.debug("#{direction} REQUEST HEADERS: #{headers}")
printed_headers = headers.is_a?(NewRelic::Agent::HTTPClients::AbstractRequest) ? headers.headers : headers
NewRelic::Agent.logger.debug("#{direction} REQUEST HEADERS: #{printed_headers}")
end

def insert_headers(headers)
Expand Down
3 changes: 2 additions & 1 deletion test/agent_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ def assert_equal_unordered(left, right)
def assert_log_contains(log, message)
lines = log.array

assert (lines.any? { |line| line.match(message) })
assert (lines.any? { |line| line.match(message) }),
"Could not find message. Log contained: #{lines.join("\n")}"
end

def assert_audit_log_contains(audit_log_contents, needle)
Expand Down
31 changes: 31 additions & 0 deletions test/new_relic/agent/transaction/distributed_tracer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,37 @@ def tests_outbound_distributed_trace_headers_omitted_when_exclude_is_true
end
end
end

def test_log_request_headers_with_hash_argument
test_header = {'Snow' => 'Day'}
log = with_array_logger(level = :debug) do
in_transaction do |txn|
txn.distributed_tracer.log_request_headers(test_header)
end
end

assert_log_contains(log, /REQUEST HEADERS: #{test_header}/)
end

def test_log_request_headers_with_request_argument
skip_unless_minitest5_or_above

test_header = {'Test' => 'Header'}
request = MiniTest::Mock.new

request.expect :headers, test_header
request.expect :is_a?, true, [NewRelic::Agent::HTTPClients::AbstractRequest]

log = with_array_logger(level = :debug) do
in_transaction do |txn|
txn.distributed_tracer.log_request_headers(request)
end
end

assert_log_contains(log, /REQUEST HEADERS: #{test_header}/)

request.verify
end
end
end
end
10 changes: 10 additions & 0 deletions test/new_relic/http_client_test_cases.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def test_validate_request_wrapper
req = request_instance

assert_implements req, :type
assert_implements req, :headers
assert_implements req, :host
assert_implements req, :host_from_header
assert_implements req, :method
Expand All @@ -97,6 +98,15 @@ def test_status_code_is_present
assert_equal 200, res.status_code
end

def test_request_headers
header_key = 'Honolooploop'
instance = request_instance
instance[header_key] = 'Charles Petrescu'

assert instance.headers.is_a?(Hash)
assert instance.headers.any? { |k, v| k.casecmp?(header_key) }
end

# Some libraries (older Typhoeus), have had odd behavior around [] for
# missing keys. This generates log messages, although it behaves right in
# terms of metrics, so double-check we get what we expect
Expand Down