Skip to content

Commit f042bae

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
1 parent 3dcc5f9 commit f042bae

24 files changed

+791
-20
lines changed

.github/dependabot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ updates:
101101
- "/src/ResourceDetectors/DigitalOcean"
102102
- "/src/Sampler/RuleBased"
103103
- "/src/Shims/OpenTracing"
104+
- "/src/SqlCommenter"
104105
- "/src/Symfony"
105106
- "/src/Symfony/src/OtelBundle"
106107
- "/src/Symfony/src/OtelSdkBundle"

.github/workflows/php.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ jobs:
6565
'Sampler/RuleBased',
6666
'Sampler/Xray',
6767
'Shims/OpenTracing',
68+
'SqlCommenter',
6869
'Symfony',
6970
'Utils/Test'
7071
]

.gitsplit.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ splits:
9494
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sampler-aws-xray.git"
9595
- prefix: "src/Shims/OpenTracing"
9696
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-shim-opentracing.git"
97+
- prefix: "src/SqlCommenter"
98+
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sqlcommenter.git"
9799
- prefix: "src/Symfony"
98100
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sdk-bundle.git"
99101
- prefix: "src/Utils/Test"

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"OpenTelemetry\\Contrib\\Resource\\Detector\\DigitalOcean\\": "src/ResourceDetectors/DigitalOcean/src",
5656
"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\": "src/Sampler/RuleBased/src",
5757
"OpenTelemetry\\Contrib\\Shim\\OpenTracing\\": "src/Shims/OpenTracing/src",
58+
"OpenTelemetry\\Contrib\\SqlCommenter\\": "src/SqlCommenter/src",
5859
"OpenTelemetry\\Contrib\\Symfony\\": "src/Symfony/src",
5960
"OpenTelemetry\\TestUtils\\": "src/Utils/Test/src"
6061
},
@@ -125,6 +126,7 @@
125126
"OpenTelemetry\\Tests\\Propagation\\CloudTrace\\": "src/Propagation/CloudTrace/tests",
126127
"OpenTelemetry\\Tests\\Resource\\Detector\\Azure\\": "src/ResourceDetectors/Azure/tests",
127128
"OpenTelemetry\\Contrib\\Resource\\Detector\\DigitalOcean\\": "src/ResourceDetectors/DigitalOcean/tests",
129+
"OpenTelemetry\\Tests\\Contrib\\SqlCommenter\\": "src/SqlCommenter/tests",
128130
"OpenTelemetry\\Tests\\Contrib\\Symfony\\": "src/Symfony/tests",
129131
"OpenTelemetry\\TestUtils\\Tests\\": "src/Utils/Test/tests"
130132
}
@@ -171,6 +173,7 @@
171173
"open-telemetry/opentelemetry-propagation-instana": "self.version",
172174
"open-telemetry/opentelemetry-propagation-server-timing": "self.version",
173175
"open-telemetry/opentelemetry-propagation-traceresponse": "self.version",
176+
"open-telemetry/opentelemetry-sqlcommenter": "self.version",
174177
"open-telemetry/opentracing-shim": "self.version",
175178
"open-telemetry/sampler-rule-based": "self.version",
176179
"open-telemetry/symfony-sdk-bundle": "self.version",

src/Instrumentation/MySqli/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,9 @@ The extension can be disabled via [runtime configuration](https://opentelemetry.
5454
OTEL_PHP_DISABLED_INSTRUMENTATIONS=mysqli
5555
```
5656

57+
## Database Context Propagation
58+
59+
Enable context propagation for database queries by installing the following packages:
60+
```shell
61+
composer require open-telemetry/opentelemetry-sqlcommenter
62+
```

src/Instrumentation/MySqli/src/MySqliInstrumentation.php

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020

2121
/**
2222
* @phan-file-suppress PhanParamTooFewUnpack
23+
* @phan-file-suppress PhanUndeclaredClassMethod
2324
*/
2425
class MySqliInstrumentation
2526
{
2627
use LogsMessagesTrait;
2728

2829
public const NAME = 'mysqli';
30+
private const UNDEFINED = 'undefined';
2931

3032
private const MYSQLI_CONNECT_ARG_OFFSET = 0;
3133
private const MYSQLI_REAL_CONNECT_ARG_OFFSET = 1; // The mysqli_real_connect function in procedural mode requires a mysqli object as its first argument. The remaining arguments are consistent with those used in other connection methods, such as connect or __construct
@@ -98,7 +100,7 @@ public static function register(): void
98100
null,
99101
'mysqli_query',
100102
pre: static function (...$args) use ($instrumentation, $tracker) {
101-
self::queryPreHook('mysqli_query', $instrumentation, $tracker, ...$args);
103+
return self::queryPreHook('mysqli_query', $instrumentation, $tracker, ...$args);
102104
},
103105
post: static function (...$args) use ($instrumentation, $tracker) {
104106
self::queryPostHook($instrumentation, $tracker, ...$args);
@@ -108,7 +110,7 @@ public static function register(): void
108110
mysqli::class,
109111
'query',
110112
pre: static function (...$args) use ($instrumentation, $tracker) {
111-
self::queryPreHook('mysqli::query', $instrumentation, $tracker, ...$args);
113+
return self::queryPreHook('mysqli::query', $instrumentation, $tracker, ...$args);
112114
},
113115
post: static function (...$args) use ($instrumentation, $tracker) {
114116
self::queryPostHook($instrumentation, $tracker, ...$args);
@@ -119,7 +121,7 @@ public static function register(): void
119121
null,
120122
'mysqli_real_query',
121123
pre: static function (...$args) use ($instrumentation, $tracker) {
122-
self::queryPreHook('mysqli_real_query', $instrumentation, $tracker, ...$args);
124+
return self::queryPreHook('mysqli_real_query', $instrumentation, $tracker, ...$args);
123125
},
124126
post: static function (...$args) use ($instrumentation, $tracker) {
125127
self::queryPostHook($instrumentation, $tracker, ...$args);
@@ -129,7 +131,7 @@ public static function register(): void
129131
mysqli::class,
130132
'real_query',
131133
pre: static function (...$args) use ($instrumentation, $tracker) {
132-
self::queryPreHook('mysqli::real_query', $instrumentation, $tracker, ...$args);
134+
return self::queryPreHook('mysqli::real_query', $instrumentation, $tracker, ...$args);
133135
},
134136
post: static function (...$args) use ($instrumentation, $tracker) {
135137
self::queryPostHook($instrumentation, $tracker, ...$args);
@@ -161,7 +163,7 @@ public static function register(): void
161163
null,
162164
'mysqli_multi_query',
163165
pre: static function (...$args) use ($instrumentation, $tracker) {
164-
self::queryPreHook('mysqli_multi_query', $instrumentation, $tracker, ...$args);
166+
self::multiQueryPreHook('mysqli_multi_query', $instrumentation, $tracker, ...$args);
165167
},
166168
post: static function (...$args) use ($instrumentation, $tracker) {
167169
self::multiQueryPostHook($instrumentation, $tracker, ...$args);
@@ -171,7 +173,7 @@ public static function register(): void
171173
mysqli::class,
172174
'multi_query',
173175
pre: static function (...$args) use ($instrumentation, $tracker) {
174-
self::queryPreHook('mysqli::multi_query', $instrumentation, $tracker, ...$args);
176+
self::multiQueryPreHook('mysqli::multi_query', $instrumentation, $tracker, ...$args);
175177
},
176178
post: static function (...$args) use ($instrumentation, $tracker) {
177179
self::multiQueryPostHook($instrumentation, $tracker, ...$args);
@@ -440,11 +442,46 @@ private static function constructPostHook(int $paramsOffset, CachedInstrumentati
440442
}
441443

442444
/** @param non-empty-string $spanName */
443-
private static function queryPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, string $function, ?string $filename, ?int $lineno): void
445+
private static function queryPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, string $function, ?string $filename, ?int $lineno): array
444446
{
445447
$span = self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
446448
$mysqli = $obj ? $obj : $params[0];
449+
$query = $obj ? $params[0] : $params[1];
450+
$query = mb_convert_encoding($query ?? self::UNDEFINED, 'UTF-8');
451+
if (!is_string($query)) {
452+
$query = self::UNDEFINED;
453+
}
454+
$span->setAttributes([
455+
TraceAttributes::DB_QUERY_TEXT => $query,
456+
TraceAttributes::DB_OPERATION_NAME => self::extractQueryCommand($query),
457+
]);
458+
447459
self::addTransactionLink($tracker, $span, $mysqli);
460+
461+
if (class_exists('OpenTelemetry\Contrib\SqlCommenter\SqlCommenter') && $query !== self::UNDEFINED) {
462+
/**
463+
* @psalm-suppress UndefinedClass
464+
*/
465+
$commenter = \OpenTelemetry\Contrib\SqlCommenter\SqlCommenter::getInstance();
466+
$query = $commenter->inject($query);
467+
if ($commenter->isAttributeEnabled()) {
468+
$span->setAttributes([
469+
TraceAttributes::DB_QUERY_TEXT => (string) $query,
470+
]);
471+
}
472+
if ($obj) {
473+
return [
474+
0 => $query,
475+
];
476+
}
477+
478+
return [
479+
1 => $query,
480+
];
481+
482+
}
483+
484+
return [];
448485
}
449486

450487
private static function queryPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
@@ -454,9 +491,6 @@ private static function queryPostHook(CachedInstrumentation $instrumentation, My
454491

455492
$attributes = $tracker->getMySqliAttributes($mysqli);
456493

457-
$attributes[TraceAttributes::DB_QUERY_TEXT] = mb_convert_encoding($query, 'UTF-8');
458-
$attributes[TraceAttributes::DB_OPERATION_NAME] = self::extractQueryCommand($query);
459-
460494
if ($retVal === false || $exception) {
461495
$attributes[TraceAttributes::DB_RESPONSE_STATUS_CODE] = $mysqli->errno;
462496
}
@@ -466,6 +500,19 @@ private static function queryPostHook(CachedInstrumentation $instrumentation, My
466500

467501
}
468502

503+
/**
504+
* multi_query can execute multiple queries in one call. We will create a span for the multi_query call, but we will also track the individual queries and their results, creating spans for each query in the multi_query call.
505+
* The individual query spans will be created in the next_result hook, which is called to fetch the results of each query in the multi_query call.
506+
* As QueryPreHook has database span context propagation logic, we need to create this multiQueryPrehook function for multi_query to keep the pre-hook function unchanged.
507+
*/
508+
/** @param non-empty-string $spanName */
509+
private static function multiQueryPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, string $function, ?string $filename, ?int $lineno): void
510+
{
511+
$span = self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
512+
$mysqli = $obj ? $obj : $params[0];
513+
self::addTransactionLink($tracker, $span, $mysqli);
514+
}
515+
469516
private static function multiQueryPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
470517
{
471518

src/Instrumentation/PDO/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,11 @@ otel.instrumentation.pdo.distribute_statement_to_linked_spans = true
3333
or environment variable:
3434
```shell
3535
OTEL_PHP_INSTRUMENTATION_PDO_DISTRIBUTE_STATEMENT_TO_LINKED_SPANS=true
36+
```
37+
38+
## Database Context Propagation
39+
40+
Enable context propagation for database queries by installing the following packages:
41+
```shell
42+
composer require open-telemetry/opentelemetry-sqlcommenter
3643
```

src/Instrumentation/PDO/src/PDOInstrumentation.php

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
use PDOStatement;
2020
use Throwable;
2121

22+
/** @phan-file-suppress PhanUndeclaredClassMethod */
2223
class PDOInstrumentation
2324
{
2425
public const NAME = 'pdo';
26+
private const UNDEFINED = 'undefined';
2527

2628
public static function register(): void
2729
{
@@ -112,8 +114,12 @@ public static function register(): void
112114
/** @psalm-suppress ArgumentTypeCoercion */
113115
$builder = self::makeBuilder($instrumentation, 'PDO::query', $function, $class, $filename, $lineno)
114116
->setSpanKind(SpanKind::KIND_CLIENT);
117+
$query = mb_convert_encoding($params[0] ?? self::UNDEFINED, 'UTF-8');
118+
if (!is_string($query)) {
119+
$query = self::UNDEFINED;
120+
}
115121
if ($class === PDO::class) {
116-
$builder->setAttribute(DbAttributes::DB_QUERY_TEXT, mb_convert_encoding($params[0] ?? 'undefined', 'UTF-8'));
122+
$builder->setAttribute(DbAttributes::DB_QUERY_TEXT, $query);
117123
}
118124
$parent = Context::getCurrent();
119125
$span = $builder->startSpan();
@@ -122,6 +128,35 @@ public static function register(): void
122128
$span->setAttributes($attributes);
123129

124130
Context::storage()->attach($span->storeInContext($parent));
131+
132+
if (class_exists('OpenTelemetry\Contrib\SqlCommenter\SqlCommenter') && $query !== self::UNDEFINED) {
133+
if (array_key_exists(DbAttributes::DB_SYSTEM_NAME, $attributes)) {
134+
/** @psalm-suppress PossiblyInvalidCast */
135+
switch ((string) $attributes[DbAttributes::DB_SYSTEM_NAME]) {
136+
case 'postgresql':
137+
case 'mysql':
138+
/**
139+
* @psalm-suppress UndefinedClass
140+
*/
141+
$commenter = \OpenTelemetry\Contrib\SqlCommenter\SqlCommenter::getInstance();
142+
$query = $commenter->inject($query);
143+
if ($commenter->isAttributeEnabled()) {
144+
$span->setAttributes([
145+
DbAttributes::DB_QUERY_TEXT => (string) $query,
146+
]);
147+
}
148+
149+
return [
150+
0 => $query,
151+
];
152+
default:
153+
// Do nothing, not a database we want to propagate
154+
break;
155+
}
156+
}
157+
}
158+
159+
return [];
125160
},
126161
post: static function (PDO $pdo, array $params, mixed $statement, ?Throwable $exception) {
127162
self::end($exception);
@@ -135,8 +170,12 @@ public static function register(): void
135170
/** @psalm-suppress ArgumentTypeCoercion */
136171
$builder = self::makeBuilder($instrumentation, 'PDO::exec', $function, $class, $filename, $lineno)
137172
->setSpanKind(SpanKind::KIND_CLIENT);
173+
$query = mb_convert_encoding($params[0] ?? self::UNDEFINED, 'UTF-8');
174+
if (!is_string($query)) {
175+
$query = self::UNDEFINED;
176+
}
138177
if ($class === PDO::class) {
139-
$builder->setAttribute(DbAttributes::DB_QUERY_TEXT, mb_convert_encoding($params[0] ?? 'undefined', 'UTF-8'));
178+
$builder->setAttribute(DbAttributes::DB_QUERY_TEXT, $query);
140179
}
141180
$parent = Context::getCurrent();
142181
$span = $builder->startSpan();
@@ -145,6 +184,35 @@ public static function register(): void
145184
$span->setAttributes($attributes);
146185

147186
Context::storage()->attach($span->storeInContext($parent));
187+
188+
if (class_exists('OpenTelemetry\Contrib\SqlCommenter\SqlCommenter') && $query !== self::UNDEFINED) {
189+
if (array_key_exists(DbAttributes::DB_SYSTEM_NAME, $attributes)) {
190+
/** @psalm-suppress PossiblyInvalidCast */
191+
switch ((string) $attributes[DbAttributes::DB_SYSTEM_NAME]) {
192+
case 'postgresql':
193+
case 'mysql':
194+
/**
195+
* @psalm-suppress UndefinedClass
196+
*/
197+
$commenter = \OpenTelemetry\Contrib\SqlCommenter\SqlCommenter::getInstance();
198+
$query = $commenter->inject($query);
199+
if ($commenter->isAttributeEnabled()) {
200+
$span->setAttributes([
201+
DbAttributes::DB_QUERY_TEXT => (string) $query,
202+
]);
203+
}
204+
205+
return [
206+
0 => $query,
207+
];
208+
default:
209+
// Do nothing, not a database we want to propagate
210+
break;
211+
}
212+
}
213+
}
214+
215+
return [];
148216
},
149217
post: static function (PDO $pdo, array $params, mixed $statement, ?Throwable $exception) {
150218
self::end($exception);

src/Instrumentation/PostgreSql/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ The extension can be disabled via [runtime configuration](https://opentelemetry.
6767
OTEL_PHP_DISABLED_INSTRUMENTATIONS=postgresql
6868
```
6969

70+
## Database Context Propagation
71+
72+
Enable context propagation for database queries by installing the following packages:
73+
```shell
74+
composer require open-telemetry/opentelemetry-sqlcommenter
75+
```
76+
7077
## Compatibility
7178

7279
PHP 8.2 or newer is required

0 commit comments

Comments
 (0)