Skip to content

Commit 7d84ab3

Browse files
authored
Allow console trace id be set via env vars (#27)
1 parent 6561a17 commit 7d84ab3

17 files changed

+207
-49
lines changed

docs/configuration/tracecontext.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ return static function (SymfonyTraceConfig $config): void {
3232
$config->enableMonolog(true);
3333

3434
// Whether to add the request id to console commands, defaults to true
35-
$config->enableConsole(true);
35+
$config->console()->enabled(true);
36+
37+
// Optional, set console trace id based on env var. defaults to null
38+
$config->console()->traceId(env('TRACE_ID'));
3639

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

phpstan-baseline.neon

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
parameters:
22
ignoreErrors:
33
-
4-
message: "#^Parameter \\#1 \\$mergedConfig \\(array\\{traceMode\\: 'tracecontext'\\|'traceid', traceid\\: array\\{request_header\\: string, response_header\\: string, generator_service\\: string\\|null\\}, trust_request_header\\: bool, send_response_header\\: bool, storage_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\\(\\)$#"
4+
message: "#^Parameter \\#1 \\$mergedConfig \\(array\\{traceMode\\: 'tracecontext'\\|'traceid', traceid\\: array\\{request_header\\: string, response_header\\: string, generator_service\\: string\\|null\\}, trust_request_header\\: bool, send_response_header\\: bool, storage_service\\: string\\|null, enable_monolog\\: bool, enable_console\\: bool, console\\: array\\{enabled\\: bool, trace_id\\: string\\|null\\}, \\.\\.\\.\\}\\) of method DR\\\\SymfonyTraceBundle\\\\DependencyInjection\\\\SymfonyTraceExtension\\:\\:loadInternal\\(\\) should be contravariant with parameter \\$mergedConfig \\(array\\) of method Symfony\\\\Component\\\\HttpKernel\\\\DependencyInjection\\\\ConfigurableExtension\\:\\:loadInternal\\(\\)$#"
55
count: 1
66
path: src/DependencyInjection/SymfonyTraceExtension.php
77

src/DependencyInjection/Configuration.php

+55-24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Sentry\State\HubInterface;
88
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
9+
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
910
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
1011
use Symfony\Component\Config\Definition\ConfigurationInterface;
1112

@@ -16,7 +17,7 @@
1617
class Configuration implements ConfigurationInterface
1718
{
1819
public const TRACEMODE_TRACECONTEXT = 'tracecontext';
19-
public const TRACEMODE_TRACEID = 'traceid';
20+
public const TRACEMODE_TRACEID = 'traceid';
2021

2122
public function getConfigTreeBuilder(): TreeBuilder
2223
{
@@ -69,7 +70,21 @@ public function getConfigTreeBuilder(): TreeBuilder
6970
->end()
7071
->booleanNode('enable_console')
7172
->info('Whether to add the trace id to console commands, defaults to true')
72-
->defaultTrue()
73+
->defaultNull()
74+
->setDeprecated('digitalrevolution/symfony-trace-bundle', '0.6.0')
75+
->end()
76+
->arrayNode('console')
77+
->addDefaultsIfNotSet()
78+
->children()
79+
->booleanNode('enabled')
80+
->info('Whether to add the trace id to console commands, defaults to true')
81+
->defaultTrue()
82+
->end()
83+
->scalarNode('trace_id')
84+
->info('Option to set the `Trace-Id` to use for console commands from env var. Defaults to null.')
85+
->defaultNull()
86+
->end()
87+
->end()
7388
->end()
7489
->booleanNode('enable_messenger')
7590
->info('Whether to add the trace id to message bus events, defaults to false')
@@ -79,37 +94,53 @@ public function getConfigTreeBuilder(): TreeBuilder
7994
->info('Whether or not to enable the twig `trace_id()` and `transaction_id()` functions. Only works if TwigBundle is present.')
8095
->defaultTrue()
8196
->end()
82-
->arrayNode('http_client')
83-
->addDefaultsIfNotSet()
84-
->children()
85-
->booleanNode('enabled')
86-
->info('Whether or not to enable the trace id aware http client')
87-
->defaultTrue()
88-
->end()
89-
->booleanNode('tag_default_client')
90-
->info('Whether or not to tag the default http client')
91-
->defaultFalse()
92-
->end()
93-
->scalarNode('header')
94-
->info('The header the bundle set in the request in the http client')
95-
->defaultValue('X-Trace-Id')
96-
->end()
97+
->append($this->createHttpClientConfiguration())
98+
->append($this->createSentryConfiguration())
99+
;
100+
101+
return $tree;
102+
}
103+
104+
private function createHttpClientConfiguration(): NodeDefinition
105+
{
106+
$node = (new TreeBuilder('http_client'))->getRootNode();
107+
$node
108+
->addDefaultsIfNotSet()
109+
->children()
110+
->booleanNode('enabled')
111+
->info('Whether or not to enable the trace id aware http client')
112+
->defaultTrue()
113+
->end()
114+
->booleanNode('tag_default_client')
115+
->info('Whether or not to tag the default http client')
116+
->defaultFalse()
117+
->end()
118+
->scalarNode('header')
119+
->info('The header the bundle set in the request in the http client')
120+
->defaultValue('X-Trace-Id')
97121
->end()
98122
->end()
99-
->arrayNode('sentry')
100-
->addDefaultsIfNotSet()
101-
->children()
102-
->booleanNode('enabled')
123+
->end();
124+
125+
return $node;
126+
}
127+
128+
private function createSentryConfiguration(): NodeDefinition
129+
{
130+
$node = (new TreeBuilder('sentry'))->getRootNode();
131+
$node
132+
->addDefaultsIfNotSet()
133+
->children()
134+
->booleanNode('enabled')
103135
->info('Whether or not to enable passing trace and transaction id to Sentry')
104136
->defaultFalse()
105137
->end()
106138
->scalarNode('hub_service')
107139
->info('The service id of the Sentry Hub. Defaults to Sentry\State\HubInterface')
108140
->defaultValue(HubInterface::class)
109141
->end()
110-
->end()
111-
;
142+
->end();
112143

113-
return $tree;
144+
return $node;
114145
}
115146
}

src/DependencyInjection/SymfonyTraceExtension.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
* storage_service: ?string,
4444
* enable_monolog: bool,
4545
* enable_console: bool,
46+
* console: array{
47+
* enabled: bool,
48+
* trace_id: ?string
49+
* },
4650
* enable_messenger: bool,
4751
* enable_twig: bool,
4852
* http_client: array{
@@ -167,11 +171,18 @@ private function configureMonolog(array $mergedConfig, ContainerBuilder $contain
167171
*/
168172
private function configureConsole(array $mergedConfig, ContainerBuilder $container): void
169173
{
170-
if (class_exists(Application::class) === false || $mergedConfig['enable_console'] === false) {
174+
$enabled = $mergedConfig['enable_console'] ?? $mergedConfig['console']['enabled'];
175+
if (class_exists(Application::class) === false || $enabled === false) {
171176
return;
172177
}
173178
$container->register(CommandSubscriber::class)
174-
->setArguments([new Reference(TraceStorageInterface::class), new Reference(TraceServiceInterface::class)])
179+
->setArguments(
180+
[
181+
new Reference(TraceStorageInterface::class),
182+
new Reference(TraceServiceInterface::class),
183+
$mergedConfig['console']['trace_id']
184+
]
185+
)
175186
->setPublic(false)
176187
->addTag('kernel.event_subscriber');
177188
}

src/EventSubscriber/CommandSubscriber.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
*/
1515
final class CommandSubscriber implements EventSubscriberInterface
1616
{
17-
public function __construct(private readonly TraceStorageInterface $storage, private readonly TraceServiceInterface $service)
18-
{
17+
public function __construct(
18+
private readonly TraceStorageInterface $storage,
19+
private readonly TraceServiceInterface $service,
20+
private readonly ?string $traceId,
21+
) {
1922
}
2023

2124
/**
@@ -33,6 +36,12 @@ public function onCommand(): void
3336
return;
3437
}
3538

39+
if ($this->traceId !== null && $this->traceId !== '') {
40+
$this->storage->setTrace($this->service->createTraceFrom($this->traceId));
41+
42+
return;
43+
}
44+
3645
$this->storage->setTrace($this->service->createNewTrace());
3746
}
3847
}

src/Service/TraceContext/TraceContextParser.php

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99

1010
class TraceContextParser
1111
{
12+
public static function isValid(string $traceParent): bool
13+
{
14+
return preg_match('/^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/i', $traceParent) === 1;
15+
}
16+
1217
public static function parseTraceContext(string $traceParent, string $traceState): TraceContext
1318
{
1419
$traceContext = self::parseTraceParent($traceParent);

src/Service/TraceContext/TraceContextService.php

+16-7
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ public function supports(Request $request): bool
2929
return false;
3030
}
3131

32-
$traceParent = $request->headers->get(self::HEADER_TRACEPARENT, '');
33-
34-
return preg_match('/^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/i', $traceParent) === 1;
32+
return TraceContextParser::isValid($request->headers->get(self::HEADER_TRACEPARENT, ''));
3533
}
3634

3735
public function createNewTrace(): TraceContext
@@ -43,10 +41,22 @@ public function createNewTrace(): TraceContext
4341
return $trace;
4442
}
4543

44+
public function createTraceFrom(string $traceParent): TraceContext
45+
{
46+
if (TraceContextParser::isValid($traceParent) === false) {
47+
return $this->createNewTrace();
48+
}
49+
50+
$trace = TraceContextParser::parseTraceContext($traceParent, '');
51+
$trace->setTransactionId($this->generator->generateTransactionId());
52+
53+
return $trace;
54+
}
55+
4656
public function getRequestTrace(Request $request): TraceContext
4757
{
48-
$traceParent = $request->headers->get(self::HEADER_TRACEPARENT, '');
49-
$traceState = $request->headers->get(self::HEADER_TRACESTATE, '');
58+
$traceParent = $request->headers->get(self::HEADER_TRACEPARENT, '');
59+
$traceState = $request->headers->get(self::HEADER_TRACESTATE, '');
5060

5161
$trace = TraceContextParser::parseTraceContext($traceParent, $traceState);
5262
$trace->setTransactionId($this->generator->generateTransactionId());
@@ -66,8 +76,7 @@ public function handleResponse(Response $response, TraceContext $context): void
6676

6777
public function handleClientRequest(TraceContext $trace, string $method, string $url, array $options = []): array
6878
{
69-
$traceParent = $this->renderTraceParent($trace);
70-
$options['headers'][self::HEADER_TRACEPARENT] = $traceParent;
79+
$options['headers'][self::HEADER_TRACEPARENT] = $this->renderTraceParent($trace);
7180

7281
$traceState = $this->renderTraceState($trace);
7382
if ($traceState !== '') {

src/Service/TraceId/TraceIdService.php

+9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ public function createNewTrace(): TraceContext
3737
return $trace;
3838
}
3939

40+
public function createTraceFrom(string $traceId): TraceContext
41+
{
42+
$trace = new TraceContext();
43+
$trace->setTraceId($traceId);
44+
$trace->setTransactionId($this->generator->generateTransactionId());
45+
46+
return $trace;
47+
}
48+
4049
public function getRequestTrace(Request $request): TraceContext
4150
{
4251
$trace = new TraceContext();

src/Service/TraceServiceInterface.php

+4
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111
interface TraceServiceInterface
1212
{
1313
public function supports(Request $request): bool;
14+
1415
public function createNewTrace(): TraceContext;
1516

17+
public function createTraceFrom(string $traceId): TraceContext;
18+
1619
public function getRequestTrace(Request $request): TraceContext;
20+
1721
public function handleResponse(Response $response, TraceContext $context): void;
1822

1923
public function handleClientRequest(TraceContext $trace, string $method, string $url, array $options = []): array;

tests/Functional/App/config/config.yml

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
parameters:
22
secret: dev
33
locale: en
4+
env(TRACE_ID): null
45

56
framework:
67
test: ~

tests/Functional/App/config/traceid.yml

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ symfony_trace:
66
trust_request_header: true
77
storage_service: request.id.storage
88
enable_messenger: true
9+
console:
10+
enabled: true
11+
trace_id: '%env(TRACE_ID)%'
912
http_client:
1013
enabled: true
1114
header: Trace-Id

tests/Functional/ApplicationTest.php

+20-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
#[CoversNothing]
1919
class ApplicationTest extends AbstractKernelTestCase
2020
{
21+
protected function tearDown(): void
22+
{
23+
parent::tearDown();
24+
putenv('TRACE_ID=');
25+
}
26+
2127
/**
2228
* @throws Exception
2329
*/
@@ -26,9 +32,7 @@ class ApplicationTest extends AbstractKernelTestCase
2632
#[TestWith([Configuration::TRACEMODE_TRACECONTEXT, 'request.id.storage'])]
2733
public function testCommandShouldSetTrace(string $traceMode, string $storageServiceId): void
2834
{
29-
$application = new Application(
30-
static::bootKernel(['environment' => 'test', 'debug' => false, 'tracemode' => $traceMode])
31-
);
35+
$application = new Application(static::bootKernel(['environment' => 'test', 'debug' => false, 'tracemode' => $traceMode]));
3236

3337
$storage = self::getContainer()->get($storageServiceId);
3438
static::assertInstanceOf(TraceStorageInterface::class, $storage);
@@ -41,4 +45,17 @@ public function testCommandShouldSetTrace(string $traceMode, string $storageServ
4145
static::assertNotNull($storage->getTraceId());
4246
static::assertNotNull($storage->getTransactionId());
4347
}
48+
49+
public function testCommandShouldTakeTraceIdFromEnv(): void
50+
{
51+
putenv('TRACE_ID=test-trace-id');
52+
53+
$kernel = static::bootKernel(['environment' => 'test', 'debug' => false, 'tracemode' => Configuration::TRACEMODE_TRACEID]);
54+
55+
$storage = self::getContainer()->get('request.id.storage');
56+
static::assertInstanceOf(TraceStorageInterface::class, $storage);
57+
58+
static::assertSame(Command::SUCCESS, (new Application($kernel))->doRun(new ArrayInput(['help']), new NullOutput()));
59+
static::assertSame('test-trace-id', $storage->getTraceId());
60+
}
4461
}

tests/Unit/EventSubscriber/CommandSubscriberTest.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ protected function setUp(): void
2626

2727
public function testOnCommandTrace(): void
2828
{
29-
$subscriber = new CommandSubscriber($this->storage, $this->service);
29+
$subscriber = new CommandSubscriber($this->storage, $this->service, null);
3030

3131
$trace = new TraceContext();
3232
$trace->setTraceId('trace-id');
@@ -39,14 +39,25 @@ public function testOnCommandTrace(): void
3939

4040
public function testOnCommandStorageHasTrace(): void
4141
{
42-
$subscriber = new CommandSubscriber($this->storage, $this->service);
42+
$subscriber = new CommandSubscriber($this->storage, $this->service, null);
4343

4444
$this->service->expects(static::never())->method('createNewTrace');
4545
$this->storage->expects(static::once())->method('getTraceId')->willReturn("abc123");
4646

4747
$subscriber->onCommand();
4848
}
4949

50+
public function testOnCommandPresetTraceId(): void
51+
{
52+
$subscriber = new CommandSubscriber($this->storage, $this->service, 'test-trace-id');
53+
54+
$this->storage->expects(static::once())->method('getTraceId')->willReturn(null);
55+
$this->service->expects(static::once())->method('createTraceFrom')->with('test-trace-id');
56+
$this->storage->expects(static::once())->method('setTrace');
57+
58+
$subscriber->onCommand();
59+
}
60+
5061
public function testGetSubscribedEvents(): void
5162
{
5263
static::assertSame([ConsoleEvents::COMMAND => ['onCommand', 999]], CommandSubscriber::getSubscribedEvents());

tests/Unit/Service/TraceContext/TraceContextParserTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
#[CoversClass(TraceContextParser::class)]
1212
class TraceContextParserTest extends TestCase
1313
{
14+
public function testIsValid(): void
15+
{
16+
static::assertTrue(TraceContextParser::isValid('00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00'));
17+
static::assertFalse(TraceContextParser::isValid('invalid'));
18+
}
19+
1420
public function testParseTraceContext(): void
1521
{
1622
$traceContext = TraceContextParser::parseTraceContext(

0 commit comments

Comments
 (0)