Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ updates:
- "/src/ResourceDetectors/DigitalOcean"
- "/src/Sampler/RuleBased"
- "/src/Shims/OpenTracing"
- "/src/SqlCommenter"
- "/src/Symfony"
- "/src/Symfony/src/OtelBundle"
- "/src/Symfony/src/OtelSdkBundle"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
'Sampler/RuleBased',
'Sampler/Xray',
'Shims/OpenTracing',
'SqlCommenter',
'Symfony',
'Utils/Test'
]
Expand Down
2 changes: 2 additions & 0 deletions .gitsplit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ splits:
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sampler-aws-xray.git"
- prefix: "src/Shims/OpenTracing"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-shim-opentracing.git"
- prefix: "src/SqlCommenter"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sqlcommenter.git"
- prefix: "src/Symfony"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sdk-bundle.git"
- prefix: "src/Utils/Test"
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"OpenTelemetry\\Contrib\\Resource\\Detector\\DigitalOcean\\": "src/ResourceDetectors/DigitalOcean/src",
"OpenTelemetry\\Contrib\\Sampler\\RuleBased\\": "src/Sampler/RuleBased/src",
"OpenTelemetry\\Contrib\\Shim\\OpenTracing\\": "src/Shims/OpenTracing/src",
"OpenTelemetry\\Contrib\\SqlCommenter\\": "src/SqlCommenter/src",
"OpenTelemetry\\Contrib\\Symfony\\": "src/Symfony/src",
"OpenTelemetry\\TestUtils\\": "src/Utils/Test/src"
},
Expand Down Expand Up @@ -125,6 +126,7 @@
"OpenTelemetry\\Tests\\Propagation\\CloudTrace\\": "src/Propagation/CloudTrace/tests",
"OpenTelemetry\\Tests\\Resource\\Detector\\Azure\\": "src/ResourceDetectors/Azure/tests",
"OpenTelemetry\\Contrib\\Resource\\Detector\\DigitalOcean\\": "src/ResourceDetectors/DigitalOcean/tests",
"OpenTelemetry\\Tests\\Contrib\\SqlCommenter\\": "src/SqlCommenter/tests",
"OpenTelemetry\\Tests\\Contrib\\Symfony\\": "src/Symfony/tests",
"OpenTelemetry\\TestUtils\\Tests\\": "src/Utils/Test/tests"
}
Expand Down Expand Up @@ -171,6 +173,7 @@
"open-telemetry/opentelemetry-propagation-instana": "self.version",
"open-telemetry/opentelemetry-propagation-server-timing": "self.version",
"open-telemetry/opentelemetry-propagation-traceresponse": "self.version",
"open-telemetry/opentelemetry-sqlcommenter": "self.version",
"open-telemetry/opentracing-shim": "self.version",
"open-telemetry/sampler-rule-based": "self.version",
"open-telemetry/symfony-sdk-bundle": "self.version",
Expand Down
6 changes: 6 additions & 0 deletions src/Instrumentation/MySqli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ The extension can be disabled via [runtime configuration](https://opentelemetry.
OTEL_PHP_DISABLED_INSTRUMENTATIONS=mysqli
```

## Database Context Propagation

Enable context propagation for database queries by installing the following packages:
```shell
composer require open-telemetry/opentelemetry-sqlcommenter
```
67 changes: 57 additions & 10 deletions src/Instrumentation/MySqli/src/MySqliInstrumentation.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@

/**
* @phan-file-suppress PhanParamTooFewUnpack
* @phan-file-suppress PhanUndeclaredClassMethod
*/
class MySqliInstrumentation
{
use LogsMessagesTrait;

public const NAME = 'mysqli';
private const UNDEFINED = 'undefined';

private const MYSQLI_CONNECT_ARG_OFFSET = 0;
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
Expand Down Expand Up @@ -98,7 +100,7 @@ public static function register(): void
null,
'mysqli_query',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::queryPreHook('mysqli_query', $instrumentation, $tracker, ...$args);
return self::queryPreHook('mysqli_query', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::queryPostHook($instrumentation, $tracker, ...$args);
Expand All @@ -108,7 +110,7 @@ public static function register(): void
mysqli::class,
'query',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::queryPreHook('mysqli::query', $instrumentation, $tracker, ...$args);
return self::queryPreHook('mysqli::query', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::queryPostHook($instrumentation, $tracker, ...$args);
Expand All @@ -119,7 +121,7 @@ public static function register(): void
null,
'mysqli_real_query',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::queryPreHook('mysqli_real_query', $instrumentation, $tracker, ...$args);
return self::queryPreHook('mysqli_real_query', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::queryPostHook($instrumentation, $tracker, ...$args);
Expand All @@ -129,7 +131,7 @@ public static function register(): void
mysqli::class,
'real_query',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::queryPreHook('mysqli::real_query', $instrumentation, $tracker, ...$args);
return self::queryPreHook('mysqli::real_query', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::queryPostHook($instrumentation, $tracker, ...$args);
Expand Down Expand Up @@ -161,7 +163,7 @@ public static function register(): void
null,
'mysqli_multi_query',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::queryPreHook('mysqli_multi_query', $instrumentation, $tracker, ...$args);
self::multiQueryPreHook('mysqli_multi_query', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::multiQueryPostHook($instrumentation, $tracker, ...$args);
Expand All @@ -171,7 +173,7 @@ public static function register(): void
mysqli::class,
'multi_query',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::queryPreHook('mysqli::multi_query', $instrumentation, $tracker, ...$args);
self::multiQueryPreHook('mysqli::multi_query', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::multiQueryPostHook($instrumentation, $tracker, ...$args);
Expand Down Expand Up @@ -440,11 +442,46 @@ private static function constructPostHook(int $paramsOffset, CachedInstrumentati
}

/** @param non-empty-string $spanName */
private static function queryPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, string $function, ?string $filename, ?int $lineno): void
private static function queryPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, string $function, ?string $filename, ?int $lineno): array
{
$span = self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
$mysqli = $obj ? $obj : $params[0];
$query = $obj ? $params[0] : $params[1];
$query = mb_convert_encoding($query ?? self::UNDEFINED, 'UTF-8');
if (!is_string($query)) {
$query = self::UNDEFINED;
}
$span->setAttributes([
TraceAttributes::DB_QUERY_TEXT => $query,
TraceAttributes::DB_OPERATION_NAME => self::extractQueryCommand($query),
]);

self::addTransactionLink($tracker, $span, $mysqli);

if (class_exists('OpenTelemetry\Contrib\SqlCommenter\SqlCommenter') && $query !== self::UNDEFINED) {
Copy link

@cheempz cheempz Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the previous PR #406 the SQL Commenter feature was enabled via an explicit configuration. This PR proposes instead the customers opt-in by installing the sqlcommenter package, correct? Can the READMEs for each database instrumentation be updated to provide usage / configuration info?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the opt-in is by installing the sqlcommenter & context-propagator.
Updated the README.md per each database instrumentation library.

/**
* @psalm-suppress UndefinedClass
*/
$commenter = \OpenTelemetry\Contrib\SqlCommenter\SqlCommenter::getInstance();
$query = $commenter->inject($query);
if ($commenter->isAttributeEnabled()) {
$span->setAttributes([
TraceAttributes::DB_QUERY_TEXT => (string) $query,
]);
}
if ($obj) {
return [
0 => $query,
];
}

return [
1 => $query,
];

}

return [];
}

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

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

$attributes[TraceAttributes::DB_QUERY_TEXT] = mb_convert_encoding($query, 'UTF-8');
$attributes[TraceAttributes::DB_OPERATION_NAME] = self::extractQueryCommand($query);

if ($retVal === false || $exception) {
$attributes[TraceAttributes::DB_RESPONSE_STATUS_CODE] = $mysqli->errno;
}
Expand All @@ -466,6 +500,19 @@ private static function queryPostHook(CachedInstrumentation $instrumentation, My

}

/**
* 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.
* 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.
* 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.
*/
/** @param non-empty-string $spanName */
private static function multiQueryPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, string $function, ?string $filename, ?int $lineno): void
{
$span = self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
$mysqli = $obj ? $obj : $params[0];
self::addTransactionLink($tracker, $span, $mysqli);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why this prehook was added?

Copy link
Contributor Author

@jerrytfleung jerrytfleung Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is for multi_query scenario where it works so differently when compared to query and real_query.
In order to reflect the db query text attribute for pre-or-post comment injection, I need to move the assignment of DB_QUERY_TEXT from post hook to pre hook. query and real_query works in this way.

For multi_query, the DB_QUERY_TEXT is set at the post hook and also the next_result post hook. There is no easy way to maintain the same QueryPrehook for multi_query. So I added a pre hook.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not seeing what this added code is doing for multi query, can you explain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments to the function to explain.

In short, multi_query shared the same pre-hook function QueryPreHook as query and real_query. I need to create a new pre hook to keep multi_query works the same logic as before.


private static function multiQueryPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
{

Expand Down
7 changes: 7 additions & 0 deletions src/Instrumentation/PDO/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,11 @@ otel.instrumentation.pdo.distribute_statement_to_linked_spans = true
or environment variable:
```shell
OTEL_PHP_INSTRUMENTATION_PDO_DISTRIBUTE_STATEMENT_TO_LINKED_SPANS=true
```

## Database Context Propagation

Enable context propagation for database queries by installing the following packages:
```shell
composer require open-telemetry/opentelemetry-sqlcommenter
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now it would be enabled for both postgresql or mysql under PDO, can we add a config to enable only specific driver?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brettmc / @open-telemetry/php-maintainers Do you think it is a good idea to create a config to enable only specific driver?
e.g.
OTEL_PHP_SQLCOMMENTER_OPT_IN_DATABASE where default value = mysql and valid values are { mysql, postgresql }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happy to wait for somebody to ask for it, this is a 0.1 release

```
72 changes: 70 additions & 2 deletions src/Instrumentation/PDO/src/PDOInstrumentation.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
use PDOStatement;
use Throwable;

/** @phan-file-suppress PhanUndeclaredClassMethod */
class PDOInstrumentation
{
public const NAME = 'pdo';
private const UNDEFINED = 'undefined';

public static function register(): void
{
Expand Down Expand Up @@ -112,8 +114,12 @@ public static function register(): void
/** @psalm-suppress ArgumentTypeCoercion */
$builder = self::makeBuilder($instrumentation, 'PDO::query', $function, $class, $filename, $lineno)
->setSpanKind(SpanKind::KIND_CLIENT);
$query = mb_convert_encoding($params[0] ?? self::UNDEFINED, 'UTF-8');
if (!is_string($query)) {
$query = self::UNDEFINED;
}
if ($class === PDO::class) {
$builder->setAttribute(DbAttributes::DB_QUERY_TEXT, mb_convert_encoding($params[0] ?? 'undefined', 'UTF-8'));
$builder->setAttribute(DbAttributes::DB_QUERY_TEXT, $query);
}
$parent = Context::getCurrent();
$span = $builder->startSpan();
Expand All @@ -122,6 +128,35 @@ public static function register(): void
$span->setAttributes($attributes);

Context::storage()->attach($span->storeInContext($parent));

if (class_exists('OpenTelemetry\Contrib\SqlCommenter\SqlCommenter') && $query !== self::UNDEFINED) {
if (array_key_exists(DbAttributes::DB_SYSTEM_NAME, $attributes)) {
/** @psalm-suppress PossiblyInvalidCast */
switch ((string) $attributes[DbAttributes::DB_SYSTEM_NAME]) {
case 'postgresql':
case 'mysql':
/**
* @psalm-suppress UndefinedClass
*/
$commenter = \OpenTelemetry\Contrib\SqlCommenter\SqlCommenter::getInstance();
$query = $commenter->inject($query);
if ($commenter->isAttributeEnabled()) {
$span->setAttributes([
DbAttributes::DB_QUERY_TEXT => (string) $query,
]);
}

return [
0 => $query,
];
default:
// Do nothing, not a database we want to propagate
break;
}
}
}

return [];
},
post: static function (PDO $pdo, array $params, mixed $statement, ?Throwable $exception) {
self::end($exception);
Expand All @@ -135,8 +170,12 @@ public static function register(): void
/** @psalm-suppress ArgumentTypeCoercion */
$builder = self::makeBuilder($instrumentation, 'PDO::exec', $function, $class, $filename, $lineno)
->setSpanKind(SpanKind::KIND_CLIENT);
$query = mb_convert_encoding($params[0] ?? self::UNDEFINED, 'UTF-8');
if (!is_string($query)) {
$query = self::UNDEFINED;
}
if ($class === PDO::class) {
$builder->setAttribute(DbAttributes::DB_QUERY_TEXT, mb_convert_encoding($params[0] ?? 'undefined', 'UTF-8'));
$builder->setAttribute(DbAttributes::DB_QUERY_TEXT, $query);
}
$parent = Context::getCurrent();
$span = $builder->startSpan();
Expand All @@ -145,6 +184,35 @@ public static function register(): void
$span->setAttributes($attributes);

Context::storage()->attach($span->storeInContext($parent));

if (class_exists('OpenTelemetry\Contrib\SqlCommenter\SqlCommenter') && $query !== self::UNDEFINED) {
if (array_key_exists(DbAttributes::DB_SYSTEM_NAME, $attributes)) {
/** @psalm-suppress PossiblyInvalidCast */
switch ((string) $attributes[DbAttributes::DB_SYSTEM_NAME]) {
case 'postgresql':
case 'mysql':
/**
* @psalm-suppress UndefinedClass
*/
$commenter = \OpenTelemetry\Contrib\SqlCommenter\SqlCommenter::getInstance();
$query = $commenter->inject($query);
if ($commenter->isAttributeEnabled()) {
$span->setAttributes([
DbAttributes::DB_QUERY_TEXT => (string) $query,
]);
}

return [
0 => $query,
];
default:
// Do nothing, not a database we want to propagate
break;
}
}
}

return [];
},
post: static function (PDO $pdo, array $params, mixed $statement, ?Throwable $exception) {
self::end($exception);
Expand Down
7 changes: 7 additions & 0 deletions src/Instrumentation/PostgreSql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ The extension can be disabled via [runtime configuration](https://opentelemetry.
OTEL_PHP_DISABLED_INSTRUMENTATIONS=postgresql
```

## Database Context Propagation

Enable context propagation for database queries by installing the following packages:
```shell
composer require open-telemetry/opentelemetry-sqlcommenter
```

## Compatibility

PHP 8.2 or newer is required
Loading