Skip to content

[HttpClient][MonologBridge] Add HttpClientProcessor to debug HttpClient errors #35659

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

Closed

Conversation

B-Galati
Copy link
Contributor

@B-Galati B-Galati commented Feb 10, 2020

Q A
Branch? master
Bug fix? no
New feature? yes/no
Deprecations? no
Tickets Fix #34289
License MIT
Doc PR symfony/symfony-docs#...
  • Tests
  • Doc
  • Changelog

Add HttpClient monolog processor that will write http request / response info in log context.
It can help debugging 4XX / 5XX in a prod context.

Processor can be enabled like this:

services:
    Symfony\Bridge\Monolog\Processor\HttpClientProcessor:
        tags:
            - { name: monolog.processor }

@B-Galati B-Galati changed the title [MonologBridge] Add HttpClientProcessor to debug HttpClient errors [HttpClient][MonologBridge] Add HttpClientProcessor to debug HttpClient errors Feb 10, 2020
@B-Galati B-Galati force-pushed the 34289_http_client_monolog_processor branch from de54fe9 to 6ef1c2e Compare February 10, 2020 08:44
@B-Galati
Copy link
Contributor Author

@nicolas-grekas

At the moment the processor is not able to log http client content for some reasons.

I try to dig a bit and observed that this code is able to get the response content while the processor cannot:

$response = $this->httpClient->request('GET', 'https://127.0.0.1:8000/sleep/1/throw');
try {
    $response->getContent();
} catch(HttpExceptionInterface $e) {
    dump($e->getResponse()->getContent(false));
}

My guess is that the CurlResponse::__destruct is doing stuff that may trash/cancel the request/response.

@nicolas-grekas
Copy link
Member

If you don't read the content, neither will the client. The destructor will ensure headers arrived and close the stream just after. Why would it care about the body if the app doesn't ?

@B-Galati
Copy link
Contributor Author

@nicolas-grekas Thanks for the quick answer.

For example, I was calling an API to update a user profile but that request ends in a 400 because one of the field was wrongly formatted.
The only way to know which field and why, was to read the response content.

@nicolas-grekas nicolas-grekas added this to the next milestone Feb 10, 2020
@nicolas-grekas
Copy link
Member

Then this should be done on the app's side, not in the processor (or you're taking the risk of logging an ISO/AVI file content...)

@B-Galati
Copy link
Contributor Author

Then this should be done on the app's side, not in the processor (or you're taking the risk of logging an ISO/AVI file content...)

That's quite cumbersome to do sadly but I see your point. What do you think if we add safe guards like:

  • Check content size before downloading it? if it's too large let's not even log it.
  • Check if the content-type is safe for logging or not? Whitelist some of them like json, xml, text, etc.

At the moment the processor truncates the content in log after 10.000 characters.

@nicolas-grekas
Copy link
Member

How are people going to enable this processor? Maybe you can update the description of the PR to hint about this as that'd help write the doc afterward?
If it's via service configuration, then providing constructor arguments telling which content-types should log the body might be a fit?
Don't miss fabbot's report, adding tests and a line in the changelog of course :)

@B-Galati
Copy link
Contributor Author

How are people going to enable this processor? Maybe you can update the description of the PR to hint about this as that'd help write the doc afterward?

Updated.

If it's via service configuration, then providing constructor arguments telling which content-types should log the body might be a fit?

Yeap!

Don't miss fabbot's report, adding tests and a line in the changelog of course :)

Yeap it's on my todo but I wanted to talk about it before going too far for 2 reasons:

  • The response content is not logged when the Exception is thrown by CurlResponse::__destruct() and it was the big plus of the feature; at least to me. It is working at the moment for me with Symfony 4.3. I don't know what changed in 5.x. I am not sure to end this PR if I cannot get this working as it was the main reason I did it in the 1st place.
  • We talk about having the processor not breaking asynchronicity of the client in the issue but I don't really get how to achieve it.

@nicolas-grekas
Copy link
Member

The response content is not logged when the Exception is thrown by CurlResponse::__destruct()

I don't get this issue as the response is not destructed yet when the exception is processed, so you should be able to get the body

the processor not breaking asynchronicity

that's not possible, but if this is opt-in (eg by default the body is ever logged), then this is fine I think.

@B-Galati
Copy link
Contributor Author

I don't get this issue as the response is not destructed yet when the exception is processed, so you should be able to get the body

I will set up a reproducer tomorrow hopefully.

that's not possible, but if this is opt-in (eg by default the body is ever logged), then this is fine I think.

Thanks! So what did you have in mind with this comment #34289 (comment)?

@nicolas-grekas
Copy link
Member

My previous comment is not very relevant in the context of errors :)

@B-Galati
Copy link
Contributor Author

@nicolas-grekas Hello! Here is the reproducer https://github.com/B-Galati/symfony_reproducer_34289

Let me know for anything.

while ($exception instanceof \Throwable) {
if ($exception instanceof HttpExceptionInterface) {
// It needs to be the 1st statement in order to fulfil the response info
$responseContent = $exception->getResponse()->getContent(false);
Copy link
Member

Choose a reason for hiding this comment

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

this must be wrapped in a try/catch(TransportExceptionInterface)


$record['context']['http_client'][] =
$exception->getResponse()->getInfo()
+ ['response_content' => mb_strimwidth($responseContent, 0, 10000)]
Copy link
Member

Choose a reason for hiding this comment

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

mb_strimwidth is random to me (all mb_* without encoding arguments are, relying on global state).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you think it should be removed? What would be the alternative otherwise, specify the $encoding argument?

Copy link
Contributor Author

@B-Galati B-Galati Feb 11, 2020

Choose a reason for hiding this comment

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

I guess it can be removed if the response size is checked before trying to get the content. WDYT?

fabpot added a commit that referenced this pull request Feb 11, 2020
…ctor throwed an HttpExceptionInterface (nicolas-grekas)

This PR was merged into the 4.4 branch.

Discussion
----------

[HttpClient] fix getting response content after its destructor throwed an HttpExceptionInterface

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | -

Spotted by @B-Galati in #35659 (comment)

Commits
-------

6d1657b [HttpClient] fix getting response content after its destructor throwed an HttpExceptionInterface
@buteihua
Copy link

buteihua commented Feb 11, 2020 via email

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Mar 31, 2020

I'm not sure this is appropriate, especially if you consider prod: $response->getInfo() can contain personal data (eg cookies). What's the minimum amount of info that would be useful?

@B-Galati
Copy link
Contributor Author

@nicolas-grekas Agree! I think response_headers, redirect_count, redirect_url and response_content should be enough no?

@B-Galati
Copy link
Contributor Author

B-Galati commented Apr 3, 2020

In fact, cookies are initially set by response headers so that's not what we want :D

@B-Galati
Copy link
Contributor Author

@nicolas-grekas Sounds like a decorator using AsyncDecoratorTrait #36779 with a logger would do a better job than that no?

@nicolas-grekas
Copy link
Member

@B-Galati AsyncDecoratorTrait would hook very differently, not sure it would cover the same need. On 2nd thought, I think it's OK to "break" async here - the exception has already been thrown. About the body, let's use the same regexp as in HttpClientTrait to log based on the content-type? '#^(?:text/|application/(?:.+\+)?(?:json|xml)$)#i' No need to truncate then to me, at least as a 1st step.

@B-Galati
Copy link
Contributor Author

B-Galati commented Jul 7, 2020

Indeed! The LoggerDecorator would log a lot more information that may be useless.

The regexp looks good to me 👍, very nice!

@fabpot
Copy link
Member

fabpot commented Aug 18, 2020

@B-Galati What's the status of this PR?

@B-Galati
Copy link
Contributor Author

@fabpot still WIP. I am going to change the status.

@B-Galati B-Galati marked this pull request as draft August 18, 2020 18:01
@fabpot
Copy link
Member

fabpot commented Aug 23, 2020

@B-Galati My question was more about the mergeability status estimated time. If it won't be ready in the next few weeks, I'd recommend to close it and reopen when ready.

@B-Galati
Copy link
Contributor Author

Sounds good to me 👍

@B-Galati B-Galati closed this Aug 23, 2020
@nicolas-grekas nicolas-grekas modified the milestones: next, 5.2 Oct 5, 2020
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.

[HttpClient] Debugging option
5 participants