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

Refactor raygun client #117

Merged
merged 23 commits into from
Dec 5, 2019

Conversation

anfly0
Copy link
Contributor

@anfly0 anfly0 commented Nov 12, 2019

Hi!
This pull request builds on the changes made in pr #116
Backwards compatibility with 1.x will be broken by this pr!

The purpose of this pr is to move the message transmission implementation, from the RaygunClient to a new separate class.
These changes have the benefit of making any future changes to the transmission mechanism much more straightforward and safer.

As an added bonus, this pr implements two 'transports' built on guzzle.
One async and one sync, thus closing #111

My thinking is that this pr is a solid stepping stone towards releasing 2.0.

Things that are missing or need discussing:

  • It would probably be nice to have a client factory.
  • The naming of classes and interfaces, prepend 'Raygun' yes or no?
  • Might be some dead code left in RaygunClient.
  • Readme needs updating.

Anyhow, I'm looking forward to hearing what you guys have to say.

Copy link
Contributor

@robbieaverill robbieaverill left a comment

Choose a reason for hiding this comment

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

I've added some cursory comments. Some other notes:

  • breaking changes - all good, this is 2.x-dev already in master
  • "Raygun" prefixes on existing classes - I think Raygun4php\Client would probably be better than Raygun4php\RaygunClient, especially since you've changed the constructor signature so people's implementations will break obviously anyway, this is the right time to rename classes if you want to do so
  • "It would probably be nice to have a client factory." - yes, agreed, particularly since you have provided both sync and async transport options

Thanks for the PR!

I'd like to get some feedback from the Raygun team before this gets merged in, but I'm happy to provide some suggestions along the way.

composer.json Outdated Show resolved Hide resolved
{
public function build(\Exception $exception): void;

public function toJson(): string;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I've mentioned this before, but perhaps we should use JsonSerializable here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I mentioned in #116 my reasoning behind toJson() is to not have to deal with the encoding issus solved by toJsonRemoveUnicodeSequences() and removeNullBytes().
It is at mather of taste, but I like it when an object takes care of all aspects of serializing itself.

$this->httpPromises[] = $this->httpClient
->requestAsync('POST', '/entries', ['body' => $messageJson]);
} catch (TransferException $th) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Providing some pluggability here somehow would probably be a good thing, this will be hard to debug for people having failed API calls

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that is definitely true.
One idea is to implement PSR-3 LoggerAwareInterface and add appropriate logging

}

$responseCode = $httpResponse->getStatusCode();
return $responseCode === 202;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is 202 always returned from the Raygun API?

Copy link
Contributor Author

@anfly0 anfly0 Nov 12, 2019

Choose a reason for hiding this comment

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

No, but 202 is the only one that signifies success.
success = true
fail = false

@robbieaverill
Copy link
Contributor

I've merged #116 for you

@anfly0
Copy link
Contributor Author

anfly0 commented Nov 15, 2019

@robbieaverill I'm looking into some of the things you commented on. But I don't think I will push any more commits to this pr before the Raygun team has taken a look.
Thank you for the great and quick feedback!

@robbieaverill
Copy link
Contributor

cc @MindscapeHQ/external-php-admins for review when you have a chance

Copy link
Contributor

@samuel-holt samuel-holt left a comment

Choose a reason for hiding this comment

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

Great work with this, these changes are looking good and the transmission of data a lot more reliable. I've just pointed out a few minor stylistic issues and some questions around how things work. The tests are good, I assume you've tested this with a sample app and it's all working as expected?

There were a couple of items in the PR description which I feel might need addressing:

  • Might be some dead code left in RaygunClient. Is this still relevant? Ideally any unused code would be deleted
  • Readme needs updating. Could this be done as part of this PR? Would we need separate docs for v1.x?


private function toJsonRemoveUnicodeSequences($struct)
{
return preg_replace_callback("/\\\\u([a-f0-9]{4})/", function ($matches) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would good to have a comment explaining exactly what's happening here...

It looks like we're replacing all instances of unicode strings with UTF-8 encoded strings. Is this a standard operation? Why UCS-4LE? Why is the V (unsigned long) format string used?

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 is a leftover from version 1, and I'm not really sure of the what's and the why's of this implementation. The change I made was to move this method from the client class to the message class.

Anyhow, there is definitely room for improvements regarding this method, but I think it is a bit out of scope for this pr.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 I agree, the improvements to this are out of scope

$this->httpPromises[] = $this->httpClient
->requestAsync('POST', '/entries', ['body' => $messageJson]);
} catch (TransferException $th) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we do something besides just returning false here? Could the exception at least be logged if in debug mode?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.
One idea is to implement PSR-3 LoggerAwareInterface and add appropriate logging.
Do you have any other suggestions?

Copy link
Contributor

Choose a reason for hiding this comment

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

We could create a callback (e.g onTransmissionFailure($th, $message)) which can be defined in the initial RaygunClient setup. That way developers could choose what to do with the exception. If in debug mode we could log the exception - the LoggerAwareInterface looks really good.

* Synchronously transmits the message to the raygun API.
*
* @param RaygunMessageInterface $message
* @return boolean False if request fails or of the response status code is not 202.
Copy link
Contributor

Choose a reason for hiding this comment

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

/s "... fails or if the response status ..."

$httpResponse = $this->httpClient
->request('POST', '/entries', ['body' => $messageJson]);
} catch (TransferException $th) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above, would be good to log something if the request fails

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above, PSR-3.

$this->transportMock = $this->getMockBuilder(TransportInterface::class)
->setMethods(['transmit'])
->getMock();

Copy link
Contributor

Choose a reason for hiding this comment

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

Delete extra space here

$client->SendException(new \Exception('test'));
$raygunMessage = $transportStub->getMessage();

$this->assertJsonMatchesSchemaString($this->jsonSchema, json_decode($raygunMessage->toJson()));
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary? It looks like it's encoding as JSON, then decoding again: json_decode($raygunMessage->toJson()) - is this to do with the removal of unicode chars and null strings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think it is.
The reason for this is that I want the test to be decoupled from the implementation of RaygunMessage.

The test is essentially asserting that in the client $transport->transmit($message) is called with a message the can produce a valid JSON representation of itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Cool, makes sense

public function removeNullBytes($string)
{
return str_replace("\0", '', $string);
return $this->transport->transmit($message);
Copy link
Contributor

Choose a reason for hiding this comment

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

Great job deleting this spaghetti code. This is so much better! 😀

* 202 if accepted, 403 if invalid JSON payload
* @throws Raygun4PhpException
* @return bool Returns true if the transmission attempt is successful.
* However, this does not guarantee that the message is delivered.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean that it can return true when it has not delivered the data? If it does return true and the message has not been delivered, is there any way to know this (e.g. logging)?

Copy link
Contributor Author

@anfly0 anfly0 Dec 2, 2019

Choose a reason for hiding this comment

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

Yes, you could have a case where some async implementation of TransportInterface successfully queues a message for delivery and returns True, but the actual delivery fails for whatever reason. The natural place to log such an event would be in the transport implementation.

@anfly0
Copy link
Contributor Author

anfly0 commented Dec 2, 2019

@samuel-holt
Thanks for the review!

I have done some basic testing, and have not noticed any problems.

I rechecked the client class, and there seems to be some dead code left, but I will remove it.

The documentation most definitely needs to be separate from v1.x as the instantiation of the client is completely different. For now, I can add a separate usage example for v2.x.

@anfly0
Copy link
Contributor Author

anfly0 commented Dec 2, 2019

Ok, I have pushed some minor changes and synced this branch with the master
This pr is growing and becoming a bit unwieldy.
But I take it that you guys think that this pr is moving the codebase in the right direction.

Maybe this is a good point to merge and open up new issues for:

  • Logging in the transports
  • Updating documentation
  • (Testing)

@samuel-holt
Copy link
Contributor

@anfly0 I agree, these things can be done in a separate PR

Copy link
Contributor

@samuel-holt samuel-holt left a comment

Choose a reason for hiding this comment

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

👍 Awesome work improving the transport, a much needed refactor. As you mentioned, this PR is getting big and handling the transport logging in a separate PR is a good approach.

Copy link
Contributor

@robbieaverill robbieaverill left a comment

Choose a reason for hiding this comment

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

LGTM too, thank you for contributing, and for persisting with us through review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants