Skip to content

feat(laravel): inject trace context into outbound HTTP client requests#656

Open
zigzagdev wants to merge 7 commits into
open-telemetry:mainfrom
zigzagdev:fix/laravel-outbound-trace-propagation
Open

feat(laravel): inject trace context into outbound HTTP client requests#656
zigzagdev wants to merge 7 commits into
open-telemetry:mainfrom
zigzagdev:fix/laravel-outbound-trace-propagation

Conversation

@zigzagdev

Copy link
Copy Markdown
Contributor

Summary

Motivation

ClientRequestWatcher records spans for outbound Illuminate\Http\Client requests, but never injected traceparent/tracestate headers into them.
Since the watcher only listens on the RequestSending event, which carries an immutable PSR-7 request that never reaches the request Guzzle actually sends, distributed tracing was completely broken for any downstream service called via Laravel's HTTP client — there was no way to link the receiving service's trace back to the calling trace.

What I have done

  • Added RequestPropagationSetter (src/Propagators/RequestPropagationSetter.php), a PropagationSetterInterfaceimplementation for PSR-7 RequestInterface, following the same pattern as the existing ResponsePropagationSetter and the other PropagationSetterInterface implementations across this monorepo.
  • Added a hook on Illuminate\Http\Client\PendingRequest::runBeforeSendingCallbacks() (src/Hooks/Illuminate/Http/Client/PendingRequest.php), the point where Laravel finalizes the PSR-7 request just
    before handing it to Guzzle. Injects the active span's trace context into the request headers there, since that's
    the last point the actual outgoing request can still be mutated.
  • Registered the new hook in LaravelInstrumentation.php.
  • Added test_it_injects_trace_context_into_outbound_requests to ClientTest.php, asserting the injected
    traceparent header matches an active parent span's trace/span id.

Test Results

vendor/bin/phpunit tests/Integration/Http/ClientTest.php
PHPUnit 9.6.34 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.1.34
Configuration: /usr/src/myapp/src/Instrumentation/Laravel/phpunit.xml.dist

...                                                                 3 / 3 (100%)

Time: 00:01.066, Memory: 24.00 MB
OK (3 tests, 17 assertions)

Setter for injecting W3C trace-context headers into an immutable PSR-7
RequestInterface, following the same assert()+singleton pattern as the
other PropagationSetterInterface implementations in this monorepo.
Hooks PendingRequest::runBeforeSendingCallbacks(), where Laravel
finalizes the PSR-7 request just before handing it to Guzzle, and
injects the active span's trace context into the request headers.

Previously no traceparent/tracestate was ever sent, so downstream
services had no way to link back into the calling trace.
@zigzagdev
zigzagdev requested a review from a team as a code owner July 11, 2026 13:46
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.00000% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.02%. Comparing base (03b5f93) to head (fb70805).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
...rc/Hooks/Illuminate/Http/Client/PendingRequest.php 50.00% 8 Missing ⚠️
...rumentation/Laravel/src/LaravelInstrumentation.php 75.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #656      +/-   ##
============================================
- Coverage     80.09%   80.02%   -0.07%     
- Complexity     1597     1605       +8     
============================================
  Files           118      120       +2     
  Lines          6179     6204      +25     
============================================
+ Hits           4949     4965      +16     
- Misses         1230     1239       +9     
Flag Coverage Δ
Context/Swoole 0.00% <ø> (ø)
Exporter/Instana 49.80% <ø> (ø)
Instrumentation/AwsSdk 82.14% <ø> (ø)
Instrumentation/CakePHP 20.42% <ø> (ø)
Instrumentation/CodeIgniter 79.31% <ø> (ø)
Instrumentation/Curl 86.88% <ø> (ø)
Instrumentation/Doctrine 92.82% <ø> (ø)
Instrumentation/ExtAmqp 88.80% <ø> (ø)
Instrumentation/Guzzle 79.76% <ø> (ø)
Instrumentation/HttpAsyncClient 78.94% <ø> (ø)
Instrumentation/HttpConfig 28.76% <ø> (ø)
Instrumentation/IO 0.00% <ø> (ø)
Instrumentation/Laravel 75.37% <64.00%> (-0.35%) ⬇️
Instrumentation/Magento2 88.12% <ø> (ø)
Instrumentation/MongoDB 76.84% <ø> (ø)
Instrumentation/OpenAIPHP 86.71% <ø> (ø)
Instrumentation/PostgreSql 91.36% <ø> (ø)
Instrumentation/Psr14 77.41% <ø> (ø)
Instrumentation/Psr15 89.74% <ø> (ø)
Instrumentation/Psr16 97.43% <ø> (ø)
Instrumentation/Psr18 79.41% <ø> (ø)
Instrumentation/Psr6 97.56% <ø> (ø)
Instrumentation/ReactPHP 99.41% <ø> (ø)
Instrumentation/Session 94.28% <ø> (ø)
Instrumentation/Slim 84.21% <ø> (ø)
Propagation/CloudTrace 90.69% <ø> (ø)
Propagation/Instana 98.07% <ø> (ø)
Propagation/ServerTiming 94.73% <ø> (ø)
Propagation/TraceResponse 94.73% <ø> (ø)
ResourceDetectors/Azure 91.66% <ø> (ø)
ResourceDetectors/DigitalOcean 100.00% <ø> (ø)
Sampler/Xray 78.38% <ø> (ø)
Shims/OpenTracing 92.99% <ø> (ø)
SqlCommenter 95.58% <ø> (ø)
Utils/Test 87.79% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ravel/src/Propagators/RequestPropagationSetter.php 100.00% <100.00%> (ø)
...rumentation/Laravel/src/LaravelInstrumentation.php 13.04% <75.00%> (+13.04%) ⬆️
...rc/Hooks/Illuminate/Http/Client/PendingRequest.php 50.00% <50.00%> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 03b5f93...fb70805. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ChrisLightfootWild ChrisLightfootWild left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should opt into this behaviour rather than it be the default. There is no mechanism to prevent leaking to external endpoints (we do not know what a developer may be sending in baggage etc)

The target of an outbound Http::client request may be a third-party
service outside the application's control, so trace context (and, in
future, baggage) should not be sent there by default. Gate injection
behind OTEL_PHP_INSTRUMENTATION_LARAVEL_HTTP_CLIENT_PROPAGATION_ENABLED,
off unless explicitly opted into.
@zigzagdev

Copy link
Copy Markdown
Contributor Author

@ChrisLightfootWild

I fixed it.

@PuvaanRaaj PuvaanRaaj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the updated opt-in propagation implementation and its Laravel CI results. The opt-in addresses the earlier leakage concern, but the current head has a directly introduced Phan failure that blocks the quality matrix.

return $params;
}

TraceContextPropagator::getInstance()->inject($request, RequestPropagationSetter::instance(), Context::getCurrent());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This call currently fails the Laravel quality jobs with PhanAccessMethodInternal: RequestPropagationSetter is marked @internal and is being called from the Hooks subnamespace. Please adjust the annotation/scope or use the project’s established suppression pattern so the required quality checks pass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@PuvaanRaaj

Suppressed via the same @phan-suppress-next-line PhanAccessMethodInternal pattern used in Kernel.php, rather than removing @internal from

▎ RequestPropagationSetter (it's intentionally internal). phan passes locally now.

fix commit

@zigzagdev
zigzagdev requested a review from PuvaanRaaj July 22, 2026 00:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants