292 lines
12 KiB
Markdown
292 lines
12 KiB
Markdown
# Exploring Opentelemetry in Laravel Applications
|
|
|
|
## Introduction
|
|
|
|
Distributed tracing helps developers and management gain insights into how well applications perform in terms of traces,
|
|
metrics, and logs. This guide shows how developers can integrate OpenTelemetry PHP into their Laravel applications for
|
|
the above benefits. Our example application visualizes exceptions from a Laravel application using both Jaeger and
|
|
Zipkin.
|
|
|
|
To follow this guide you will need:
|
|
|
|
* PHP Installed; this example uses PHP 7.4.
|
|
* [Composer](https://getcomposer.org/download/ ) for dependency management.
|
|
* [Docker](https://docs.docker.com/get-docker/) for bundling our visualization tools. We have setup instructions for
|
|
docker on this project's [readme](https://github.com/open-telemetry/opentelemetry-php#development).
|
|
|
|
This example uses Laravel version `9.1` and OpenTelemetry PHP SDK version `0.0.11`.
|
|
|
|
> ⚠ This example is only intended to introduce how OpenTelemetry can be used in a Laravel application. The
|
|
> example code is not suited for production applications, and must not be consulted for any code that goes into
|
|
> production.
|
|
|
|
## Step 1 - Creating a Laravel Application
|
|
|
|
The Laravel framework supports creating applications using composer. To do that,
|
|
run `composer create-project <project-name>`. We are naming our project `otel-php-laravel-basic-example`, so the
|
|
command is as follows:
|
|
|
|
`composer create-project laravel/laravel otel-php-laravel-basic-example`
|
|
|
|
To confirm that our application works, we can move to the application directory
|
|
using `cd otel-php-laravel-basic-example` , then serve the application with `php artisan serve` .
|
|
|
|

|
|
|
|
Let's navigate to `http://127.0.0.1:8000` on our browser to see the default Laravel welcome page.
|
|
|
|

|
|
|
|
## Step 2 - Require OpenTelemetry PHP Package
|
|
|
|
Starting from version `v.0.0.2`, the open-telemetry php package allows users to use their preferred HTTP layers for
|
|
exporting traces. The benefit of this is that users can reuse already existing HTTP configurations for their
|
|
applications. Hence, there is need to require packages that satisfy both `psr/http-client-implementation`
|
|
and `psr/http-factory-implementation` before requiring the opentelemetry-php package.
|
|
|
|
By default, the Laravel framework utilizes `guzzlehttp/guzzle` and this satisfies `psr/http-client-implementation`, so
|
|
we need to require the `guzzlehttp/psr7` to meet the `psr/http-factory-implementation` requirement. Let's
|
|
run `composer require guzzlehttp/psr7:2.1`.
|
|
|
|
Note: We are specifying `2.1` as that is the release for `guzzlehttp/psr7` that includes HTTP factories as at the
|
|
time of writing this guide.
|
|
|
|
Starting from version `v.0.0.4`, opentelemetry-php package also requires `php-http/async-client-implementation`. Any
|
|
client implementation would work, but for this example, let's go ahead with `php-http/guzzle7-adapter`. Run
|
|
`composer require php-http/guzzle7-adapter` to install `php-http/guzzle7-adapter`.
|
|
|
|
Next, let's run `composer require open-telemetry/opentelemetry` to pull in the openTelemetry-php package. It is worthy
|
|
of note that this command pulls in the last stable release for the library.
|
|
|
|
## Step 3 - Bundle Zipkin and Jaeger into the Application
|
|
|
|
To visualize traces exported from our application, we need to integrate open source tracing
|
|
tools [Zipkin](https://zipkin.io/) and [Jaeger](https://www.jaegertracing.io/) into our setup using docker.
|
|
|
|
First, we create a `docker-compose.yaml` file in the root of our project, with content as follows:
|
|
|
|
```yaml
|
|
version: '3.7'
|
|
services:
|
|
zipkin:
|
|
image: openzipkin/zipkin-slim
|
|
ports:
|
|
- "9411:9411"
|
|
jaeger:
|
|
image: jaegertracing/all-in-one
|
|
environment:
|
|
COLLECTOR_ZIPKIN_HOST_PORT: 9412
|
|
|
|
ports:
|
|
- "9412:9412"
|
|
- "16686:16686"
|
|
```
|
|
|
|
Next, we pull in Zipkin and Jaeger by running `docker-compose up -d`. This might take some time, depending on your
|
|
internet connection speed.
|
|
|
|

|
|
|
|
We can confirm that Zipkin is up by navigating to `http://localhost:9411/` on our browser. For Jaeger, navigating
|
|
to `http://localhost:16686/` on our browser should display the Jaeger home page.
|
|
|
|

|
|
|
|

|
|
|
|
## Step 5 - Instrument Laravel Application
|
|
|
|
For this step, we will utilize our OpenTelemetry PHP Library to export traces to both Zipkin and Jaeger.
|
|
|
|
The default entry point for Laravel applications is the `index.php` file located in the `public` folder. If we navigate
|
|
to `public\index.php` we can see that the index file autoloads classes from packages within our vendor folder, making
|
|
them easily usable within our application.
|
|
|
|
```php
|
|
require __DIR__.'/../vendor/autoload.php';
|
|
```
|
|
|
|
The other parts of the `index.php` file enable request and response resolution using the application kernel.
|
|
|
|
```php
|
|
$app = require_once __DIR__.'/../bootstrap/app.php';
|
|
|
|
$kernel = $app->make(Kernel::class);
|
|
|
|
$response = tap($kernel->handle(
|
|
$request = Request::capture()
|
|
))->send();
|
|
|
|
$kernel->terminate($request, $response);
|
|
```
|
|
|
|
It is worthy of note that resources(namespaces, classes, variables) created within the `index.php` file are available
|
|
within the entire application.
|
|
|
|
To use open-telemetry specific classes within our application we have to import them at the top of our index file, using
|
|
the `use` keyword. This is what our list of open-telemetry imported classes should look like:
|
|
|
|
```php
|
|
use GuzzleHttp\Client;
|
|
use GuzzleHttp\Psr7\HttpFactory;
|
|
use OpenTelemetry\API\Trace\Span;
|
|
use OpenTelemetry\API\Trace\StatusCode;
|
|
use OpenTelemetry\API\Trace\TracerInterface;
|
|
use OpenTelemetry\Contrib\Zipkin\Exporter as ZipkinExporter;
|
|
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
|
|
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
|
|
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
|
|
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
|
|
use OpenTelemetry\SDK\Trace\TracerProvider;
|
|
```
|
|
|
|
Remember that these imports should go side by side with the default class imports that come with the `index.php` file.
|
|
|
|
Next, we create a sample recording trace using
|
|
the [AlwaysOnSampler](https://github.com/open-telemetry/opentelemetry-php/blob/main/sdk/Trace/Sampler/AlwaysOnSampler.php)
|
|
class, which is default. Since we are looking to export traces to both Zipkin and Jaeger, we configure a tracer with exporters for both the
|
|
services just before the app instance is created like below;
|
|
|
|
```php
|
|
$httpClient = new Client();
|
|
$httpFactory = new HttpFactory();
|
|
|
|
$tracer = (new TracerProvider(
|
|
[
|
|
new SimpleSpanProcessor(
|
|
new OpenTelemetry\Contrib\Zipkin\Exporter(
|
|
PsrTransportFactory::discover()->create('http://zipkin:9411/api/v2/spans', 'application/json')
|
|
),
|
|
),
|
|
],
|
|
new AlwaysOnSampler(),
|
|
))->getTracer('Hello World Laravel Web Server');
|
|
```
|
|
|
|
Next we create a span from our tracer;
|
|
|
|
```php
|
|
$request = Request::capture();
|
|
$span = $tracer->spanBuilder($request->url())->startSpan();
|
|
$spanScope = $span->activate();
|
|
```
|
|
|
|
Finally, we end the active spans once and detach the span scope by adding the following block at the end of
|
|
the `index.php` file;
|
|
|
|
```php
|
|
$span->end();
|
|
$spanScope->detach();
|
|
```
|
|
|
|
Let's confirm that we can see exported traces on both Zipkin and Jaeger. To do that, we need to
|
|
reload `http://127.0.0.1:8000` on our browser;
|
|
|
|
We also need reload both Zipkin and Jaeger on our browser, using the URLs `http://localhost:9411/`
|
|
and `http://localhost:16686/`. Do ensure that both your Laravel server and docker instance are running for this step.
|
|
|
|
For Jaeger under service, you should see a `Hello World Web Server Jaeger` service. Go ahead and click find traces to
|
|
see exported traces.
|
|
|
|

|
|
|
|
Once we click on `Find Traces`, you should be able to see traces like below:
|
|
|
|

|
|
|
|
We can click on a trace to get more information about the trace.
|
|
|
|

|
|
|
|
For Zipkin, we can visualize our trace by clicking on `Run Query`
|
|
|
|

|
|
|
|
Since resources in Laravel's `public\index.php` file are available to the entire application, we can use any of the
|
|
already instantiated tracers to further instrument controllers or any other parts of our application.
|
|
|
|
Let's create a `Hello` controller to check this out. Run the command `php artisan make:controller HelloController`
|
|
|
|

|
|
|
|
Next we need to add a route for accessing the controller. To do this we need to utilize the `HelloController` class
|
|
within our web routes file located in the `routes\web.php` as follows:
|
|
|
|
```php
|
|
use App\Http\Controllers\HelloController;
|
|
```
|
|
|
|
Next we need to add a route and method for the controller.
|
|
|
|
```php
|
|
Route::get('/hello', [HelloController::class, 'index']);
|
|
```
|
|
|
|
The above snippet routes every GET request from the `/hello` route on the browser to an index method within
|
|
the `HelloController` class. For now, this method does not exist, so we have to add it to our controller in
|
|
`app\Http\Controllers\HelloController.php` as follows:
|
|
|
|
```php
|
|
public function index(){
|
|
return "hello";
|
|
}
|
|
```
|
|
|
|
Let's confirm that everything works well by visiting the `/hello` route on our browser.
|
|
|
|

|
|
|
|
Now that we have the `index` method working, let's make changes to the existing rootSpan and create an exception in a
|
|
child span of the rootSpan. We can do the same as follows:
|
|
|
|
- Import the required functions on top of the file:
|
|
|
|
```php
|
|
use OpenTelemetry\API\Trace\Span;
|
|
use OpenTelemetry\API\Trace\StatusCode;
|
|
use OpenTelemetry\SDK\Trace\TracerProvider;
|
|
```
|
|
|
|
- Put the below snippet in `index()` function before the `return` statement:
|
|
|
|
```php
|
|
/** @var TracerProvider $tracer */
|
|
$tracer = TracerProvider::getDefaultTracer();
|
|
if ($tracer) {
|
|
/** @var Span $span */
|
|
$span = Span::getCurrent();
|
|
|
|
$span->setAttribute('foo', 'bar');
|
|
$span->setAttribute('Application', 'Laravel');
|
|
$span->setAttribute('foo', 'bar1');
|
|
$span->updateName('New name');
|
|
|
|
$childSpan = $tracer->spanBuilder('Child span')->startSpan();
|
|
$childScope = $childSpan->activate();
|
|
try {
|
|
throw new \Exception('Exception Example');
|
|
} catch (\Exception $exception) {
|
|
$childSpan->recordException($exception);
|
|
}
|
|
$childSpan->end();
|
|
$childScope->detach();
|
|
}
|
|
```
|
|
|
|
In the above snippet, we set two new attributes for the current span, and then change the value for one of the attribute
|
|
and the trace reflects the latest value. We also change the name of our current span and add an exception event to the
|
|
child span.
|
|
|
|
* Point to note: all the variables in `index.php` are available in this file. But we use inbuilt functions to get `$tracer`
|
|
rather than using the global variables.
|
|
|
|
We need to reload our `http://127.0.0.1:8000/hello` route, then navigate to Zipkin and Jaeger like before, to see that our
|
|
span name gets updated to `new name` and our `Exception Example` is visible.
|
|
|
|

|
|
|
|
## Summary
|
|
|
|
With the above example we have been able to instrument a Laravel application using the OpenTelemetry PHP library. You
|
|
can fork the example project [here](https://github.com/prondubuisi/otel-php-laravel-basic-example).
|