Skip to content

Commit e2e54af

Browse files
authored
feat: sqlcommenter for PDO, MySqli, PostgreSql (pdo_mysql, pdo_pgsql) (#442)
* Initial * Added SqlCommenter and its tests * Added context propagator and updated PDO * Fixed the dependencies * Changed for MySqli * Changed for PostgreSql * nits * Updated README.md * Simplify PostgreSqlInstrumentation * Considered the attributes in PostgreSql * Updated * Updated * Typo * Updated README.md(s) * Added comments * Remove the query command re-extraction * Moved Context Propagator into SqlCommenter to make it per component * Renamed env variable OTEL_PHP_SQLCOMMENTER_CONTEXT_PROPAGATORS * Renamed other environment variables * Moved logic to SqlCommenter * nits
0 parents  commit e2e54af

14 files changed

+598
-0
lines changed

.gitattributes

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
* text=auto
2+
3+
*.md diff=markdown
4+
*.php diff=php
5+
6+
/.gitattributes export-ignore
7+
/.gitignore export-ignore
8+
/.php-cs-fixer.php export-ignore
9+
/phpstan.neon.dist export-ignore
10+
/phpunit.xml.dist export-ignore
11+
/psalm.xml.dist export-ignore
12+
/tests export-ignore

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/vendor/

.php-cs-fixer.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
$finder = PhpCsFixer\Finder::create()
3+
->exclude('vendor')
4+
->exclude('var/cache')
5+
->in(__DIR__);
6+
7+
$config = new PhpCsFixer\Config();
8+
return $config->setRules([
9+
'concat_space' => ['spacing' => 'one'],
10+
'declare_equal_normalize' => ['space' => 'none'],
11+
'is_null' => true,
12+
'modernize_types_casting' => true,
13+
'ordered_imports' => true,
14+
'php_unit_construct' => true,
15+
'single_line_comment_style' => true,
16+
'yoda_style' => false,
17+
'@PSR2' => true,
18+
'array_syntax' => ['syntax' => 'short'],
19+
'blank_line_after_opening_tag' => true,
20+
'blank_line_before_statement' => true,
21+
'cast_spaces' => true,
22+
'declare_strict_types' => true,
23+
'type_declaration_spaces' => true,
24+
'include' => true,
25+
'lowercase_cast' => true,
26+
'new_with_parentheses' => true,
27+
'no_extra_blank_lines' => true,
28+
'no_leading_import_slash' => true,
29+
'echo_tag_syntax' => true,
30+
'no_unused_imports' => true,
31+
'no_useless_else' => true,
32+
'no_useless_return' => true,
33+
'phpdoc_order' => true,
34+
'phpdoc_scalar' => true,
35+
'phpdoc_types' => true,
36+
'short_scalar_cast' => true,
37+
'blank_lines_before_namespace' => true,
38+
'single_quote' => true,
39+
'trailing_comma_in_multiline' => true,
40+
])
41+
->setRiskyAllowed(true)
42+
->setFinder($finder);
43+

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/contrib-sqlcommenter/releases)
2+
[![Issues](https://img.shields.io/badge/issues-pink)](https://github.com/open-telemetry/opentelemetry-php/issues)
3+
[![Source](https://img.shields.io/badge/source-contrib-green)](https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/src/SqlCommenter)
4+
[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php--contrib-blue)](https://github.com/opentelemetry-php/contrib-sqlcommenter)
5+
[![Latest Version](http://poser.pugx.org/open-telemetry/opentelemetry-sqlcommenter/v/unstable)](https://packagist.org/packages/open-telemetry/opentelemetry-sqlcommenter/)
6+
[![Stable](http://poser.pugx.org/open-telemetry/opentelemetry-sqlcommenter/v/stable)](https://packagist.org/packages/open-telemetry/opentelemetry-sqlcommenter/)
7+
8+
> **Note:** This is a read-only subtree split of [open-telemetry/opentelemetry-php-contrib](https://github.com/open-telemetry/opentelemetry-php-contrib).
9+
10+
# OpenTelemetry SQL Commenter
11+
12+
OpenTelemetry SQL Commenter for PHP provides a [SqlCommenter](https://opentelemetry.io/docs/specs/semconv/database/database-spans/#sql-commenter) implementation, enabling you to inject trace and context comments into SQL queries for enhanced observability and distributed tracing.
13+
14+
## Installation
15+
16+
Install via Composer:
17+
18+
```bash
19+
composer require open-telemetry/opentelemetry-sqlcommenter
20+
```
21+
22+
## Usage
23+
24+
Inject comments into your SQL query as follows:
25+
26+
```php
27+
use OpenTelemetry\SqlCommenter\SqlCommenter;
28+
29+
$comments = [
30+
'traceparent' => '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00',
31+
'custom' => 'value',
32+
];
33+
$query = SqlCommenter::inject($query, $comments);
34+
```
35+
36+
## Configuration
37+
38+
- **Context Propagators**
39+
40+
Set the propagators to use (comma-separated):
41+
42+
```shell
43+
OTEL_PHP_SQLCOMMENTER_CONTEXT_PROPAGATORS=tracecontext
44+
```
45+
Default: `''`
46+
47+
- **SQL Commenter Attribute**
48+
49+
Add SQL comments to `DbAttributes::DB_QUERY_TEXT` in span attributes:
50+
51+
```shell
52+
otel.sqlcommenter.attribute = true
53+
```
54+
or via environment variable:
55+
```shell
56+
OTEL_PHP_SQLCOMMENTER_ATTRIBUTE=true
57+
```
58+
Default: `false`
59+
60+
- **Prepend Comments**
61+
62+
Prepend comments to the query statement using either a configuration directive:
63+
64+
```shell
65+
otel.sqlcommenter.prepend = true
66+
```
67+
or via environment variable:
68+
69+
```shell
70+
OTEL_PHP_SQLCOMMENTER_PREPEND=true
71+
```
72+
Default: `false`
73+
74+
## Development
75+
76+
Install dependencies and run tests from the `SqlCommenter` subdirectory:
77+
78+
```bash
79+
composer install
80+
./vendor/bin/phpunit tests
81+
```
82+

composer.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "open-telemetry/opentelemetry-sqlcommenter",
3+
"description": "OpenTelemetry sqlcommenter.",
4+
"keywords": ["opentelemetry", "otel", "open-telemetry", "sqlcommenter"],
5+
"type": "library",
6+
"homepage": "https://opentelemetry.io/docs/php",
7+
"readme": "./README.md",
8+
"license": "Apache-2.0",
9+
"minimum-stability": "dev",
10+
"prefer-stable": true,
11+
"require": {
12+
"php": "^8.1",
13+
"open-telemetry/api": "^1.0",
14+
"open-telemetry/context": "^1.0"
15+
},
16+
"autoload": {
17+
"psr-4": {
18+
"OpenTelemetry\\Contrib\\SqlCommenter\\": "src/"
19+
}
20+
},
21+
"require-dev": {
22+
"friendsofphp/php-cs-fixer": "^3",
23+
"phan/phan": "^5.0",
24+
"phpstan/phpstan": "^1.1",
25+
"phpstan/phpstan-phpunit": "^1.0",
26+
"psalm/plugin-phpunit": "^0.19.2",
27+
"open-telemetry/sdk": "^1.0",
28+
"phpunit/phpunit": "^9.5",
29+
"vimeo/psalm": "6.4.0",
30+
"symfony/http-client": "^5.4|^6.0",
31+
"guzzlehttp/promises": "^2",
32+
"php-http/message-factory": "^1.0",
33+
"nyholm/psr7": "^1.5"
34+
},
35+
"config": {
36+
"allow-plugins": {
37+
"php-http/discovery": true,
38+
"tbachert/spi": true
39+
}
40+
}
41+
}

phpstan.neon.dist

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
includes:
2+
- vendor/phpstan/phpstan-phpunit/extension.neon
3+
4+
parameters:
5+
tmpDir: var/cache/phpstan
6+
level: 5
7+
paths:
8+
- src
9+
- tests

phpunit.xml.dist

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
6+
backupGlobals="false"
7+
backupStaticAttributes="false"
8+
cacheResult="false"
9+
colors="false"
10+
convertErrorsToExceptions="true"
11+
convertNoticesToExceptions="true"
12+
convertWarningsToExceptions="true"
13+
forceCoversAnnotation="false"
14+
processIsolation="false"
15+
stopOnError="false"
16+
stopOnFailure="false"
17+
stopOnIncomplete="false"
18+
stopOnSkipped="false"
19+
stopOnRisky="false"
20+
timeoutForSmallTests="1"
21+
timeoutForMediumTests="10"
22+
timeoutForLargeTests="60"
23+
verbose="true">
24+
25+
<coverage processUncoveredFiles="true" disableCodeCoverageIgnore="false">
26+
<include>
27+
<directory>src</directory>
28+
</include>
29+
</coverage>
30+
31+
<php>
32+
<ini name="date.timezone" value="UTC" />
33+
<ini name="display_errors" value="On" />
34+
<ini name="display_startup_errors" value="On" />
35+
<ini name="error_reporting" value="E_ALL" />
36+
</php>
37+
38+
<testsuites>
39+
<testsuite name="unit">
40+
<directory>tests/Unit</directory>
41+
</testsuite>
42+
</testsuites>
43+
44+
</phpunit>

psalm.xml.dist

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0"?>
2+
<psalm
3+
errorLevel="3"
4+
cacheDirectory="var/cache/psalm"
5+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6+
xmlns="https://getpsalm.org/schema/config"
7+
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd">
8+
<projectFiles>
9+
<directory name="src"/>
10+
<directory name="tests"/>
11+
</projectFiles>
12+
<plugins>
13+
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
14+
</plugins>
15+
</psalm>

src/ContextPropagatorFactory.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Contrib\SqlCommenter;
6+
7+
use OpenTelemetry\API\Behavior\LogsMessagesTrait;
8+
use OpenTelemetry\Context\Propagation\MultiTextMapPropagator;
9+
use OpenTelemetry\Context\Propagation\NoopTextMapPropagator;
10+
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
11+
use OpenTelemetry\SDK\Registry;
12+
13+
class ContextPropagatorFactory
14+
{
15+
use LogsMessagesTrait;
16+
17+
public function create(): ?TextMapPropagatorInterface
18+
{
19+
$propagators = [];
20+
if (class_exists('OpenTelemetry\SDK\Common\Configuration\Configuration')) {
21+
$propagators = \OpenTelemetry\SDK\Common\Configuration\Configuration::getList('OTEL_PHP_SQLCOMMENTER_CONTEXT_PROPAGATORS', []);
22+
}
23+
24+
switch (count($propagators)) {
25+
case 0:
26+
return null;
27+
case 1:
28+
$propagator = $this->buildPropagator($propagators[0]);
29+
if ($propagator !== null && is_a($propagator, NoopTextMapPropagator::class)) {
30+
return null;
31+
}
32+
33+
return $propagator;
34+
default:
35+
$props = $this->buildPropagators($propagators);
36+
if ($props) {
37+
return new MultiTextMapPropagator($props);
38+
}
39+
40+
return null;
41+
}
42+
}
43+
44+
/**
45+
* @return ?list<TextMapPropagatorInterface>
46+
*/
47+
private function buildPropagators(array $names): ?array
48+
{
49+
$propagators = [];
50+
foreach ($names as $name) {
51+
$propagator = $this->buildPropagator($name);
52+
if ($propagator !== null && !is_a($propagator, NoopTextMapPropagator::class)) {
53+
$propagators[] = $propagator;
54+
}
55+
}
56+
if (count($propagators) === 0) {
57+
return null;
58+
}
59+
60+
return $propagators;
61+
}
62+
63+
private function buildPropagator(string $name): ?TextMapPropagatorInterface
64+
{
65+
try {
66+
return Registry::textMapPropagator($name);
67+
} catch (\RuntimeException $e) {
68+
self::logWarning($e->getMessage());
69+
}
70+
71+
return null;
72+
}
73+
}

src/SqlCommenter.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Contrib\SqlCommenter;
6+
7+
use OpenTelemetry\API\Globals;
8+
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
9+
10+
class SqlCommenter
11+
{
12+
private static ?self $instance = null;
13+
14+
public function __construct(private readonly ?TextMapPropagatorInterface $contextPropagator = null)
15+
{
16+
}
17+
18+
public static function getInstance(): self
19+
{
20+
if (null === self::$instance) {
21+
self::$instance = new self((new ContextPropagatorFactory())->create());
22+
}
23+
24+
return self::$instance;
25+
}
26+
27+
public function isAttributeEnabled(): bool
28+
{
29+
if (class_exists('OpenTelemetry\\SDK\\Common\\Configuration\\Configuration') && \OpenTelemetry\SDK\Common\Configuration\Configuration::getBoolean('OTEL_PHP_SQLCOMMENTER_ATTRIBUTE', false)) {
30+
return true;
31+
}
32+
33+
return filter_var(get_cfg_var('otel.sqlcommenter.attribute'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
34+
}
35+
36+
public function isPrepend(): bool
37+
{
38+
if (class_exists('OpenTelemetry\SDK\Common\Configuration\Configuration') && \OpenTelemetry\SDK\Common\Configuration\Configuration::getBoolean('OTEL_PHP_SQLCOMMENTER_PREPEND', false)) {
39+
return true;
40+
}
41+
42+
return filter_var(get_cfg_var('otel.sqlcommenter.prepend'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
43+
}
44+
45+
public function inject(string $query): string
46+
{
47+
$comments = [];
48+
if ($this->contextPropagator !== null) {
49+
$this->contextPropagator->inject($comments);
50+
} else {
51+
Globals::propagator()->inject($comments);
52+
}
53+
$query = trim($query);
54+
if ($this->isPrepend()) {
55+
return Utils::formatComments(array_filter($comments)) . $query;
56+
}
57+
$hasSemicolon = $query !== '' && $query[strlen($query) - 1] === ';';
58+
$query = rtrim($query, ';');
59+
60+
return $query . Utils::formatComments(array_filter($comments)) . ($hasSemicolon ? ';' : '');
61+
}
62+
}

0 commit comments

Comments
 (0)