Skip to content

Commit 40e0510

Browse files
committed
Add initial documentation for the SdkBundle
1 parent 39c8df6 commit 40e0510

File tree

1 file changed

+372
-0
lines changed
  • instrumentation/symfony/OtelSdkBundle

1 file changed

+372
-0
lines changed
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
# OpenTelemetry Symfony SdkBundle
2+
3+
- Adds configuration for the [OpenTelemetry php SDK](https://github.com/open-telemetry/opentelemetry-php-contrib) to a Symfony project (^4.4|^5.3|^6.0).
4+
- Populates service objects in the Symfony DI container based on given configuration.
5+
- Autoinstrumentation of Symfony projects will be addressed in an upcoming `InstrumentationBundle`, which
6+
will sit on top of the `SdkBundle`.
7+
8+
> Notice: For now this bundle covers the `trace` and `resource` parts of the OpenTelemetry
9+
> [specification](https://github.com/open-telemetry/opentelemetry-specification) and
10+
> [PHP library](https://github.com/open-telemetry/opentelemetry-php) with `metrics` soon™ to come and `logging`, once the
11+
> appropriate specification is marked as stable and the PHP library implements it.
12+
13+
**TLDR: If you just want to give this bundle a try, and see how it works, you will find a link to an example
14+
symfony application using the bundle at the very bottom of this doc.**
15+
16+
## 1. Prerequisites
17+
18+
- An existing Symfony project (^4.4|^5.3|^6.0), or create a [new project](https://symfony.com/doc/current/setup.html).
19+
- An installation of a trace collector supported by the [OpenTelemetry php library](https://github.com/open-telemetry/opentelemetry-php-contrib)
20+
- Some knowledge of the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification) and
21+
[PHP library](https://github.com/open-telemetry/opentelemetry-php) would be helpful, however both, the PHP library
22+
and this bundle, aim to abstract the complexity of the specification details away. We assume, you have a basic understanding on
23+
how `distributed tracing` works in general. You can find an overview of the terms used in the specification
24+
in its [glossary](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md).
25+
26+
If you don't have any collector installation at hand, you can use [docker-compose](https://docs.docker.com/compose/)
27+
and create a `docker-compose.yaml` file in the root of your project with the content as follows:
28+
29+
```yaml
30+
version: '3.7'
31+
services:
32+
jaeger:
33+
image: jaegertracing/all-in-one
34+
environment:
35+
COLLECTOR_ZIPKIN_HTTP_PORT: 9412
36+
ports:
37+
- "9412:9412"
38+
- "16686:16686"
39+
```
40+
41+
Run `docker-compose up -d` and you will have an local installation of [Jaeger](https://www.jaegertracing.io/) to collect your data.
42+
Your local instance will listen listen on the endpoint http://localhost:9412/api/v2/spans for data and you can access the GUI
43+
at http://localhost:16686/. (Keep in mind, if you define you php service in docker-compose as well, you will have to change
44+
the host from `localhost` to `jaeger` in the configurations described below)
45+
46+
## 2. Installation
47+
48+
### 2.1. Install PHP library/SDK dependencies
49+
50+
The [PHP library](https://github.com/open-telemetry/opentelemetry-php) requires a PHP version of ^7.4 or ^8.0.
51+
52+
#### 2.1.1. Install PSR17/18 implementations
53+
54+
The [PHP library](https://github.com/open-telemetry/opentelemetry-php) has a dependency on both a [PSR17](https://www.php-fig.org/psr/psr-17/)
55+
and a [PSR18](https://www.php-fig.org/psr/psr-18/) implementation. To be on the safe side of possible future upgrades
56+
of the PHP library, you should consider installing a `php-http/async-client` implementation for PSR18. This will also give you greater
57+
control on how to configure/extend your http client by using [HTTPLUG/PHP-HTTP](https://docs.php-http.org/en/latest/index.html).
58+
You can find appropriate composer packages implementing given standards on [packagist.org](https://packagist.org/).
59+
Follow [this link](https://packagist.org/providers/psr/http-factory-implementation) to find a `PSR17 (HTTP factories)` implementation,
60+
and [this link](https://packagist.org/providers/php-http/async-client-implementation) to find a `php-http/async-client` implementation.
61+
62+
> Notice: if you already have the `symfony/http-client` package installed, you are already covered in terms of the
63+
> `php-http/async-client` implementation. A popular choice of a `PSR17 (HTTP factories)` implementation to use with
64+
> Symfony is `nyholm/psr7`, as it is both lightweight and automatically registers its factories as services in Symfony
65+
> (if you have `symfony/flex` installed and working, that is).
66+
67+
#### 2.1.1. Install PHP ext-grpc
68+
69+
The [PHP library](https://github.com/open-telemetry/opentelemetry-php) has a dependency on the PHP gRPC [extension](https://pecl.php.net/package/gRPC).
70+
71+
There are basically three ways to install the gRPC extension which will be described below. Keep in mind, that whatever way
72+
to install the extension you choose, the compilation can take up to 10-15 minutes. (As an alternative you can search for
73+
a pre-compiled extension binary for your OS and PHP version)
74+
75+
1. **Installation with pecl installer** (which should come with your PHP installation):
76+
77+
```bash
78+
[sudo] pecl install grpc
79+
```
80+
81+
2. **Installation with pickle installer** (which you can find [here](https://github.com/FriendsOfPHP/pickle)):
82+
83+
```bash
84+
[sudo] pickle install grpc
85+
```
86+
87+
3. **Manually compiling the extension**, which is not really complicated either, but you should know
88+
what you are doing, so we won't cover it here.
89+
90+
> Notice: The artifact of the gRPC extension can be as large as 100mb (!!!), there are 'hacks' to reduce that size,
91+
> which you can find [in this thread](https://github.com/grpc/grpc/issues/23626). Use at your own risk.
92+
93+
> Notice: A lot of providers and systems (OpenShift's S2I for PHP for example), etc. still regard the grpc extension
94+
> as kind of 'exotic', or in other words the extension is not or cannot be installed.
95+
> The [OpenTelemetry PHP library](https://github.com/open-telemetry/opentelemetry-php) (as for now) only needs this
96+
> extension, when you want to use the `OTLP gRPC Exporter`. If you use any of the current other (HTTP based exporters),
97+
> eg. jaeger or zipkin, you actually don't need the PHP grpc extension to be present. A 'trick' to install this bundle
98+
> and the SDK without the grpc extension is to add an `--ignore-platform-reqs=ext-grpc` option to all of your composer
99+
> calls, eg.: `composer update --ignore-platform-reqs=ext-grpc`. Another way is to add a `plaform` entry to your
100+
> composer.json file to 'pretend' the grpc extension is installed, which you can find in the [composer documentation](https://getcomposer.org/doc/06-config.md#platform).
101+
102+
### 2.2. Install the Bundle
103+
104+
For now the bundle is only installable as part of the OpenTelemetry [opentelemetry-php-contrib](https://github.com/open-telemetry/opentelemetry-php-contrib)
105+
package.
106+
107+
The recommended way to install the library is through [Composer](http://getcomposer.org):
108+
109+
1. Install the composer package using [Composer's installation instructions](https://getcomposer.org/doc/00-intromd#installation-linux-unix-macos).
110+
111+
2. Add
112+
```bash
113+
"minimum-stability": "dev",
114+
"prefer-stable": true,
115+
"repositories": [
116+
{
117+
"type": "vcs",
118+
"url": "https://github.com/open-telemetry/opentelemetry-php-contrib"
119+
}
120+
],
121+
```
122+
123+
To your project's `composer.json` file, as this utility has not reached a stable release status yet,
124+
and is not yet registered on packagist.org
125+
126+
3. Install the dependency with composer:
127+
128+
```bash
129+
$ composer require open-telemetry/opentelemetry-php-contrib
130+
```
131+
132+
133+
### 2.3. Enable the Bundle
134+
135+
If you have symfony/flex installed in your project, the bundle should be automatically be registered in your project's
136+
`bundles.php` file. If for some reason the bundle could not be automatically detected, add the following line in
137+
`bundles.php` file of your project
138+
139+
````php
140+
// config/bundles.php
141+
142+
return [
143+
// ...
144+
OpenTelemetry\Instrumentation\Symfony\OtelSdkBundle\OtelSdkBundle::class => ['all' => true],
145+
// ...
146+
];
147+
````
148+
149+
### 2.4. Configure the Installed Bundle
150+
151+
#### 2.3.1. Minimal Configuration
152+
153+
*Notice: Following examples use YAML as the config format. You can of course use XML and PHP as well to configure
154+
this bundle. If you are not familiar with how XML or PHP configuration in Symfony works, take a look at the
155+
[documentation](https://symfony.com/doc/current/configuration.html#configuration-formats).*
156+
157+
Now that the bundles is downloaded and registered, you have to add some configuration.
158+
Create a file called `otel_sdk.yaml` in your project's `config/packges` directory (Once the `symfony/flex recipe` for
159+
this bundle is registered in the official [recipe contrib repo](https://github.com/symfony/recipes-contrib), this file will be automatically created for you).
160+
A minimal configuration for the bundle looks like this:
161+
162+
````yaml
163+
otel_sdk:
164+
resource:
165+
attributes:
166+
service.name: "OtelBundle Demo app"
167+
````
168+
169+
The resource's `service.name` attribute is the only mandatory configuration, however in order for the bundle to be useful,
170+
you need to configure at least one Trace Exporter, which can talk to an appropriate Trace Collector.
171+
172+
#### 2.3.1. Configuring a Trace Exporter
173+
174+
Assuming you installed Jaeger as described above, your configuration would look this (using a DSN):
175+
176+
````yaml
177+
otel_sdk:
178+
resource:
179+
attributes:
180+
service.name: "OtelBundle Demo app"
181+
trace:
182+
exporters: jaeger+http://localhost:9412/api/v2/spans
183+
````
184+
185+
or this (using `type` and endpoint `url`:
186+
187+
````yaml
188+
otel_sdk:
189+
resource:
190+
attributes:
191+
service.name: "OtelBundle Demo app"
192+
trace:
193+
exporters:
194+
- type: jaeger
195+
url: http://localhost:9412/api/v2/spans
196+
````
197+
198+
If you have multiple Exporters/Collectors, you can just add them like this (using DSN):
199+
200+
````yaml
201+
otel_sdk:
202+
resource:
203+
attributes:
204+
service.name: "OtelBundle Demo app"
205+
trace:
206+
exporters:
207+
- jaeger+http://localhost:9412/api/v2/spans
208+
- zipkin+http://localhost:9411/api/v2/spans
209+
````
210+
211+
Or equivalent to the `type` and endpoint `url` example above.
212+
213+
**2.3.2. Further Configuration**
214+
215+
The bundle comes with advanced configuration for (almost) all user facing parts of the
216+
[OpenTelemetry php SDK](https://github.com/open-telemetry/opentelemetry-php-contrib), which will be documented here, soon.
217+
For now, please refer to the configurations the bundle is tested against:
218+
219+
- [minimal](/tests/integration/instrumentation/Symfony/OtelSdkBundle/DependencyInjection/config/minimal/config.yaml)
220+
- [simple](/tests/integration/instrumentation/Symfony/OtelSdkBundle/DependencyInjection/config/simple/config.yaml)
221+
- [resource](/tests/integration/instrumentation/Symfony/OtelSdkBundle/DependencyInjection/config/resource/config.yaml)
222+
- [samplers](/tests/integration/instrumentation/Symfony/OtelSdkBundle/DependencyInjection/config/sampler/config.yaml)
223+
- [span](/tests/integration/instrumentation/Symfony/OtelSdkBundle/DependencyInjection/config/span/config.yaml)
224+
- [exporters](/tests/integration/instrumentation/Symfony/OtelSdkBundle/DependencyInjection/config/exporters/config.yaml)
225+
- [full](/tests/integration/instrumentation/Symfony/OtelSdkBundle/DependencyInjection/config/full/config.yaml)
226+
- [disabled](/tests/integration/instrumentation/Symfony/OtelSdkBundle/DependencyInjection/config/disabled/config.yaml)
227+
228+
## 3. Usage
229+
230+
> Notice: The examples assume you are running Symfony in a single-threaded [runtime](https://github.com/php-runtime/runtime) like PHP-FPM and/or a "traditional"
231+
> web server. If you are using a more modern multi-threaded or event-loop based [runtime](https://github.com/php-runtime/runtime)
232+
> like [Roadrunner](https://roadrunner.dev/), [Swoole](https://www.swoole.co.uk/), [Swow](https://github.com/swow/swow),
233+
> [ReactPHP](https://reactphp.org/), [Amp](https://amphp.org/), [Revolt](https://revolt.run/), [Workerman](https://github.com/walkor/Workerman),
234+
> etc., the examples won't necessarily work. We will address how to use the bundle with said runtimes, once the bundle is
235+
> better tested against them.
236+
237+
238+
The bundle populates all needed (and configured) services to allow distributed tracing with the SDK in Symfony's
239+
DI container, however the intended usage according to the specification is to only interact with an instance of a [Tracer](https://github.com/open-telemetry/opentelemetry-php/blob/main/src/SDK/Trace/Tracer.php)
240+
or [TracerProvider](https://github.com/open-telemetry/opentelemetry-php/blob/main/src/SDK/Trace/TracerProvider.php) as your entry point.
241+
In a programmatic setup you get an instance of a Tracer by calling the method [getTracer](https://github.com/open-telemetry/opentelemetry-php/blob/main/src/SDK/Trace/TracerProvider.php#L58)
242+
on the TracerProvider. The bundle uses the TracerProvider as a factory for the Tracer instance, so if you don't need any
243+
of the other features of the TracingProvider, you can simply work with a Tracer instance. For the matter of simplicity, we
244+
will use the Tracer instance in this example. You can find an advanced example on how to use the TracerProvider in the demo
245+
application (link below). Also keep in mind, the examples are not meant to show 'best practices' on how to use or work
246+
with the SDK and/or tracing, they are just a way to get you started.
247+
248+
### 3.1. Setup up a Kernel Listener or Subscriber
249+
- As an entrypoint for our tracing we can create a [Listener or Subscriber](https://symfony.com/doc/current/event_dispatcher.html)
250+
which will listen to [events of the HTTPKernel](https://symfony.com/doc/current/reference/events.html). For this example
251+
we will create a Subscriber, since they require less (or actually none) configuration and are more flexible.
252+
- With `autowire` and `autoconfigure` activated in your Symfony configuration (should be on per default), all you need is to type
253+
hint the [Tracer](https://github.com/open-telemetry/opentelemetry-php/blob/main/src/SDK/Trace/Tracer.php) in your constructor
254+
and Symfony will automatically inject the Tracer instance, when creating the Listener service.
255+
- Once we have the Tracer instance at hand, we can create trace spans from it.
256+
257+
So our Listener class could look like this:
258+
259+
```php
260+
// src/EventSubscriber/TracingKernelSubscriber.php
261+
<?php
262+
263+
namespace App\EventSubscriber;
264+
265+
use OpenTelemetry\API\Trace\SpanInterface;
266+
use OpenTelemetry\SDK\Trace\Tracer;
267+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
268+
use Symfony\Component\HttpKernel\KernelEvents;
269+
use Symfony\Component\HttpKernel\Event\TerminateEvent;
270+
271+
class TracingKernelSubscriber implements EventSubscriberInterface
272+
{
273+
private Tracer $tracer;
274+
private ?SpanInterface $mainSpan = null;
275+
276+
public function __construct(Tracer $tracer)
277+
{
278+
// store a reference to the Tracer instance in case we want to create
279+
// more spans on different events (not covered in this example)
280+
$this->tracer = $tracer;
281+
282+
// Create our main span and activate it
283+
$this->mainSpan = $tracer->spanBuilder('main')->startSpan();
284+
$this->mainSpan->activate();
285+
}
286+
287+
public function onTerminateEvent(TerminateEvent $event): void
288+
{
289+
// end our main span once the request has been processed and the kernel terminates.
290+
$this->mainSpan->end();
291+
}
292+
293+
public static function getSubscribedEvents(): array
294+
{
295+
// return the subscribed events, their methods and priorities
296+
// use a very low negative integer for the priority, so the listener
297+
// will be the last one to be called.
298+
return [
299+
KernelEvents::TERMINATE => [['onTerminateEvent', -10000]],
300+
];
301+
}
302+
}
303+
304+
```
305+
306+
With this Listener created, you should already see a single span in your tracing collector (Jaeger, etc.) once you
307+
request any page of your Symfony application.
308+
309+
> Notice: In above example the first span is created at the time the Listener is instantiated. There is a
310+
> latency between the request coming in and the Listener being created, which is the time it takes for the HttpKernel
311+
> to boot. So the trace does not cover the whole time it took for the request to be processed. There are ways to address
312+
> this issue without tempering with the front controller (index.php). In essence, one can query the Kernel instance for
313+
> its instantiation time and retrospectively adjust the set start time of the first span. The InstrumentationBundle will take care of
314+
> this automatically.
315+
316+
### 3.1. Create sub spans in the Controller
317+
318+
In the same way we just used a type hint to inject the Tracer into the Subscriber to record certain operations of our
319+
business logic.
320+
> Notice: While you can inject the Tracer into the Controller, it is not a good idea to do something like that in a "real"
321+
> application. While metrics are important, they are cross-cutting concerns and your business logic should not know about
322+
> them, or even depend on them. Also, business logic should not depend on 3rd party code in the first place. So in
323+
> reality you should create a service and/or custom Event/Listener to interact with the Tracer and create an adapter for
324+
> the SDK.
325+
326+
With above's notice out of the way, the Controller could look like this:
327+
328+
```php
329+
// src/Controller/HelloController.php
330+
namespace App\Controller;
331+
332+
use Symfony\Component\HttpFoundation\Response;
333+
use OpenTelemetry\SDK\Trace\Tracer;
334+
335+
class HelloController
336+
{
337+
private Tracer $tracer;
338+
339+
public function __construct(Tracer $tracer)
340+
{
341+
$this->tracer = $tracer;
342+
}
343+
344+
/**
345+
* @Route("/hello", name="hello")
346+
*/
347+
public function index(): Response
348+
{
349+
$span = $this->tracer->spanBuilder(__METHOD__)->startSpan();
350+
351+
// DO stuff
352+
353+
$span->end();
354+
355+
return new Response('Hello')
356+
}
357+
}
358+
359+
```
360+
361+
Now when you request the appropriate page (e.: `http://localhost/hello`), you should be able to see the main span and
362+
the child span in your tracing collector.
363+
364+
### 3.3. Further usage
365+
366+
For further usage of spans (events, attributes, etc.), please consult the documentation of the
367+
[PHP library](https://github.com/open-telemetry/opentelemetry-php) or take a look at the demo application below.
368+
369+
## 4. Demo Application
370+
371+
You can find a demo application using the SdkBundle [here](https://github.com/tidal/otel-sdk-bundle-example-sf5). The demo extends the examples given above and comes with a
372+
docker-compose setup, so it is easy to try out.

0 commit comments

Comments
 (0)