feature #30964 [HttpKernel] Add a "short" trace header format, make header configurable (mpdude)

This PR was squashed before being merged into the 4.3-dev branch (closes #30964).

Discussion
----------

[HttpKernel] Add a "short" trace header format, make header configurable

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        | symfony/symfony-docs#11340

This pull requests adds the first usage of `array_key_first` to the Symfony code base. Additionally, it makes it possible to configure the `HttpCache` to also add a "trace" header in production.

The `HttpCache` is a convenient, low-barrier yet performant way of accelerating the application. By having the "trace" information returned as a header in production as well, you can write it to server log files. For example, with Apache you can use `%{X-Symfony-Cache}o` in the `LogFormat` directive to log response headers.

With the information in the log files, you can easily process it from logfile processing/system metrics tools to find out about cache performance, efficiency and the URLs that might need extra cache tweaking.

<img width="1040" alt="Bildschirmfoto 2019-04-07 um 11 43 23" src="https://user-images.githubusercontent.com/1202333/55681763-6e90e980-592a-11e9-900f-e096350531c2.png">

The "short" format will only output information for the main request to avoid leaking internal URLs for ESI subrequests. I also chose a concise format like `stale/valid/store` because that's much easier to parse out of logfiles (no whitespace, no need for quotes etc.).

If you're not comfortable with having `Symfony` in the header name that way, the header name can be changed through a configuration setting as well.

#FOSSHackathon

Commits
-------

9a2fcc9392 [HttpKernel] Add a \"short\" trace header format, make header configurable
This commit is contained in:
Nicolas Grekas 2019-04-07 13:45:14 +02:00
commit 226b36e99a
5 changed files with 76 additions and 4 deletions

View File

@ -24,6 +24,7 @@ CHANGELOG
* renamed `GetResponseForExceptionEvent` to `ExceptionEvent`
* renamed `PostResponseEvent` to `TerminateEvent`
* added `HttpClientKernel` for handling requests with an `HttpClientInterface` instance
* added `trace_header` and `trace_level` configuration options to `HttpCache`
4.2.0
-----

View File

@ -40,7 +40,14 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
*
* The available options are:
*
* * debug: If true, the traces are added as a HTTP header to ease debugging
* * debug If true, exceptions are thrown when things go wrong. Otherwise, the cache
* will try to carry on and deliver a meaningful response.
*
* * trace_level May be one of 'none', 'short' and 'full'. For 'short', a concise trace of the
* master request will be added as an HTTP header. 'full' will add traces for all
* requests (including ESI subrequests). (default: 'full' if in debug; 'none' otherwise)
*
* * trace_header Header name to use for traces. (default: X-Symfony-Cache)
*
* * default_ttl The number of seconds that a cache entry should be considered
* fresh when no explicit freshness information is provided in
@ -87,7 +94,13 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
'allow_revalidate' => false,
'stale_while_revalidate' => 2,
'stale_if_error' => 60,
'trace_level' => 'none',
'trace_header' => 'X-Symfony-Cache',
], $options);
if (!isset($options['trace_level']) && $this->options['debug']) {
$this->options['trace_level'] = 'full';
}
}
/**
@ -110,6 +123,23 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
return $this->traces;
}
private function addTraces(Response $response)
{
$traceString = null;
if ('full' === $this->options['trace_level']) {
$traceString = $this->getLog();
}
if ('short' === $this->options['trace_level'] && $masterId = array_key_first($this->traces)) {
$traceString = implode('/', $this->traces[$masterId]);
}
if (null !== $traceString) {
$response->headers->add([$this->options['trace_header'] => $traceString]);
}
}
/**
* Returns a log message for the events of the last request processing.
*
@ -194,8 +224,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
$this->restoreResponseBody($request, $response);
if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) {
$response->headers->set('X-Symfony-Cache', $this->getLog());
if (HttpKernelInterface::MASTER_REQUEST === $type) {
$this->addTraces($response);
}
if (null !== $this->surrogate) {

View File

@ -1508,6 +1508,44 @@ class HttpCacheTest extends HttpCacheTestCase
// Surrogate request
$cache->handle($request, HttpKernelInterface::SUB_REQUEST);
}
public function testTraceHeaderNameCanBeChanged()
{
$this->cacheConfig['trace_header'] = 'X-My-Header';
$this->setNextResponse();
$this->request('GET', '/');
$this->assertTrue($this->response->headers->has('X-My-Header'));
}
public function testTraceLevelDefaultsToFullIfDebug()
{
$this->setNextResponse();
$this->request('GET', '/');
$this->assertTrue($this->response->headers->has('X-Symfony-Cache'));
$this->assertEquals('GET /: miss', $this->response->headers->get('X-Symfony-Cache'));
}
public function testTraceLevelDefaultsToNoneIfNotDebug()
{
$this->cacheConfig['debug'] = false;
$this->setNextResponse();
$this->request('GET', '/');
$this->assertFalse($this->response->headers->has('X-Symfony-Cache'));
}
public function testTraceLevelShort()
{
$this->cacheConfig['trace_level'] = 'short';
$this->setNextResponse();
$this->request('GET', '/');
$this->assertTrue($this->response->headers->has('X-Symfony-Cache'));
$this->assertEquals('miss', $this->response->headers->get('X-Symfony-Cache'));
}
}
class TestKernel implements HttpKernelInterface

View File

@ -122,7 +122,9 @@ class HttpCacheTestCase extends TestCase
$this->store = new Store(sys_get_temp_dir().'/http_cache');
$this->cacheConfig['debug'] = true;
if (!isset($this->cacheConfig['debug'])) {
$this->cacheConfig['debug'] = true;
}
$this->esi = $esi ? new Esi() : null;
$this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig);

View File

@ -22,6 +22,7 @@
"symfony/http-foundation": "^4.1.1",
"symfony/debug": "~3.4|~4.0",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-php73": "^1.9",
"psr/log": "~1.0"
},
"require-dev": {