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

Add request_id option to client #133

Merged
merged 51 commits into from
Apr 23, 2018
Merged

Add request_id option to client #133

merged 51 commits into from
Apr 23, 2018

Conversation

funkyboy
Copy link
Contributor

@funkyboy funkyboy commented Apr 17, 2018

Adds a request_id=random_value query string to any path if the client is initialized with options
{ add_request_ids: true }. Handy for debugging.

@funkyboy funkyboy requested a review from SimonWoolf April 17, 2018 07:41
Copy link
Member

@SimonWoolf SimonWoolf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing the crucial part, which is including the request_id in the ConnectionTimeout, ConnectionError etc. so that when the customer gets the timeout they can correlate it with our logs. Otherwise there's no point, we get the request id but don't have anything to correlate it with

@@ -146,6 +150,7 @@ def initialize(options)
@custom_host = options.delete(:rest_host)
@custom_port = options.delete(:port)
@custom_tls_port = options.delete(:tls_port)
@add_request_ids = options.delete(:add_request_ids) == false ? false : true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an optional extra debug option, this should default to false (unless and until it's added to the spec). So just @add_request_ids = options.delete(:add_request_ids) is fine.

(Also, cond ? false : true is a pointless pattern, it's equivalent to !cond)

path_with_params.query_values = {request_id: SecureRandom.hex(20)}
query = path_with_params.query
path = "#{path}?#{query}"
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you manually munging the path? This method takes a params hash. You can add it to the params hash.

Copy link
Member

@mattheworiordan mattheworiordan Apr 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonWoolf because this is Ruby and not Elixir. So the params object would be changed by just calling this method, which is bad.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the params object would be changed by just calling this method, which is bad.

@mattheworiordan fair enough, but imo the solution to that is to clone the hash (as Cesare has done in the latest version of this pr), not to start munging a param into the path (meaning faraday has to parse it out again to combine with the params -- assuming it even does that rather than give you a path with two ?s). (for one thing the original approach assumes the path doesn't already have a querysting, so fails the 'what if two things did this' test)

it 'includes request_id in URL' do
expect(subject.add_request_ids).to eql(true)
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're going to add a test, may as well be one that checks the result (i.e. uses webmock to check that a request_id actually gets added to the querystring for rest requests, and checks timeout exceptions to make sure that the request ID gets printed with those), rather than just checks that an instance variable got set correctly, no?

@funkyboy
Copy link
Contributor Author

@SimonWoolf re-ready for review.

Copy link
Member

@SimonWoolf SimonWoolf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there

@@ -434,6 +439,10 @@ def send_request(method, path, params, options)
max_retry_duration = http_defaults.fetch(:max_retry_duration)
requested_at = Time.now
retry_count = 0
if @add_request_ids
params = {} if params.nil?
params[:request_id] = SecureRandom.hex(20)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hex takes 2 characters per byte, which means this adds 40 characters, which is just unnecessary. Go for SecureRandom.urlsafe_base64(10) instead, that'll be 14 characters

def initialize(message, status = nil, code = nil, base_exception = nil, options = {})
super message, status, code, base_exception, options
def initialize(message, status = nil, code = nil, base_exception = nil, options = {}, request_id = nil)
super message, status, code, base_exception, options, request_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting there, but just having the request ID as a property of the exception isn't enough -- it'll be printed to a log file, it needs to be in the string representation, next method below this one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we leave request_id as an option of options, then no change here is needed.

@@ -456,12 +465,12 @@ def send_request(method, path, params, options)
logger.warn { "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed: #{error}" }
retry
end

request_id = params[:request_id] if @add_request_ids
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you generated and set the request ID only a few lines earlier; just make a local var there, it'll still be in scope, no need to fetch from the params hash

when Faraday::ClientError
raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error)
raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error, {}, request_id)
else
raise error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the errors they want to correlate was the nonce value one, which'll be in this branch, that also needs the requestId set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also not keen on a var called random_request_id, that sounds more like a method. It's no longer random once it's assigned, and thus it's consistent for the request.

Copy link
Member

@mattheworiordan mattheworiordan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to @SimonWoolf's feedback...


def initialize(message, status = nil, code = nil, base_exception = nil, options = {})
def initialize(message, status = nil, code = nil, base_exception = nil, options = {}, request_id = nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have preferred for request_id to be an option given it's a flexible hash for this reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn’t know the supposed usage of that hash, that’s why I didn’t touch it. Good to know that I can use it.

def initialize(message, status = nil, code = nil, base_exception = nil, options = {})
super message, status, code, base_exception, options
def initialize(message, status = nil, code = nil, base_exception = nil, options = {}, request_id = nil)
super message, status, code, base_exception, options, request_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we leave request_id as an option of options, then no change here is needed.

@@ -434,6 +439,10 @@ def send_request(method, path, params, options)
max_retry_duration = http_defaults.fetch(:max_retry_duration)
requested_at = Time.now
retry_count = 0
if @add_request_ids
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's bad practice to read an instance variable like this when it's available as a reader i.e. if @add_request_idds would always be false due to a type, whereas if add_request_idds would fail.

case error
when Faraday::TimeoutError
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error)
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error, {}, request_id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my proposed change, it would be raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error, { request_id: request_id }).
I think raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error, request_id: request_id) may work too, although I recall 1.9.2 having some constraints with pseudo named args.

@@ -50,4 +50,11 @@
end
end
end

context 'request_id generation' do
let(:client_options) { {key: 'appid.keyuid:keysecret', add_request_ids: true} }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unusual spacing, in all other places { has a space after, and } a space before

@funkyboy
Copy link
Contributor Author

@SimonWoolf @mattheworiordan

  • added request_id for nonce errors
  • now passing request_id in the options param
  • added more tests for both nonce and timeout errors with add_request_ids true and false

Ready for another look.

Copy link
Member

@mattheworiordan mattheworiordan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a few comments I am afraid.

@@ -449,6 +449,7 @@ def send_request(method, path, params, options)
use_fallback = can_fallback_to_alternate_ably_host? && retry_count > 0

connection(use_fallback: use_fallback).send(method, path, params) do |request|
request.options.context = {request_id: random_request_id} if add_request_ids
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent spacing here again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, shouldn't this append/merge into the request options context in case there are other attributes already? This will always overwrite them which feels wrong and is error prone.

@@ -961,6 +961,7 @@ def encode64(text)
end

context 'request_id generation' do
context 'Timeout error' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation issues here. Why did you add context & end without indenting the code?

begin
client2.auth.request_token(token_params)
rescue Ably::Exceptions::UnauthorizedRequest => err
expect(err.request_id).to_not eql(nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any test to ensure that when add_request_ids is false, that the request_id is nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes!

path_with_params.query_values = {request_id: SecureRandom.hex(20)}
query = path_with_params.query
path = "#{path}?#{query}"
end
Copy link
Member

@mattheworiordan mattheworiordan Apr 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonWoolf because this is Ruby and not Elixir. So the params object would be changed by just calling this method, which is bad.

@@ -470,9 +470,9 @@ def send_request(method, path, params, options)
end
case error
when Faraday::TimeoutError
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error, {}, random_request_id)
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error, {request_id: random_request_id})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing issues again, here and many times below

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a big fan of this. random_request_id is defined in line 445, yet is within a conditional. I realise Ruby allows this sort of thing, but it's messy and error prone. Why are you defining random_request_id as a varaiable when you have access to the params variable in this scope which is available anyway?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I am not sure I have seen a test that confirms that a) a request is sent to Ably with a request_id, b) it fails (intentionally), c) the test confirms that the exception raised has that SAME request_id, d) the Logger is showing the request_id in the output. This is the crux of what we're doing here. Given this code path, I fear our tests so far are just testing that a request_id is present, but not ensuring it is consistent throughout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense, but I am struggling a bit to inspect the url of a request, say client.stats. Any suggestion?

@@ -27,7 +27,7 @@ def on_complete(env)
end

message = 'Unknown server error' if message.to_s.strip == ''
request_id = env.request.context[:request_id]
request_id = env.request.context[:request_id] if env.request.context
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing issues below.

when Faraday::ClientError
raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error)
raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error, {}, request_id)
else
raise error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also not keen on a var called random_request_id, that sounds more like a method. It's no longer random once it's assigned, and thus it's consistent for the request.

@mattheworiordan
Copy link
Member

mattheworiordan commented Apr 18, 2018

image

@funkyboy what is going on with your indentation?

@funkyboy funkyboy force-pushed the add-request-id-to-path branch from f99e186 to 8b87e5f Compare April 20, 2018 11:18
Copy link
Member

@mattheworiordan mattheworiordan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@@ -434,11 +439,22 @@ def send_request(method, path, params, options)
max_retry_duration = http_defaults.fetch(:max_retry_duration)
requested_at = Time.now
retry_count = 0
request_id = nil
if add_request_ids
params = {} if params.nil?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a tiny comment, but it seems unnecesary to dup an empty hash, so perhaps instead just:

params = if params.nil?
  {}
else
  params.dup
end

when Faraday::ClientError
raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error)
raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error, { request_id: request_id })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be nice for future us to add a note about the raise error not explicitly passing the request_id, but in the Middleware it's available through the requext context. WDYT?

@funkyboy funkyboy merged commit 6b9358b into master Apr 23, 2018
@funkyboy funkyboy deleted the add-request-id-to-path branch April 23, 2018 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants