Skip to content

Commit

Permalink
Add support for the tracecontext standard (#16)
Browse files Browse the repository at this point in the history
Add support for the tracecontext standard
- new traceMode config option to switch between traceid and tracecontext
-- supporting both options at the same time is out of scope for this PR, might be possible in the future
-- Need to think about what happens when X-TraceId header is in the request with a value that's not valid for traceContext standard, but app is doing ajax calls with traceparent headers.

MessageBusSubscriber now passes the full TraceContext object to the TraceStamp
- clones original TraceContext, fills parentTransactionId in cloned trace
  • Loading branch information
bram123 authored Dec 13, 2023
1 parent 53c3b5d commit 230669c
Show file tree
Hide file tree
Showing 57 changed files with 1,549 additions and 668 deletions.
86 changes: 16 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ Use [Composer](https://getcomposer.org/).
composer require digitalrevolution/symfony-trace-bundle
```

And one of the UUID generator libraries:
```shell
composer require ramsey/uuid
# OR
composer require symfony/uid
```

Then enable the bundle in your `/config/bundles.php`:

```php
Expand All @@ -42,72 +35,25 @@ return [

## Configuration

```php
# /config/packages/symfony-trace-bundle.php
<?php
declare(strict_types=1);

use DR\SymfonyTraceBundle\Generator\RamseyUuid4Generator;
use DR\SymfonyTraceBundle\SimpleIdStorage;
use Symfony\Config\SymfonyTraceConfig;

return static function (SymfonyTraceConfig $config): void {
// The header which the bundle inspects for the incoming trace ID
// if this is not set an ID will be generated and set at this header
$config->requestHeader('X-Trace-Id');

// Whether to trust the incoming request header. This is turned
// on by default. If true a value in the `X-Trace-Id` header in the request
// will be used as the trace ID for the rest of the request. If false
// those values are ignored.
$config->trustRequestHeader(true);

// The header which the bundle will set the trace ID to on the response
$config->responseHeader('X-Trace-Id');

// The service key of an object that implements
// DR\SymfonyTraceBundle\IdStorageInterface
$config->storageService(SimpleIdStorage::class);

// The service key of an object that implements
// DR\SymfonyTraceBundle\Generator\IdGeneratorInterface
// Optional, will default to Symfony's Uuid or Ramsey's Uuid.
$config->generatorService(RamseyUuid4Generator::class);

// Whether to add the monolog process, defaults to true
$config->enableMonolog(true);

// Whether to add the request id to console commands, defaults to true
$config->enableConsole(true);

// Whether to add the request id to message bus events, defaults to false
$config->enableMessenger(false);

// Whether to add the twig extension, defaults to true
$config->enableTwig(true);

$config->httpClient()
->enabled(true)
->tagDefaultClient(false)
->header('X-Trace-Id');
};
```
By default, the bundle will use the [W3C TraceContext](https://www.w3.org/TR/trace-context/) standard to receive and pass on the traceId.
It's also possible to configure the bundle to setup custom request/response headers and custom ID generators.
Read more about the [TraceId configuration](docs/configuration/traceid.md) and [TraceContext configuration](docs/configuration/tracecontext.md) in the /docs pages.

## How it Works

When a request arrives, it is inspected for the `X-Trace-Id` header. If present,
When a request arrives, it is inspected for request header containing a traceId. If present,
the value in that header will be used throughout the rest of the bundle. This
lets you use trace ID's from somewhere higher up in the stack (like in the web
server itself).

If no trace ID is found, one is generated by the `IdGeneratorInterface`. The
default generator creates version 4 UUIDs.
If no trace ID is found, one is generated by the `TraceIdGeneratorInterface`.
In tracecontext modus In traceId modus, the IDs are generated according to the TraceContext standard.
The default generator in traceId modus creates version 4 UUIDs.

On the way out, the `X-Trace-Id` header is set on the response as well using
the value described above.
On the way out, a response header can be set on the response as well using
the value(s) described above.

The headers are configurable. See the [configuration](#configuration) above.

Internally a transaction ID is generator as well. This ID is used to identify a single request.

## Monolog Integration
Expand Down Expand Up @@ -137,13 +83,13 @@ $monolog->handler('main')

## Messenger Integration

When enabled, the `trace_id` of the dispatcher process, will be added to the `Envelope` of the message. On the consumer
side the `trace_id` will be applied to the running consumer process. Once the `Envelope` has been handled, the `trace_id`
will be reset to the original `trace_id` of the consumer process (if any).
When enabled, the full trace data of the dispatcher process, will be added to the `Envelope` of the message. On the consumer
side the trace data will be applied to the running consumer process. Once the `Envelope` has been handled, the values
will be reset to the original values of the consumer process (if any).

## Twig Integration

By default this bundle will add a global `trace_id` function to your twig
By default, this bundle will add a global `trace_id` and `transaction_id` function to your twig
environment. To disable this set `enable_twig` to `false` in the bundle
configuration.

Expand All @@ -164,10 +110,10 @@ Here's an example of a template.

## HttpClient integration

By default this bundle will check for services tagged with the `http_client.trace_id` tag and decorate them with the TraceIdAwareHttpClient.
By default this bundle will check for services tagged with the `http_client.trace_id` tag and decorate them with the TraceAwareHttpClient.
When `tagDefaultClient` is enabled the default symfony http client will also be tagged and thus decorated.
This will add the `X-Trace-Id` header to all outgoing requests for the tagged clients.
The header name can be changed with the `header` configuration option.
This will add the trace header(s) to all outgoing requests for the tagged clients.
In traceId modus the header name can be changed with the `header` configuration option.

## About us

Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"symfony/framework-bundle": "^6.3||^7.0"
},
"require-dev": {
"digitalrevolution/accessorpair-constraint": "^2.1",
"digitalrevolution/phpunit-extensions": "^1.2",
"digitalrevolution/phpunit-file-coverage-inspection": "^v2.0.0",
"digitalrevolution/utils": "^1.9",
Expand All @@ -30,14 +31,15 @@
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-phpunit": "^1.3",
"phpstan/phpstan-strict-rules": "^1.5",
"phpstan/phpstan-symfony": "^1.3",
"phpunit/phpunit": "^10.4",
"ramsey/uuid": "^4.7",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.7",
"symfony/browser-kit": "^6.3||^7.0",
"symfony/css-selector": "^6.3||^7.0",
"symfony/messenger": "^6.3||^7.0",
"symfony/http-client": "^6.3||^7.0",
"symfony/messenger": "^6.3||^7.0",
"symfony/monolog-bridge": "^6.3||^7.0",
"symfony/monolog-bundle": "^3.10",
"symfony/phpunit-bridge": "^6.3||^7.0",
Expand Down
45 changes: 45 additions & 0 deletions docs/configuration/tracecontext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# TraceContext setup
With the default traceContext setup, tracing will be configured according to the [W3C TraceContext](https://www.w3.org/TR/trace-context/) specification.
Incoming request data will be taken from the traceparent/tracestate headers, this data will be updated and passed to messenger and httpclient requests.

## Configuration

```php
# /config/packages/symfony-trace-bundle.php
<?php
declare(strict_types=1);

use DR\SymfonyTraceBundle\Generator\TraceId\RamseyUuid4Generator;
use DR\SymfonyTraceBundle\TraceStorage;
use Symfony\Config\SymfonyTraceConfig;

return static function (SymfonyTraceConfig $config): void {
// Whether to trust the incoming request header. This is turned on by default.
// If true a value in the `traceparent` header in the request
// will be used and parsed to get the trace ID for the rest of the request. If false
// those values are ignored and new trace ID's are generated.
$config->trustRequestHeader(true);

// The service key of an object that implements
// DR\SymfonyTraceBundle\TraceStorageInterface
// Defaults to TraceStorage::class
$config->storageService(TraceStorage::class);

// Whether to add the monolog process, defaults to true
$config->enableMonolog(true);

// Whether to add the request id to console commands, defaults to true
$config->enableConsole(true);

// Whether to add the request id to message bus events, defaults to false
$config->enableMessenger(false);

// Whether to add the twig extension, defaults to true
$config->enableTwig(true);

// Whether to pass traceparent & tracestate to outgoing http requests, defaults to false
$config->httpClient()
->enabled(true)
->tagDefaultClient(false);
};
```
65 changes: 65 additions & 0 deletions docs/configuration/traceid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# TraceId setup
With traceId mode it's possible to configure the request/response/httpclient headers, and configure custom ID generators.

The bundle will try and use either Symfony Uid or Ramsey Uuid to generate the trace ids:
```shell
composer require ramsey/uuid
# OR
composer require symfony/uid
```
Instead it's also possible to configure a custom ID generator using the `generatorService` option.

## Configuration

```php
# /config/packages/symfony-trace-bundle.php
<?php
declare(strict_types=1);

use DR\SymfonyTraceBundle\Generator\TraceId\RamseyUuid4Generator;
use DR\SymfonyTraceBundle\TraceStorage;
use Symfony\Config\SymfonyTraceConfig;

return static function (SymfonyTraceConfig $config): void {
$config->traceMode('traceId');

// Whether to trust the incoming request header. This is turned
// on by default. If true a value in the `X-Trace-Id` header in the request
// will be used as the trace ID for the rest of the request. If false
// those values are ignored.
$config->trustRequestHeader(true);

$config->traceid()
// The header which the bundle inspects for the incoming trace ID
// if this is not set an ID will be generated and set at this header
->requestHeader('X-Trace-Id')
// The header which the bundle will set the trace ID to on the response
->responseHeader('X-Trace-Id')
// The service key of an object that implements
// DR\SymfonyTraceBundle\Generator\IdGeneratorInterface
// Optional, will default to Symfony's Uuid or Ramsey's Uuid.
->generatorService(RamseyUuid4Generator::class);

// The service key of an object that implements
// DR\SymfonyTraceBundle\TraceStorageInterface
$config->storageService(TraceStorage::class);

// Whether to add the monolog process, defaults to true
$config->enableMonolog(true);

// Whether to add the request id to console commands, defaults to true
$config->enableConsole(true);

// Whether to add the request id to message bus events, defaults to false
$config->enableMessenger(false);

// Whether to add the twig extension, defaults to true
$config->enableTwig(true);

// Whether to pass traceId to outgoing http requests, defaults to false
$config->httpClient()
->enabled(true)
->tagDefaultClient(false)
// The header which the bundle will set the trace ID to on the outgoing request
->header('X-Trace-Id');
};
4 changes: 4 additions & 0 deletions phpmd.baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<?xml version="1.0"?>
<phpmd-baseline>
<violation rule="PHPMD\Rule\Design\CouplingBetweenObjects" file="src/DependencyInjection/SymfonyTraceExtension.php"/>
<violation rule="PHPMD\Rule\CyclomaticComplexity" file="src/DependencyInjection/SymfonyTraceExtension.php" method="loadInternal"/>
<violation rule="PHPMD\Rule\Design\NpathComplexity" file="src/DependencyInjection/SymfonyTraceExtension.php" method="loadInternal"/>
<violation rule="PHPMD\Rule\Design\LongMethod" file="src/DependencyInjection/SymfonyTraceExtension.php" method="loadInternal"/>
<violation rule="PHPMD\Rule\Controversial\Superglobals" file="tests/Functional/AbstractKernelTestCase.php" method="createKernel"/>
<violation rule="PHPMD\Rule\Controversial\Superglobals" file="tests/Functional/AbstractWebTestCase.php" method="createKernel"/>
</phpmd-baseline>
63 changes: 49 additions & 14 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,39 +1,74 @@
parameters:
ignoreErrors:
-
message: "#^Cannot call method booleanNode\\(\\) on Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\|null\\.$#"
message: "#^Parameter \\#1 \\$mergedConfig \\(array\\{traceMode\\: 'tracecontext'\\|'traceid', traceid\\: array\\{request_header\\: string, response_header\\: string, generator_service\\: string\\|null\\}, trust_request_header\\: bool, storage_service\\: string\\|null, enable_monolog\\: bool, enable_console\\: bool, enable_messenger\\: bool, enable_twig\\: bool, \\.\\.\\.\\}\\) of method DR\\\\SymfonyTraceBundle\\\\DependencyInjection\\\\SymfonyTraceExtension\\:\\:loadInternal\\(\\) should be contravariant with parameter \\$mergedConfig \\(array\\) of method Symfony\\\\Component\\\\HttpKernel\\\\DependencyInjection\\\\ConfigurableExtension\\:\\:loadInternal\\(\\)$#"
count: 1
path: src/DependencyInjection/Configuration.php
path: src/DependencyInjection/SymfonyTraceExtension.php

-
message: "#^Parameter \\#1 \\$mergedConfig \\(array\\{request_header\\: string, trust_request_header\\: bool, response_header\\: string, storage_service\\: string\\|null, generator_service\\: string\\|null, enable_monolog\\: bool, enable_console\\: bool, enable_messenger\\: bool, \\.\\.\\.\\}\\) of method DR\\\\SymfonyTraceBundle\\\\DependencyInjection\\\\SymfonyTraceExtension\\:\\:loadInternal\\(\\) should be contravariant with parameter \\$mergedConfig \\(array\\) of method Symfony\\\\Component\\\\HttpKernel\\\\DependencyInjection\\\\ConfigurableExtension\\:\\:loadInternal\\(\\)$#"
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Http\\\\TraceAwareHttpClient\\:\\:request\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1
path: src/DependencyInjection/SymfonyTraceExtension.php
path: src/Http/TraceAwareHttpClient.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Http\\\\TraceAwareHttpClient\\:\\:withOptions\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1
path: src/Http/TraceAwareHttpClient.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Monolog\\\\TraceProcessor\\:\\:__invoke\\(\\) has parameter \\$record with no value type specified in iterable type array\\.$#"
count: 1
path: src/Monolog/TraceProcessor.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Monolog\\\\TraceProcessor\\:\\:__invoke\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Monolog/TraceProcessor.php

-
message: "#^Return type \\(array\\|Monolog\\\\LogRecord\\) of method DR\\\\SymfonyTraceBundle\\\\Monolog\\\\TraceProcessor\\:\\:__invoke\\(\\) should be covariant with return type \\(Monolog\\\\LogRecord\\) of method Monolog\\\\Processor\\\\ProcessorInterface\\:\\:__invoke\\(\\)$#"
count: 1
path: src/Monolog/TraceProcessor.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Service\\\\TraceContext\\\\TraceContextService\\:\\:handleClientRequest\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1
path: src/Service/TraceContext/TraceContextService.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Service\\\\TraceContext\\\\TraceContextService\\:\\:handleClientRequest\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Service/TraceContext/TraceContextService.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Service\\\\TraceId\\\\TraceIdService\\:\\:handleClientRequest\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1
path: src/Service/TraceId/TraceIdService.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Http\\\\TraceIdAwareHttpClient\\:\\:request\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Service\\\\TraceId\\\\TraceIdService\\:\\:handleClientRequest\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Http/TraceIdAwareHttpClient.php
path: src/Service/TraceId/TraceIdService.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Http\\\\TraceIdAwareHttpClient\\:\\:withOptions\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Service\\\\TraceServiceInterface\\:\\:handleClientRequest\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1
path: src/Http/TraceIdAwareHttpClient.php
path: src/Service/TraceServiceInterface.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Monolog\\\\TraceIdProcessor\\:\\:__invoke\\(\\) has parameter \\$record with no value type specified in iterable type array\\.$#"
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Service\\\\TraceServiceInterface\\:\\:handleClientRequest\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Monolog/TraceIdProcessor.php
path: src/Service/TraceServiceInterface.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Monolog\\\\TraceIdProcessor\\:\\:__invoke\\(\\) return type has no value type specified in iterable type array\\.$#"
message: "#^Parameter \\#1 \\$options \\(array\\{environment\\?\\: string, debug\\?\\: bool, tracemode\\?\\: string\\}\\) of method DR\\\\SymfonyTraceBundle\\\\Tests\\\\Functional\\\\AbstractKernelTestCase\\:\\:createKernel\\(\\) should be contravariant with parameter \\$options \\(array\\) of method Symfony\\\\Bundle\\\\FrameworkBundle\\\\Test\\\\KernelTestCase\\:\\:createKernel\\(\\)$#"
count: 1
path: src/Monolog/TraceIdProcessor.php
path: tests/Functional/AbstractKernelTestCase.php

-
message: "#^Return type \\(array\\|Monolog\\\\LogRecord\\) of method DR\\\\SymfonyTraceBundle\\\\Monolog\\\\TraceIdProcessor\\:\\:__invoke\\(\\) should be covariant with return type \\(Monolog\\\\LogRecord\\) of method Monolog\\\\Processor\\\\ProcessorInterface\\:\\:__invoke\\(\\)$#"
message: "#^Parameter \\#1 \\$options \\(array\\{environment\\?\\: string, debug\\?\\: bool, tracemode\\?\\: string\\}\\) of method DR\\\\SymfonyTraceBundle\\\\Tests\\\\Functional\\\\AbstractWebTestCase\\:\\:createKernel\\(\\) should be contravariant with parameter \\$options \\(array\\) of method Symfony\\\\Bundle\\\\FrameworkBundle\\\\Test\\\\KernelTestCase\\:\\:createKernel\\(\\)$#"
count: 1
path: src/Monolog/TraceIdProcessor.php
path: tests/Functional/AbstractWebTestCase.php

-
message: "#^Method DR\\\\SymfonyTraceBundle\\\\Tests\\\\Functional\\\\App\\\\Monolog\\\\MemoryHandler\\:\\:write\\(\\) has parameter \\$record with no value type specified in iterable type array\\.$#"
Expand Down
Loading

0 comments on commit 230669c

Please sign in to comment.