I have a very simple stack of PHP + Otel auto instrumetation + Elastic APM + Prometheus.
My stack gets tracing and metrics information from my php app. My tracings are forwarded to the elastic cloud, where I use APM. My metrics go to Prometheus.
I see that my tracing is partially working as I can't see the dependencies. Which in this case would be my external api.
The base of my stack: (I didn't attach the php.ini and .htaccess files as I don't think it's relevant.)
index.php
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use DI\Container;
use GuzzleHttp\Client;
require __DIR__ . '/vendor/autoload.php';
$container = new Container();
AppFactory::setContainer($container);
$app = AppFactory::create();
$app->get('/', function (Request $request, Response $response) {
$response->getBody()->write('<h3>RANDOM API</h3>
<p>O que vocĂȘ gostaria de ver?</p>
<ul>
<li>Random dogs? <a href="/dogs">Clique</li>
</ul>');
return $response;
});
$app->get('/healthcheck', function (Request $request, Response $response) {
return $response->withStatus(204);
});
$app->get('/dogs', function (Request $request, Response $response) {
$client = new Client();
try {
$apiResponse = $client->get('https://dog.ceo/api/breeds/image/random');
$dogData = json_decode($apiResponse->getBody());
$response->getBody()->write('<img src="' . $dogData->message . '" alt="random dog" style="max-width: 500px"/>');
} catch (\Exception $e) {
$response->getBody()->write($e->getMessage());
return $response->withStatus(500);
}
return $response;
});
$app->run();
Dockerfile
FROM php:8.2
RUN set -xe; \
apt-get update; \
apt-get -y install g++ zlib1g-dev build-essential zlib1g-dev gcc make autoconf m4 perl git; \
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \
pecl install grpc && docker-php-ext-enable grpc; \
pecl install opentelemetry-beta && docker-php-ext-enable opentelemetry
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV OTEL_PHP_AUTOLOAD_ENABLED=true
ENV OTEL_SERVICE_NAME=your-service-name
ENV OTEL_TRACES_EXPORTER=console
ENV OTEL_METRICS_EXPORTER=otlp
ENV OTEL_EXPORTER_OTLP_PROTOCOL=grpc
ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317
ENV OTEL_PROPAGATORS=baggage,tracecontext
WORKDIR /usr/src/myapp
COPY composer.json composer.lock* /usr/src/myapp/
RUN composer install --no-dev --no-scripts --no-autoloader
COPY . /usr/src/myapp
RUN composer dump-autoload --optimize --classmap-authoritative; \
composer require slim/slim:^4 slim/psr7:^1; \
composer require open-telemetry/exporter-otlp php-http/guzzle7-adapter; \
composer require open-telemetry/transport-grpc; \
composer config allow-plugins.php-http/discovery false; \
composer require open-telemetry/sdk open-telemetry/opentelemetry-auto-slim; \
php --ri opentelemetry
COPY php.ini /usr/local/etc/php/php.ini
CMD [ "php", "-S", "0.0.0.0:8080" ]
docker-compose.yml
services:
otel_collector:
networks:
- backend
image: otel/opentelemetry-collector-contrib:latest
volumes:
- "./otel-collector-config.yml:/etc/otelcol/otel-collector-config.yml"
command: --config /etc/otelcol/otel-collector-config.yml
ports:
- "14278:14278"
- "65535:65535"
- "55677:55677"
- "12345:12345"
- "4318:4318"
- "4317:4317"
- "8889:8889"
prometheus:
networks:
- backend
image: prom/prometheus:latest
volumes:
- "./prometheus.yml:/etc/prometheus/prometheus.yml"
ports:
- "9090:9090"
app-php:
build: ./php
image: app-php
ports:
- 8083:8080
container_name: app-php
environment:
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel_collector:4317
OTEL_SERVICE_NAME: app-php
OTEL_TRACES_EXPORTER: otlp
networks:
- backend
networks:
backend:
composer.json
{
"require": {
"slim/slim": "4.*",
"php-di/php-di": "^6.3",
"guzzlehttp/guzzle": "^7.3",
"open-telemetry/api": "^1.0.0",
"open-telemetry/sdk": "^1.0.0"
},
"minimum-stability": "dev"
}
otel-collector-config.yml
receivers:
otlp:
protocols:
grpc: {}
http: {}
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
logging:
verbosity: detailed
otlp/elastic:
endpoint: "xxx-xxx-xxx"
headers:
Authorization: "xxx-xxx-xxx"
processors:
batch: {}
service:
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus, logging]
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/elastic, logging]
prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: aggregated-trace-metrics
static_configs:
- targets: ['otel_collector:8889']
It looks like your routes for the Slim Framework application are being traced and are visible in elastic, but you are missing spans for the http calls to https://dog.ceo
The slim framework auto-instrumentation does not instrument outgoing http calls. If the $client
that you are using is a PSR-18 http client, then it can be auto-instrumented by installing the open-telemetry/opentelemetry-auto-psr18
package. Do note that $client->get()
is not part of the PSR-18 spec, only sendRequest()
is. If the http client you are using proxies get()
through to sendRequest()
, then it should work. Otherwise, create the request and send it using the PSR-18 method:
$request = new Request('GET', 'https://dog.ceo/api/breeds/image/random');
$client->sendRequest($request);