- Adds configuration for the OpenTelemetry php SDK to a Symfony project (^4.4|^5.3|^6.0).
- Populates service objects in the Symfony DI container based on given configuration.
- Autoinstrumentation of Symfony projects will be addressed in an upcoming
InstrumentationBundle
, which will sit on top of theSdkBundle
.
Notice: For now this bundle covers the
trace
andresource
parts of the OpenTelemetry specification and PHP library withmetrics
soon™ to come andlogging
, once the appropriate specification is marked as stable and the PHP library implements it.
TLDR: If you just want to give this bundle a try, and see how it works, you will find a link to an example symfony application using the bundle at the very bottom of this doc.
- An existing Symfony project (^4.4|^5.3|^6.0), or create a new project.
- An installation of a trace collector supported by the OpenTelemetry php library
- Some knowledge of the OpenTelemetry specification and
PHP library would be helpful, however both, the PHP library
and this bundle, aim to abstract the complexity of the specification details away. We assume, you have a basic understanding on
how
distributed tracing
works in general. You can find an overview of the terms used in the specification in its glossary.
If you don't have any collector installation at hand, you can use docker-compose
and create a docker-compose.yaml
file in the root of your project with the content as follows:
version: '3.7'
services:
jaeger:
image: jaegertracing/all-in-one
environment:
COLLECTOR_ZIPKIN_HTTP_PORT: 9412
ports:
- "9412:9412"
- "16686:16686"
Run docker-compose up -d
and you will have an local installation of Jaeger to collect your data.
Your local instance will listen listen on the endpoint http://localhost:9412/api/v2/spans for data and you can access the GUI
at http://localhost:16686/. (Keep in mind, if you define you php service in docker-compose as well, you will have to change
the host from localhost
to jaeger
in the configurations described below)
Take a look at the documentation of the PHP library on how to install its dependencies.
For now the bundle is only installable as part of the OpenTelemetry opentelemetry-php-contrib package.
The recommended way to install the library is through Composer:
-
Install the composer package using Composer's installation instructions.
-
Add
"minimum-stability": "dev",
"prefer-stable": true,
To your project's composer.json
file, as this utility has not reached a stable release status yet.
- Install the package with composer:
$ composer require open-telemetry/opentelemetry-php-contrib
If you have symfony/flex installed in your project, the bundle should be automatically be registered in your project's
bundles.php
file. If for some reason the bundle could not be automatically detected, add the following line in
bundles.php
file of your project
// config/bundles.php
return [
// ...
OpenTelemetry\Symfony\OtelSdkBundle\OtelSdkBundle::class => ['all' => true],
// ...
];
Notice: Following examples use YAML as the config format. You can of course use XML and PHP as well to configure this bundle. If you are not familiar with how XML or PHP configuration in Symfony works, take a look at the documentation.
Now that the bundles is downloaded and registered, you have to add some configuration.
Create a file called otel_sdk.yaml
in your project's config/packges
directory (Once the symfony/flex recipe
for
this bundle is registered in the official recipe contrib repo, this file will be automatically created for you).
A minimal configuration for the bundle looks like this:
otel_sdk:
resource:
attributes:
service.name: "OtelBundle Demo app"
The resource's service.name
attribute is the only mandatory configuration, however in order for the bundle to be useful,
you need to configure at least one Trace Exporter, which can talk to an appropriate Trace Collector.
Assuming you installed Jaeger as described above, your configuration would look this (using a DSN):
otel_sdk:
resource:
attributes:
service.name: "OtelBundle Demo app"
trace:
exporters: jaeger+http://localhost:9412/api/v2/spans
or this (using type
and endpoint url
:
otel_sdk:
resource:
attributes:
service.name: "OtelBundle Demo app"
trace:
exporters:
- type: jaeger
url: http://localhost:9412/api/v2/spans
If you have multiple Exporters/Collectors, you can just add them like this (using DSN):
otel_sdk:
resource:
attributes:
service.name: "OtelBundle Demo app"
trace:
exporters:
- jaeger+http://localhost:9412/api/v2/spans
- zipkin+http://localhost:9411/api/v2/spans
Or equivalent to the type
and endpoint url
example above.
2.3.2. Further Configuration
The bundle comes with advanced configuration for (almost) all user facing parts of the OpenTelemetry php SDK, which will be documented here, soon. For now, please refer to the configurations the bundle is tested against:
Notice: The examples assume you are running Symfony in a single-threaded runtime like PHP-FPM and/or a "traditional" web server. If you are using a more modern multi-threaded or event-loop based runtime like Roadrunner, Swoole, Swow, ReactPHP, Amp, Revolt, Workerman, etc., the examples won't necessarily work. We will address how to use the bundle with said runtimes, once the bundle is better tested against them.
The bundle populates all needed (and configured) services to allow distributed tracing with the SDK in Symfony's DI container, however the intended usage according to the specification is to only interact with an instance of a Tracer or TracerProvider as your entry point. In a programmatic setup you get an instance of a Tracer by calling the method getTracer on the TracerProvider. The bundle uses the TracerProvider as a factory for the Tracer instance, so if you don't need any of the other features of the TracingProvider, you can simply work with a Tracer instance. For the matter of simplicity, we will use the Tracer instance in this example. You can find an advanced example on how to use the TracerProvider in the demo application (link below). Also keep in mind, the examples are not meant to show 'best practices' on how to use or work with the SDK and/or tracing, they are just a way to get you started.
- As an entrypoint for our tracing we can create a Listener or Subscriber which will listen to events of the HTTPKernel. For this example we will create a Subscriber, since they require less (or actually none) configuration and are more flexible.
- With
autowire
andautoconfigure
activated in your Symfony configuration (should be on per default), all you need is to type hint the Tracer in your constructor and Symfony will automatically inject the Tracer instance, when creating the Listener service. - Once we have the Tracer instance at hand, we can create trace spans from it.
So our Listener class could look like this:
// src/EventSubscriber/TracingKernelSubscriber.php
<?php
namespace App\EventSubscriber;
use OpenTelemetry\API\Trace\SpanInterface;
use OpenTelemetry\SDK\Trace\Tracer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
class TracingKernelSubscriber implements EventSubscriberInterface
{
private Tracer $tracer;
private ?SpanInterface $mainSpan = null;
public function __construct(Tracer $tracer)
{
// store a reference to the Tracer instance in case we want to create
// more spans on different events (not covered in this example)
$this->tracer = $tracer;
// Create our main span and activate it
$this->mainSpan = $tracer->spanBuilder('main')->startSpan();
$this->mainSpan->activate();
}
public function onTerminateEvent(TerminateEvent $event): void
{
// end our main span once the request has been processed and the kernel terminates.
$this->mainSpan->end();
}
public static function getSubscribedEvents(): array
{
// return the subscribed events, their methods and priorities
// use a very low negative integer for the priority, so the listener
// will be the last one to be called.
return [
KernelEvents::TERMINATE => [['onTerminateEvent', -10000]],
];
}
}
With this Listener created, you should already see a single span in your tracing collector (Jaeger, etc.) once you request any page of your Symfony application.
Notice: In above example the first span is created at the time the Listener is instantiated. There is a latency between the request coming in and the Listener being created, which is the time it takes for the HttpKernel to boot. So the trace does not cover the whole time it took for the request to be processed. There are ways to address this issue without tempering with the front controller (index.php). In essence, one can query the Kernel instance for its instantiation time and retrospectively adjust the set start time of the first span. The InstrumentationBundle will take care of this automatically.
In the same way we just used a type hint to inject the Tracer into the Subscriber to record certain operations of our business logic.
Notice: While you can inject the Tracer into the Controller, it is not a good idea to do something like that in a "real" application. While metrics are important, they are cross-cutting concerns and your business logic should not know about them, or even depend on them. Also, business logic should not depend on 3rd party code in the first place. So in reality you should create a service and/or custom Event/Listener to interact with the Tracer and create an adapter for the SDK.
With above's notice out of the way, the Controller could look like this:
// src/Controller/HelloController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use OpenTelemetry\SDK\Trace\Tracer;
class HelloController
{
private Tracer $tracer;
public function __construct(Tracer $tracer)
{
$this->tracer = $tracer;
}
/**
* @Route("/hello", name="hello")
*/
public function index(): Response
{
$span = $this->tracer->spanBuilder(__METHOD__)->startSpan();
// DO stuff
$span->end();
return new Response('Hello')
}
}
Now when you request the appropriate page (e.: http://localhost/hello
), you should be able to see the main span and
the child span in your tracing collector.
For further usage of spans (events, attributes, etc.), please consult the documentation of the PHP library or take a look at the demo application below.
You can find a demo application using the SdkBundle here. The demo extends the examples given above and comes with a docker-compose setup, so it is easy to try out.