Skip to content

Commit

Permalink
Refactor instrumentation logic for PHP 8.2+ Core apps (#44)
Browse files Browse the repository at this point in the history
* Make the app run via docker compose

* Add basic DB monitoring

* Refactoring

* Allow tracking parent span id

* Update the example to use mysqli

* Update README

* WIP

* wip

* Working setup

* Working copy

* Working copy

* Add README and extract common logic
  • Loading branch information
prathamesh-sonpatki authored Jan 2, 2025
1 parent 339c636 commit 4bae3af
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 98 deletions.
150 changes: 149 additions & 1 deletion php/core/8/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,149 @@
# PHP 8 Instrumentation with OpenTelemetry and Last9
# Core PHP 8.2+ application Instrumentation with OpenTelemetry and Last9

This guide explains how to use OpenTelemetry with PHP 8.2+ to send traces to Last9. This guide is useful for PHP 8.2+ applications that are not using any framework.

## Prerequisites

- PHP 8.2+
- Composer
- OpenTelemetry PHP extension

## Installation

1. Install the OpenTelemetry PHP extension:

It is important to install the [PHP OpenTelemetry extension](https://github.com/open-telemetry/opentelemetry-php-instrumentation) before adding the required packages to your `composer.json` file. It enables the auto instrumentation of the PHP runtime.


```bash
pecl install opentelemetry
```

Add the extension to your `php.ini` file:
```ini
extension=opentelemetry.so
```

If using Docker, add to your Dockerfile:

```dockerfile
RUN install-php-extensions opentelemetry
```

Verify the extension is installed:
```bash
php -m | grep opentelemetry
```

Follow the [PHP OpenTelemetry extension](https://opentelemetry.io/docs/zero-code/php/#install-the-opentelemetry-extension) installation instructions to ensure the extension is correctly configured.

2. Add the required packages to your `composer.json`:

Mandatory packages:

```
open-telemetry/opentelemetry
open-telemetry/exporter-otlp
```

Optional packages:

```
open-telemetry/opentelemetry-auto-curl # if you are using the curl extension
open-telemetry/opentelemetry-auto-guzzle # if you are using the guzzlehttp/guzzle package
```

You can find additonal packages based on your framework in the [OpenTelemetry PHP](https://opentelemetry.io/docs/instrumentation/php/) documentation.

2. Install the packages:

```bash
composer install
```

## Usage

1. Copy the `src` directory to your project.
It includes following files for auto instrumentation of core PHP project and MySQLi instrumentation.

2. Initialize instrumentation in your entry point (index.php):

```php
<?php
require __DIR__ . '/vendor/autoload.php';
use \Last9\Instrumentation;

// Initialize instrumentation with your service name
$instrumentation = Instrumentation::init(getenv('OTEL_SERVICE_NAME'));

try {
// Your application code here

// Mark request as successful
$instrumentation->setSuccess();
} catch (Exception $e) {
// Record errors
$instrumentation->setError($e);
throw $e;
}
```

3. Set the following environment variables:

- `OTEL_EXPORTER_OTLP_ENDPOINT`: Your Last9 OpenTelemetry endpoint
- `OTEL_EXPORTER_OTLP_HEADERS`: Authentication header for Last9
- `OTEL_SERVICE_NAME`: Your service name
- `OTEL_PHP_AUTOLOAD_ENABLED`: Enable PHP auto-instrumentation, (set to - true)
- `OTEL_EXPORTER_OTLP_PROTOCOL`: Protocol to use for the exporter (set to - http/json)
- `OTEL_PROPAGATORS`: Propagators to use (set to - baggage,tracecontext)
- `OTEL_RESOURCE_ATTRIBUTES`: Resource attributes to use (set to - deployment.environment=production/staging)

4. Run your application and see the traces in Last9 [Trace Explorer](https://app.last9.io/traces).

## Features

### Automatic Instrumentation

The following operations are automatically instrumented:

1. Database Operations (MySQL/MariaDB):
```php
$result = $mysqli->query("SELECT * FROM users");
$stmt = $mysqli->prepare("INSERT INTO users (name) VALUES (?)");
```

2. HTTP Client Operations:
```php
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.example.com/data');
```

### Error Handling

The instrumentation automatically captures:
- Database errors (query failures, connection issues)
- HTTP client errors (timeouts, connection failures)
- PHP errors and exceptions
- HTTP status codes

Each error includes:
- Error message and code
- Stack trace
- Error context and attributes
- Error events in the span

## How It Works

The instrumentation:
1. Creates a root span for each incoming request with format `HTTP METHOD ENDPOINT`
2. Automatically instruments database and HTTP operations
3. Propagates context through the application
4. Handles error cases and status codes
5. Sends traces to Last9's OpenTelemetry endpoint

## Support

For issues or questions:
- Check [Last9 documentation](https://docs.last9.io)
- Contact Last9 support
- Review [OpenTelemetry PHP documentation](https://opentelemetry.io/docs/instrumentation/php/)
3 changes: 2 additions & 1 deletion php/core/8/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
},
"autoload": {
"psr-4": {
"OpenTelemetry\\Contrib\\Instrumentation\\MySqli\\": "src/OpenTelemetry/Contrib/Instrumentation/MySqli/"
"OpenTelemetry\\Contrib\\Instrumentation\\MySqli\\": "src/OpenTelemetry/Contrib/Instrumentation/MySqli/",
"Last9\\": "src/Last9/"
}
},
"config": {
Expand Down
117 changes: 21 additions & 96 deletions php/core/8/index.php
Original file line number Diff line number Diff line change
@@ -1,102 +1,10 @@
<?php
require __DIR__ . '/vendor/autoload.php';
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Instrumentation\Configurator;
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
use OpenTelemetry\Contrib\Instrumentation\MySqli\MysqliInstrumentation;

// Your existing OTLP setup
$transport = (new OtlpHttpTransportFactory())->create(getenv('OTEL_EXPORTER_OTLP_ENDPOINT'), 'application/json');
$exporter = new SpanExporter($transport);

// Create the BatchSpanProcessor with async options
$spanProcessor = new BatchSpanProcessor(
$exporter,
ClockFactory::getDefault()
);

$tracerProvider = new TracerProvider($spanProcessor);

// Add mysqli instrumentation
MysqliInstrumentation::register(new CachedInstrumentation('io.opentelemetry.contrib.php.mysqli'));

// Register the TracerProvider with Globals
Globals::registerInitializer(function (Configurator $configurator) use ($tracerProvider) {
return $configurator->withTracerProvider($tracerProvider);
});

$method = $_SERVER['REQUEST_METHOD'];
$route = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$spanName = sprintf('%s %s', $method, $route);

// Now get the tracer from Globals
$tracer = Globals::tracerProvider()->getTracer('<your-app-name>');

$headers = getallheaders();
$contentLength = $headers['Content-Length'] ?? null;
$contentType = $headers['Content-Type'] ?? null;
$userAgent = $headers['User-Agent'] ?? null;
$referer = $headers['Referer'] ?? null;

// Create root span with complete HTTP attributes
$rootSpan = $tracer->spanBuilder($spanName)
->setSpanKind(SpanKind::KIND_SERVER)
->startSpan();

// Set comprehensive HTTP attributes following OpenTelemetry semantic conventions
$rootSpan->setAttributes([
// Basic HTTP attributes
'http.method' => $_SERVER['REQUEST_METHOD'],
'http.target' => $_SERVER['REQUEST_URI'],
'http.route' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH),
'http.scheme' => isset($_SERVER['HTTPS']) ? 'https' : 'http',
'http.status_code' => http_response_code(),

// Server attributes
'http.server_name' => $_SERVER['SERVER_NAME'] ?? 'unknown',
'http.host' => $_SERVER['HTTP_HOST'] ?? 'unknown',
'http.flavor' => substr($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1', 5),

// Client attributes
'http.client_ip' => $_SERVER['REMOTE_ADDR'] ?? null,
'http.user_agent' => $userAgent,

// Request details
'http.request.method' => $_SERVER['REQUEST_METHOD'],
'http.request.body.size' => $contentLength,
'http.request.content_type' => $contentType,
'http.request.referer' => $referer,

// Additional network context
'net.host.name' => $_SERVER['SERVER_NAME'] ?? 'unknown',
'net.host.port' => $_SERVER['SERVER_PORT'] ?? 80,
'net.peer.ip' => $_SERVER['REMOTE_ADDR'] ?? null,
'net.peer.port' => $_SERVER['REMOTE_PORT'] ?? null,

// Optional: URL components
'url.path' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH),
'url.query' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY),
'url.scheme' => isset($_SERVER['HTTPS']) ? 'https' : 'http',
]);

// Also add trace context if present
foreach ($headers as $name => $value) {
if (strtolower($name) === 'traceparent' || strtolower($name) === 'tracestate') {
$rootSpan->setAttribute('http.request.header.' . strtolower($name), $value);
}
}
require __DIR__ . '/vendor/autoload.php';
use \Last9\Instrumentation;

// Create root context and activate it
$ctx = Context::getCurrent()->withContextValue($rootSpan);
$scope = $ctx->activate();
// Initialize instrumentation
$instrumentation = Instrumentation::init(getenv('OTEL_SERVICE_NAME'));

try {
try {
Expand Down Expand Up @@ -201,11 +109,27 @@
} catch (\Exception $e) {
throw $e;
}
<<<<<<< HEAD
$instrumentation->setSuccess();
=======
>>>>>>> 339c63609672e1ebd4d1dccfaaaa6ee0ff684d33
break;

default:
http_response_code(404);
echo "404 Not Found";
<<<<<<< HEAD
$instrumentation->setError(new Exception("404 Not Found"));
break;
}

} catch (Exception $e) {
$instrumentation->setError($e);
error_log("Main error: " . $e->getMessage());
http_response_code(500);
echo "Error: " . $e->getMessage();
}
=======
break;
}
$rootSpan->setStatus(StatusCode::STATUS_OK);
Expand All @@ -225,3 +149,4 @@
register_shutdown_function(function() use ($tracerProvider) {
$tracerProvider->shutdown();
});
>>>>>>> 339c63609672e1ebd4d1dccfaaaa6ee0ff684d33
Loading

0 comments on commit 4bae3af

Please sign in to comment.