Skip to content

Conversation

@jerrytfleung
Copy link
Contributor

@jerrytfleung jerrytfleung commented Jul 14, 2025

Description:

Currently, there isn't any sqlcommenter for the database instrumentation library that allows customers to correlate their span with the database query. This PR adds the implementation for sqlcommenter according to semantic-conventions PR

  • Updated PDO instrumentation to add sqlcommenter feature

Testing:

Unit Testing:

Wrote unit tests for sql comment for correctness and they're all passing.

Manual Testing:

  1. Run sqlcommenter query against a PostgreSQL database
[
    {
        "name": "PDO::query",
        "context": {
            "trace_id": "5ecd8a80fabe727e8500067f49d48d53",
            "span_id": "9f68c175bb598ac8",
            "trace_state": "",
            "trace_flags": 1
        },
        "resource": {
            "service.name": "auto-pgsql",
            "host.name": "otel-VMware20-1",
            "host.arch": "aarch64",
            "host.id": "a6046f12d335446a880c0d1f7366f46a",
            "os.type": "linux",
            "os.description": "6.11.0-29-generic",
            "os.name": "Linux",
            "os.version": "#29~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jun 26 13:59:03 UTC 2",
            "process.runtime.name": "cli-server",
            "process.runtime.version": "8.3.6",
            "process.pid": 5453,
            "process.executable.path": "\/usr\/bin\/php8.3",
            "process.owner": "otel",
            "sw.data.module": "apm",
            "sw.apm.version": "1.0.0+no-version-set",
            "telemetry.sdk.name": "opentelemetry",
            "telemetry.sdk.language": "php",
            "telemetry.sdk.version": "1.6.0",
            "telemetry.distro.name": "opentelemetry-php-instrumentation",
            "telemetry.distro.version": "1.1.3",
            "service.instance.id": "761e5800-ee00-45a0-96ad-0d1b4a61ca2f"
        },
        "parent_span_id": "2087ce003b4d0b9b",
        "kind": "KIND_CLIENT",
        "start": 1752530856479431714,
        "end": 1752530856649181220,
        "attributes": {
            "code.function.name": "PDO::query",
            "db.query.text": "SELECT code, name, region from country LIMIT 1\/*traceparent='00-5ecd8a80fabe727e8500067f49d48d53-9f68c175bb598ac8-01'*\/;",
            "server.address": "44.203.164.2",
            "db.namespace": "world-db",
            "db.system.name": "postgresql"
        },
        "status": {
            "code": "Unset",
            "description": ""
        },
        "events": [],
        "links": [],
        "schema_url": "https:\/\/opentelemetry.io\/schemas\/1.32.0"
    }
]

From PostgreSQL database server log,
We are able to find the modified SQL statement

2025-07-14 22:07:36.539 UTC,"world","world-db",450907,"24.87.147.252:58074",68757fa7.6e15b,1,"PARSE",2025-07-14 22:07:35 UTC,6/74550,0,LOG,00000,"duration: 0.543 ms  parse pdo_stmt_00000001: SELECT code, name, region from country LIMIT 1/*traceparent='00-5ecd8a80fabe727e8500067f49d48d53-9f68c175bb598ac8-01'*/;",,,,,,,,,"","client backend",,625232117496043950
2025-07-14 22:07:36.625 UTC,"world","world-db",450907,"24.87.147.252:58074",68757fa7.6e15b,2,"BIND",2025-07-14 22:07:35 UTC,6/74551,0,LOG,00000,"duration: 3.720 ms  bind pdo_stmt_00000001: SELECT code, name, region from country LIMIT 1/*traceparent='00-5ecd8a80fabe727e8500067f49d48d53-9f68c175bb598ac8-01'*/;",,,,,,,,,"","client backend",,625232117496043950
2025-07-14 22:07:36.625 UTC,"world","world-db",450907,"24.87.147.252:58074",68757fa7.6e15b,3,"SELECT",2025-07-14 22:07:35 UTC,6/74551,0,LOG,00000,"execute pdo_stmt_00000001: SELECT code, name, region from country LIMIT 1/*traceparent='00-5ecd8a80fabe727e8500067f49d48d53-9f68c175bb598ac8-01'*/;",,,,,,,,,"","client backend",,625232117496043950

@welcome
Copy link

welcome bot commented Jul 14, 2025

Thanks for opening your first pull request! If you haven't yet signed our Contributor License Agreement (CLA), then please do so that we can accept your contribution. A link should appear shortly in this PR if you have not already signed one.

@codecov
Copy link

codecov bot commented Jul 14, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.57%. Comparing base (afc575f) to head (d540582).
⚠️ Report is 7 commits behind head on main.

❗ There is a different number of reports uploaded between BASE (afc575f) and HEAD (d540582). Click for more details.

HEAD has 28 uploads less than BASE
Flag BASE (afc575f) HEAD (d540582)
Instrumentation/CodeIgniter 1 0
Instrumentation/ReactPHP 1 0
Instrumentation/Psr3 1 0
Propagation/CloudTrace 1 0
Instrumentation/AwsSdk 1 0
Instrumentation/Psr14 1 0
Instrumentation/Curl 1 0
ResourceDetectors/Container 1 0
Shims/OpenTracing 1 0
Instrumentation/Doctrine 1 0
Aws 1 0
Instrumentation/Slim 1 0
Instrumentation/PDO 1 0
Instrumentation/Psr15 1 0
Instrumentation/Symfony 1 0
Exporter/Instana 1 0
Instrumentation/Psr18 1 0
Sampler/Xray 1 0
Instrumentation/Yii 1 0
Instrumentation/Guzzle 1 0
ResourceDetectors/Azure 1 0
Instrumentation/IO 1 0
Instrumentation/Psr6 1 0
Instrumentation/Laravel 1 0
Instrumentation/PostgreSql 1 0
Instrumentation/ExtAmqp 1 0
Instrumentation/MySqli 1 0
Instrumentation/ExtRdKafka 1 0
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #406      +/-   ##
============================================
- Coverage     81.55%   75.57%   -5.98%     
+ Complexity     1659      525    -1134     
============================================
  Files           131       36      -95     
  Lines          6848     1986    -4862     
============================================
- Hits           5585     1501    -4084     
+ Misses         1263      485     -778     
Flag Coverage Δ
Aws ?
Context/Swoole 0.00% <ø> (ø)
Exporter/Instana ?
Instrumentation/AwsSdk ?
Instrumentation/CakePHP 20.40% <ø> (ø)
Instrumentation/CodeIgniter ?
Instrumentation/Curl ?
Instrumentation/Doctrine ?
Instrumentation/ExtAmqp ?
Instrumentation/ExtRdKafka ?
Instrumentation/Guzzle ?
Instrumentation/HttpAsyncClient 78.04% <ø> (ø)
Instrumentation/IO ?
Instrumentation/Laravel ?
Instrumentation/MySqli ?
Instrumentation/OpenAIPHP 87.21% <ø> (ø)
Instrumentation/PDO ?
Instrumentation/PostgreSql ?
Instrumentation/Psr14 ?
Instrumentation/Psr15 ?
Instrumentation/Psr16 97.50% <ø> (ø)
Instrumentation/Psr18 ?
Instrumentation/Psr3 ?
Instrumentation/Psr6 ?
Instrumentation/ReactPHP ?
Instrumentation/Slim ?
Instrumentation/Symfony ?
Instrumentation/Yii ?
Logs/Monolog 100.00% <ø> (ø)
Propagation/CloudTrace ?
Propagation/Instana 98.11% <ø> (ø)
Propagation/ServerTiming 100.00% <ø> (ø)
Propagation/TraceResponse 100.00% <ø> (ø)
ResourceDetectors/Azure ?
ResourceDetectors/Container ?
ResourceDetectors/DigitalOcean 100.00% <ø> (ø)
Sampler/RuleBased 33.51% <ø> (ø)
Sampler/Xray ?
Shims/OpenTracing ?
Utils/Test 87.53% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 105 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update afc575f...d540582. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jerrytfleung jerrytfleung changed the title Service name propagator Add SQL commenter as context propagation for databases Jul 14, 2025
Context::storage()->attach($span->storeInContext($parent));
if (self::isSqlCommenterEnabled() && $sqlStatement !== 'undefined') {
$sqlStatement = self::appendSqlComments($sqlStatement, true);
$span->setAttributes([
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's redundant to include the sql comments in the statement, that info is already available in the trace. Does the spec suggest to do this? (comment applies to several similar bits of code)

Copy link

Choose a reason for hiding this comment

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

The spec doesn't suggest this, it's a bit from our own experience--capturing the final statement in the attribute helps easily diagnose integration issues by looking at the trace (e.g. database monitoring expects certain info in the comment but didn't see it) rather than digging through server logs/system views.

But there has been the concern that including this in db.query.text makes the value high cardinality which may not be handled well by all tracing backends. What we did for Python contrib was to make it a config option whether to collect query text before or after injection. Defer to PHP maintainers though, if you simply want to always capture pre-injection.

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, there is no spec / requirement to update db.query.text.
As the instrumentation library changed the sql statement and the database is actually going to execute the modified statement, so updating TraceAttributes::DB_QUERY_TEXT to reflect the truth and it helps diagnose too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Tracking the actual (mutated) statement that the DB executed does sound reasonable, but I do share the concern that it makes the value high cardinality. Can we make it opt-in?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. Done!


use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;

class Opentelemetry
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you look at what contrib-auto-slim does with the still-in-draft response propagation? I don't think this is a good class name, what does it do? Should there be an interface that it implements?

Copy link
Contributor Author

@jerrytfleung jerrytfleung Jul 15, 2025

Choose a reason for hiding this comment

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

I am not sure which still in draft PR. Can you put the url here?

Agreed it is not a very good name, my initial thought is to keep the google sqlcommenter code. This class is to retrieve the trace context / service name required to comment sql statement. Do you have any idea about the class name?

I think using interface is a good idea as Instrumentation\MySqli will also call the same in order to retrieve trace context / service name.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think what this class needs to be is an instance of a TextMapPropagator, or perhaps its own interface like DatabaseQueryPropagationSetter (the terminology should be spec-driven), where "carrier" is a string (database query text), and this is where the query is injected with sql comments.

In my mind, it could work something like this (based on https://github.com/open-telemetry/opentelemetry-php-contrib/blob/main/src/Instrumentation/Slim/src/SlimInstrumentation.php#L100):

if ($dbPropagationEnabled && $doServiceNamePropagation) {
  if (class_exists(`\OpenTelemetry\...\ServiceNamePropagator`)) {
     $prop = new \OpenTelemetry\...\ServiceNamePropagator();
     $prop->inject($dbQueryString, DatabaseQueryPropagationSetter::instance(), $scope->context());
   }
}

//similar for trace context propagation

I also wonder if we should use Globals::propagator() for the trace context propagation. Users can configure and use different propagators or even provide their own (for example, for AWS x-ray), and may want those values to be propagated as well as/instead of ours? (Has this come up in spec discussions?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I understand correctly, it seems we don't need the Opentelemetry class but putting the injection part to the instrumentation code.
I updated the code in this commit. Please correct me if I get it wrong.

SQL commenter in theory is to put information about the code in the query statement and it is not limited to trace context / service name. However, the majority implementation is putting a trace context ( or the proposed service name) so that user can correlate the trace with the database queries. Also, there is a limit about the length of the query statement so I am not so sure about opening up to different propagators and that is not in the spec discussions.

Copy link

@cheempz cheempz Jul 16, 2025

Choose a reason for hiding this comment

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

Re:

I also wonder if we should use Globals::propagator() for the trace context propagation. Users can configure and use different propagators or even provide their own (for example, for AWS x-ray), and may want those values to be propagated as well as/instead of ours? (Has this come up in spec discussions?)

Yes this was discussed in the spec, there seems consensus that the default would be the global propagator.

And users should be able to configure their own propagator(s), which if multiple, can be passed via a composite propagator. So my take is our implementation should accept a composite propagator instead of the current logic of running through "if with trace context", "if service propagator exists".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using Globals::propagator() can add flexibility to configure other propagators, but it would also add high
cardinality risk and make the comment longer.
I don't think it is a good idea to include Globals as we just want to correlate the trace with the database query.

Copy link

Choose a reason for hiding this comment

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

High cardinality will result just from the traceontext propagation, but it is a valid concern about length of comment. We may need yet another 😬 config to limit that.

{
$setter ??= ArrayAccessGetterSetter::getInstance();

$detector = new Detectors\Service();
Copy link
Contributor

Choose a reason for hiding this comment

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

This will need some thought. Resources including service.name can come from a variety of sources (for example, a yaml file in declarative config). I think the best source is the resource that's associated with the tracer, but that's not currently accessible. Perhaps service name needs to be added to SpanContext, so that it can be accessed in the same way that other components are for context propagation? That's a spec change, we won't just add it to our API and SDK.

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, definitely the best source is from Tracer. Agreed it requires a spec change and involves a change in the API and SDK. I will monitor the discussion of the semconv to see what is the recommendation to get the service name.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this an active question or issue in spec and/or semconv? If so, can you link to it? I think it should block this being merged.

Copy link
Contributor Author

@jerrytfleung jerrytfleung Jul 16, 2025

Choose a reason for hiding this comment

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

Yes, this is the semconv PR. I saw you put comments there. Indeed there is still no breakthrough on how to get the service name.

Copy link
Contributor

Choose a reason for hiding this comment

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

The most recent comments on that PR suggest that it should be provided by the user and it may be the service name or some completely different user-defined value. We can wait for that to be agreed upon, but to me that means it's not coming from a detector, but actual user input. For an auto-instrumentation, that could be an env var or from declarative configuration.

Copy link

Choose a reason for hiding this comment

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

I think the recommendation for MS SQL Server is to use CONTEXT_INFO instead of sqlcomment as propagation method. As mentioned in #406 (comment) i really think we should limit this first cut to just the DBMS we're testing against, e.g. MySQL and maybe Postgres.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Limited to postgresql and mysql only

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 The spec mentioned the The instrumentation implementation SHOULD append the comment to the end of the query. Should I remove the option to allow prepending the query?

Copy link
Contributor

Choose a reason for hiding this comment

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

It goes on to say that Semantic conventions for individual database systems MAY specify different format, which may include different position, encoding, or schema, depending on the specific database system's requirements or preferences.
Do we know of any databases where prepending could cause an issue, or is it just user preference (in which case, removing the option seems ok to me) ?

Copy link

@cheempz cheempz Aug 27, 2025

Choose a reason for hiding this comment

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

I'd vote to keep this option in, based on the semconv discussion where there are valid reasons for the user to choose prepend depending on database.

@cheempz
Copy link

cheempz commented Jul 15, 2025

@jerrytfleung i know it's not addressed yet in the semconv, but we probably do want configuration to explicitly opt into particular DB systems. For example only enable for MySQL and not for MS SQL Server, etc.

@bobstrecansky bobstrecansky added the blocked blocked on some sort of external dependency label Jul 16, 2025
@jerrytfleung
Copy link
Contributor Author

@jerrytfleung i know it's not addressed yet in the semconv, but we probably do want configuration to explicitly opt into particular DB systems. For example only enable for MySQL and not for MS SQL Server, etc.

I am not sure if I get it correctly. Does it mean we need a different implementation for MS SQL Server? From this and that, it is using SET CONTEXT_INFO to pass information to MS SQL Server.

@cheempz
Copy link

cheempz commented Jul 16, 2025

@jerrytfleung i know it's not addressed yet in the semconv, but we probably do want configuration to explicitly opt into particular DB systems. For example only enable for MySQL and not for MS SQL Server, etc.

I am not sure if I get it correctly. Does it mean we need a different implementation for MS SQL Server? From this and that, it is using SET CONTEXT_INFO to pass information to MS SQL Server.

That's right, there's now the MS SQL Server-specific semconv that states trace context propagation should be achieved via the CONTEXT_INFO mechanism. So we should not blindly enable sqlcomment injection of trace context into every database system supported at the PDO level; we should allow explicit opt-in to each db system and i'd even say hardcoded to limit to systems where we've done some testing against.

@jerrytfleung jerrytfleung marked this pull request as ready for review July 18, 2025 19:34
@jerrytfleung jerrytfleung requested a review from a team as a code owner July 18, 2025 19:34
private static function addSqlComments(string $query): string
{
$comments = [];
$prop = TraceContextPropagator::getInstance();
Copy link
Contributor

Choose a reason for hiding this comment

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

other propagators can be configured, and the correct way to get them would be via Globals

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. Done.

return filter_var(get_cfg_var('otel.instrumentation.pdo.distribute_statement_to_linked_spans'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
}

private static function isSqlCommenterEnabled(): bool
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a lot of this code could be moved into its own class, and then be unit tested. It seems like it shouldn't be called "sql commenter" - that was the name of the product before it was donated to otel, but it looks like the objective is to define how to do something similar/better. I guess we're waiting on approved spec changes to see what the names of things should be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I can see in the pending approval PR, the name should be something like "Context Propagation", and SQL commenter is a way to realize the "Context Propagation".

Is ContextPropagation a good class name for you?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Moved to its own class, named ContextPropagation. Please see if it looks good. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

If you were to add the sql-server propagation mentioned elsewhere in this PR, would it be in that class, or in a different, similar class? I feel like it should be in a different one, and so maybe SqlPropagator is an interface, and SqlCommentPropagator is this class, with potentially a SqlServerPropagator coming later which uses set_context_info?

Copy link
Contributor Author

@jerrytfleung jerrytfleung Jul 25, 2025

Choose a reason for hiding this comment

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

Not going to implement the sql-server propagation in this PR so as to make this scope manageable but I am happy to add the placeholder class SqlServerPropagator first.

Done! I added the class SqlPropagatorInterface, SqlCommenterPropagator & SqlServerPropagator.

@jerrytfleung jerrytfleung requested a review from brettmc July 23, 2025 18:24
@jerrytfleung
Copy link
Contributor Author

@bobstrecansky open-telemetry/semantic-conventions#2495 has been merged to the spec. Can you please unblock this PR?
@brettmc I addressed the comments. Please take a look. Thanks!

Copy link

@cheempz cheempz left a comment

Choose a reason for hiding this comment

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

Jerry I added a couple questions/comments. Also, now that the semconv is merged, maybe the README can refer to that instead.

case 'postgresql':
case 'mysql':
$comments = [];
Globals::propagator()->inject($comments);
Copy link

Choose a reason for hiding this comment

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

@jerrytfleung the semconv states:

The instrumentation SHOULD allow users to pass a propagator to overwrite the global propagator. If no propagator is provided by the user, instrumentation SHOULD use the global propagator.

It doesn't seem we currently support this. And side curiosity why not have the SqlCommentPropagator use global propagator directly instead of passing around $comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is because SqlCommentPropagator is not registered in Registry. The Globals::propagator() needs environment variable OTEL_PROPAGATORS https://github.com/open-telemetry/opentelemetry-php/blob/main/src/SDK/Propagation/PropagatorFactory.php#L21 to initialize the propagator list.


namespace OpenTelemetry\Contrib\Instrumentation\PDO;

class SqlServerPropagator implements SqlPropagatorInterface
Copy link

Choose a reason for hiding this comment

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

@jerrytfleung naming nit ;)... IMHO ContextInfoPropagator makes more sense given the other one is called SqlCommentPropagator.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. Done

@jerrytfleung jerrytfleung force-pushed the service_name_propagator branch from 6e65ec4 to ee41b2a Compare August 29, 2025 19:51
@jerrytfleung
Copy link
Contributor Author

Jerry I added a couple questions/comments. Also, now that the semconv is merged, maybe the README can refer to that instead.

Sure. Changed the underlying link to the otel page.

@jerrytfleung
Copy link
Contributor Author

Updated the implementation w.r.t. to the spec

  • Added capability for user to pass a propagator to overwrite the global propagator via OTEL_PHP_INSTRUMENTATION_PDO_CONTEXT_PROPAGATORS env
  • Added more tests

@jerrytfleung
Copy link
Contributor Author

jerrytfleung commented Sep 17, 2025

@brettmc Further decoupled the sqlcommenter logic in a separate package and update PDO & MySqli & PostgreSql for adding that feature in this PR. It is a replacement of this PR.

@bobstrecansky
Copy link
Contributor

@jerrytfleung - should this PR be replaced with #442 ?

@brettmc
Copy link
Contributor

brettmc commented Oct 5, 2025

Closing this, as it seems to be superseded by #442 please re-open and clarify if not.

@brettmc brettmc closed this Oct 5, 2025
@jerrytfleung
Copy link
Contributor Author

@bobstrecansky Yes, please close this PR. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blocked blocked on some sort of external dependency

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants