Skip to content

Latest commit

 

History

History
239 lines (159 loc) · 10.8 KB

symfony-integration.md

File metadata and controls

239 lines (159 loc) · 10.8 KB

Integrating Opentelemetry PHP into Symfony Applications

Introduction

As a developer, you might be wondering how OpenTelemetry could be beneficial to you. Without practical examples, the usefulness of distributed tracing can be difficult to grasp for persons without a cloud or site reliability engineering background. This user guide shows how OpenTelemetry could be useful to gain insights into exceptions happening within an application. This example uses the OpenTelemtry PHP library integrated into a Symfony application, bundled with Jaeger and Zipkin, for visualizing data.

Prerequisites

To follow this guide you will need:

  • PHP Installed, this example uses PHP 7.4.
  • Composer for dependency management.
  • Symfony CLI for managing your Symfony application.
  • Docker for bundling our visualization tools. We have setup instructions for docker on this project's readme.

This example uses Symfony version 5.2 .

Step 1 - Creating a Symfony Application

Create a Symfony application by running the command symfony new my_project_name. We are calling this example otel-php-symfony-basic-example, so the command is as follows;

symfony new otel-php-symfony-basic-example .

Step 2 - Require and Test Symfony Dependencies

To define our routes within our controller methods, let's require the Doctrine annotation library by running the command composer require doctrine/annotations.

We can test that routes defined within Controllers work by creating a HelloController.php file within the src\Controller folder as follows:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HelloController extends AbstractController
{
    /**
     * @Route("/hello", name="hello")
     */
    public function index(): Response
    { 
        return new Response('Hello World');
    }
}

To check out the routes available in our current project run php bin/console debug:router.

image

Let's confirm that our application works by running the command symfony server:start.

image

You can navigate to http://127.0.0.1:8000/hello route to see the Hello world response returned from the HelloController index method above.

image

Step 3 - Require the OpenTelemetry PHP Library

For this step, we require the OpenTelemetry PHP Library by running the command composer require open-telemetry/opentelemetry. It is worthy of note that this command pulls in the last stable release for the library.

Step 4 - Bundle Zipkin and Jaeger into the Application

To visualize traces from our application, we have to bundle open source tracing tools Zipkin and Jaeger into our application using docker.

Let's add a docker-compose.yaml file in the root of our project with the content as follows:

version: '3.7'
services:
    zipkin:
        image: openzipkin/zipkin-slim
        ports:
            - 9411:9411
    jaeger:
        image: jaegertracing/all-in-one
        environment:
            COLLECTOR_ZIPKIN_HOST_PORT: 9412 # Before version 1.22.0 use COLLECTOR_ZIPKIN_HTTP_PORT
        ports:
            - 9412:9412
            - 16686:16686

To confirm that docker is installed and running on our system, we can run the hello world docker example using the command docker run -it --rm hello-world. If everything works well, run docker-compose up -d to pull in Zipkin and Jaeger. This might take some time, depending on your internet connection speed.

image

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.

image

image

Now it is time to utilize our OpenTelemetry PHP Library to export traces to both Zipkin and Jaeger.

Step 5 - Instrument Symfony Application

The entry point for all Symfony applications is the index.php file located in the public folder. Let's navigate to public\index.php to see what is happening. It is worthy of note that resources(namespaces, classes, variables) created within the index.php file are available within the entire application, by default the index file imports all auto loaded classes within the vendor folder. It also imports contents of the .env file. The other parts of the index.php file enable debugging as well as support request and response resolution using the application kernel.

To use open-telemetry specific classes we have to import them at the top of our index file, using the use keyword. This is what our imports look like:

use App\Kernel;
use OpenTelemetry\Contrib\Jaeger\Exporter as JaegerExporter;
use OpenTelemetry\Contrib\Zipkin\Exporter as ZipkinExporter;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\SamplingResult;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\API\Trace as API;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;

Next, we create a sample recording trace using the AlwaysOnSampler class, just before the Kernel instance is created like below:

$sampler = new AlwaysOnSampler();
$samplingResult = $sampler->shouldSample(
    null,
    md5((string) microtime(true)),
    substr(md5((string) microtime(true)), 16),
    'io.opentelemetry.example',
    API\SpanKind::KIND_INTERNAL
);

Since we are looking to export traces to both Zipkin and Jaeger we have to make use of their individual exporters;

$jaegerExporter = new JaegerExporter(
    'Hello World Web Server Jaeger',
    'http://localhost:9412/api/v2/spans'
);

$zipkinExporter = new ZipkinExporter(
    'Hello World Web Server Zipkin',
    'http://localhost:9411/api/v2/spans'
);

Next we create a trace, and add processors for each trace(One for Jaeger and another for Zipkin). Then we proceed to start and activate a span for each trace. We create a trace only if the RECORD AND SAMPLED sampling result condition passes as follows;

if (SamplingResult::RECORD_AND_SAMPLED === $samplingResult->getDecision()) {

    $jaegerTracer = (new TracerProvider(null, $sampler))
        ->addSpanProcessor(new BatchSpanProcessor($jaegerExporter, Clock::get()))
        ->getTracer('io.opentelemetry.contrib.php');

    $zipkinTracer = (new TracerProvider(null, $sampler))
    ->addSpanProcessor(new BatchSpanProcessor($zipkinExporter, Clock::get()))
    ->getTracer('io.opentelemetry.contrib.php');

    $request = Request::createFromGlobals();
    $jaegerSpan = $jaegerTracer->startAndActivateSpan($request->getUri());
    $zipkinSpan = $zipkinTracer->startAndActivateSpan($request->getUri());

}

Finally we end the active spans if sampling is complete, by adding the following block at the end of the index.php file;

if (SamplingResult::RECORD_AND_SAMPLED === $samplingResult->getDecision()) {
    $zipkinSpan->end();
    $jaegerSpan->end();
}

lets 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/hello or any other route on our symfony server;

image

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 symfony 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.

) image

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

image

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

image

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

image

Since resources in Symfony's public\index.php file are available to the entire application, we can use any of the already instantiated tracers within HelloController. In addition to the tracers, we can also utilize associated properties, methods and events.

Lets try using the addEvent method, to capture errors within our controller as follows:

global $zipkinTracer;
        if ($zipkinTracer) {
            /** @var Span $span */
            $span = $zipkinTracer->getActiveSpan();
            
            $span->setAttribute('foo', 'bar');
            $span->updateName('New name');

            $zipkinTracer->startAndActivateSpan('Child span');
            try {
                throw new \Exception('Exception Example');
            } catch (\Exception $exception) {
                $span->setSpanStatus($exception->getCode(), $exception->getMessage());
            }
            $span->end();
        }

In the above snippet we change the span name and attributes for our Zipkin trace, we also add an exception event to the span.

We need to reload our http://127.0.0.1:8000/hello route, then navigate to Zipkin like before to see that our span name gets updated to new name and our Exception Example is visible

image

Summary

With the above example we have been able to instrument a Symfony application using the OpenTelemetry php library. You can fork the example project here. You can also checkout the original test application here.