Repository: open-telemetry/opentelemetry-php-contrib
Pull request #300: Add support for Doctrine auto-instrumentation
Author: login: DominicDetta
Created: 2024-10-03T07:05:38Z | Merged: 2025-03-12T02:16:13Z



Diff:
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index 8f1351eba..cfc53004f 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -25,6 +25,7 @@ jobs:
           'Instrumentation/CakePHP',
           'Instrumentation/CodeIgniter',
           'Instrumentation/Curl',
+          'Instrumentation/Doctrine',
           'Instrumentation/ExtAmqp',
           'Instrumentation/ExtRdKafka',
           'Instrumentation/Guzzle',
@@ -55,18 +56,21 @@ jobs:
           'Symfony',
         ]
         exclude:
-          - project: 'Instrumentation/IO'
-            php-version: 8.1
+
           - project: 'Instrumentation/Curl'
             php-version: 8.1
-          - project: 'Instrumentation/MySqli'
-            php-version: 8.1
-          - project: 'Instrumentation/PDO'
+          - project: 'Instrumentation/Doctrine'
             php-version: 8.1
           - project: 'Instrumentation/ExtAmqp'
             php-version: 8.1
           - project: 'Instrumentation/ExtRdKafka'
             php-version: 8.1
+          - project: 'Instrumentation/IO'
+            php-version: 8.1
+          - project: 'Instrumentation/MySqli'
+            php-version: 8.1
+          - project: 'Instrumentation/PDO'
+            php-version: 8.1
     steps:
     - uses: actions/checkout@v4
 
diff --git a/.gitsplit.yml b/.gitsplit.yml
index 23ee30cc0..77a10900a 100644
--- a/.gitsplit.yml
+++ b/.gitsplit.yml
@@ -58,6 +58,8 @@ splits:
     target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-auto-cakephp.git"
   - prefix: "src/Instrumentation/Yii"
     target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-auto-yii.git"
+  - prefix: "src/Instrumentation/Doctrine"
+    target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-auto-doctrine.git"
   - prefix: "src/Context/Swoole"
     target: "https://${GH_TOKEN}@github.com/opentelemetry-php/context-swoole.git"
   - prefix: "src/AutoInstrumentationInstaller"
diff --git a/src/Instrumentation/Doctrine/.gitattributes b/src/Instrumentation/Doctrine/.gitattributes
new file mode 100644
index 000000000..1676cf825
--- /dev/null
+++ b/src/Instrumentation/Doctrine/.gitattributes
@@ -0,0 +1,12 @@
+* text=auto
+
+*.md diff=markdown
+*.php diff=php
+
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/.php-cs-fixer.php export-ignore
+/phpstan.neon.dist export-ignore
+/phpunit.xml.dist export-ignore
+/psalm.xml.dist export-ignore
+/tests export-ignore
diff --git a/src/Instrumentation/Doctrine/.php-cs-fixer.php b/src/Instrumentation/Doctrine/.php-cs-fixer.php
new file mode 100644
index 000000000..bbfa04e61
--- /dev/null
+++ b/src/Instrumentation/Doctrine/.php-cs-fixer.php
@@ -0,0 +1,43 @@
+<?php
+
+$finder = PhpCsFixer\Finder::create()
+    ->exclude('vendor')
+    ->exclude('var/cache')
+    ->in(__DIR__);
+
+$config = new PhpCsFixer\Config();
+return $config->setRules([
+    'concat_space' => ['spacing' => 'one'],
+    'declare_equal_normalize' => ['space' => 'none'],
+    'is_null' => true,
+    'modernize_types_casting' => true,
+    'ordered_imports' => true,
+    'php_unit_construct' => true,
+    'single_line_comment_style' => true,
+    'yoda_style' => false,
+    '@PSR2' => true,
+    'array_syntax' => ['syntax' => 'short'],
+    'blank_line_after_opening_tag' => true,
+    'blank_line_before_statement' => true,
+    'cast_spaces' => true,
+    'declare_strict_types' => true,
+    'type_declaration_spaces' => true,
+    'include' => true,
+    'lowercase_cast' => true,
+    'new_with_parentheses' => true,
+    'no_extra_blank_lines' => true,
+    'no_leading_import_slash' => true,
+    'echo_tag_syntax' => true,
+    'no_unused_imports' => true,
+    'no_useless_else' => true,
+    'no_useless_return' => true,
+    'phpdoc_order' => true,
+    'phpdoc_scalar' => true,
+    'phpdoc_types' => true,
+    'short_scalar_cast' => true,
+    'blank_lines_before_namespace' => true,
+    'single_quote' => true,
+    'trailing_comma_in_multiline' => true,
+    ])
+    ->setRiskyAllowed(true)
+    ->setFinder($finder);
diff --git a/src/Instrumentation/Doctrine/README.md b/src/Instrumentation/Doctrine/README.md
new file mode 100644
index 000000000..6587063ee
--- /dev/null
+++ b/src/Instrumentation/Doctrine/README.md
@@ -0,0 +1,25 @@
+[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/contrib-auto-doctrine/releases)
+[![Issues](https://img.shields.io/badge/issues-pink)](https://github.com/open-telemetry/opentelemetry-php/issues)
+[![Source](https://img.shields.io/badge/source-contrib-green)](https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/src/Instrumentation/Doctrine)
+[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php--contrib-blue)](https://github.com/opentelemetry-php/contrib-auto-doctrine)
+[![Latest Version](http://poser.pugx.org/open-telemetry/opentelemetry-auto-doctrine/v/unstable)](https://packagist.org/packages/open-telemetry/opentelemetry-auto-doctrine/)
+[![Stable](http://poser.pugx.org/open-telemetry/opentelemetry-auto-doctrine/v/stable)](https://packagist.org/packages/open-telemetry/opentelemetry-auto-doctrine/)
+
+This is a read-only subtree split of https://github.com/open-telemetry/opentelemetry-php-contrib.
+
+# OpenTelemetry Doctrine auto-instrumentation
+
+Please read https://opentelemetry.io/docs/instrumentation/php/automatic/ for instructions on how to
+install and configure the extension and SDK.
+
+## Overview
+Auto-instrumentation hooks are registered via composer, and spans will automatically be created for
+selected `Doctrine\DBAL\Driver` and `Doctrine\DBAL\Driver\Connection` methods.
+
+## Configuration
+
+The extension can be disabled via [runtime configuration](https://opentelemetry.io/docs/instrumentation/php/sdk/#configuration):
+
+```shell
+OTEL_PHP_DISABLED_INSTRUMENTATIONS=doctrine
+```
diff --git a/src/Instrumentation/Doctrine/_register.php b/src/Instrumentation/Doctrine/_register.php
new file mode 100644
index 000000000..5baecbbeb
--- /dev/null
+++ b/src/Instrumentation/Doctrine/_register.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+use OpenTelemetry\Contrib\Instrumentation\Doctrine\DoctrineInstrumentation;
+use OpenTelemetry\SDK\Sdk;
+
+if (class_exists(Sdk::class) && Sdk::isInstrumentationDisabled(DoctrineInstrumentation::NAME) === true) {
+    return;
+}
+
+if (extension_loaded('opentelemetry') === false) {
+    trigger_error('The opentelemetry extension must be loaded in order to autoload the OpenTelemetry Doctrine auto-instrumentation', E_USER_WARNING);
+
+    return;
+}
+
+DoctrineInstrumentation::register();
diff --git a/src/Instrumentation/Doctrine/composer.json b/src/Instrumentation/Doctrine/composer.json
new file mode 100644
index 000000000..044a07f62
--- /dev/null
+++ b/src/Instrumentation/Doctrine/composer.json
@@ -0,0 +1,55 @@
+{
+    "name": "open-telemetry/opentelemetry-auto-doctrine",
+    "description": "OpenTelemetry auto-instrumentation for Doctrine",
+    "keywords": [
+        "opentelemetry",
+        "otel",
+        "open-telemetry",
+        "tracing",
+        "doctrine",
+        "instrumentation"
+    ],
+    "type": "library",
+    "homepage": "https://opentelemetry.io/docs/php",
+    "readme": "./README.md",
+    "license": "Apache-2.0",
+    "minimum-stability": "dev",
+    "prefer-stable": true,
+    "require": {
+        "php": "^8.2",
+        "ext-opentelemetry": "*",
+        "doctrine/dbal": "^3 || ^4 || ^5",
+        "open-telemetry/api": "^1.0",
+        "open-telemetry/sem-conv": "^1.24"
+    },
+    "require-dev": {
+        "friendsofphp/php-cs-fixer": "^3",
+        "phan/phan": "^5.0",
+        "php-http/mock-client": "*",
+        "phpstan/phpstan": "^1.1",
+        "phpstan/phpstan-phpunit": "^1.0",
+        "psalm/plugin-phpunit": "^0.18.4",
+        "open-telemetry/sdk": "^1.0",
+        "phpunit/phpunit": "^9.5",
+        "vimeo/psalm": "^5.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "OpenTelemetry\\Contrib\\Instrumentation\\Doctrine\\": "src/"
+        },
+        "files": [
+            "_register.php"
+        ]
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "OpenTelemetry\\Tests\\Instrumentation\\Doctrine\\": "tests/"
+        }
+    },
+    "config": {
+        "allow-plugins": {
+            "php-http/discovery": false,
+            "tbachert/spi": true
+        }
+    }
+}
diff --git a/src/Instrumentation/Doctrine/phpstan.neon.dist b/src/Instrumentation/Doctrine/phpstan.neon.dist
new file mode 100644
index 000000000..ed94c13da
--- /dev/null
+++ b/src/Instrumentation/Doctrine/phpstan.neon.dist
@@ -0,0 +1,9 @@
+includes:
+    - vendor/phpstan/phpstan-phpunit/extension.neon
+
+parameters:
+    tmpDir: var/cache/phpstan
+    level: 5
+    paths:
+        - src
+        - tests
diff --git a/src/Instrumentation/Doctrine/phpunit.xml.dist b/src/Instrumentation/Doctrine/phpunit.xml.dist
new file mode 100644
index 000000000..8eb127383
--- /dev/null
+++ b/src/Instrumentation/Doctrine/phpunit.xml.dist
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
+    backupGlobals="false"
+    backupStaticAttributes="false"
+    cacheResult="false"
+    colors="false"
+    convertErrorsToExceptions="true"
+    convertNoticesToExceptions="true"
+    convertWarningsToExceptions="true"
+    forceCoversAnnotation="false"
+    processIsolation="false"
+    stopOnError="false"
+    stopOnFailure="false"
+    stopOnIncomplete="false"
+    stopOnSkipped="false"
+    stopOnRisky="false"
+    timeoutForSmallTests="1"
+    timeoutForMediumTests="10"
+    timeoutForLargeTests="60"
+    verbose="true">
+
+    <coverage processUncoveredFiles="true" disableCodeCoverageIgnore="false">
+        <include>
+            <directory>src</directory>
+        </include>
+    </coverage>
+
+    <php>
+        <ini name="date.timezone" value="UTC" />
+        <ini name="display_errors" value="On" />
+        <ini name="display_startup_errors" value="On" />
+        <ini name="error_reporting" value="E_ALL" />
+    </php>
+
+    <testsuites>
+        <testsuite name="integration">
+            <directory>tests/Integration</directory>
+        </testsuite>
+    </testsuites>
+
+</phpunit>
diff --git a/src/Instrumentation/Doctrine/psalm.xml.dist b/src/Instrumentation/Doctrine/psalm.xml.dist
new file mode 100644
index 000000000..5a04b34d7
--- /dev/null
+++ b/src/Instrumentation/Doctrine/psalm.xml.dist
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<psalm
+    errorLevel="3"
+    cacheDirectory="var/cache/psalm"
+    findUnusedBaselineEntry="true"
+    findUnusedCode="false"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="https://getpsalm.org/schema/config"
+    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd">
+    <projectFiles>
+        <directory name="src"/>
+        <directory name="tests"/>
+    </projectFiles>
+    <plugins>
+        <pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
+    </plugins>
+</psalm>
diff --git a/src/Instrumentation/Doctrine/src/AttributesResolver.php b/src/Instrumentation/Doctrine/src/AttributesResolver.php
new file mode 100644
index 000000000..b3731d2be
--- /dev/null
+++ b/src/Instrumentation/Doctrine/src/AttributesResolver.php
@@ -0,0 +1,149 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use Exception;
+
+final class AttributesResolver
+{
+    /**
+     *
+     * Values:
+     * See list of well-known values at https://opentelemetry.io/docs/specs/semconv/database/sql/
+     *
+     * Keys:
+     * optional driver names used in Doctrine to indicate the well-known values to use in opentelemetry
+     * See list of available doctrine drivers: https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/configuration.html#driver
+     *
+     * Multiple entries can be created for the same well-known value
+     */
+    private const DB_SYSTEMS_KNOWN = [
+        'cockroachdb',
+        'ibm_db2' => 'db2',
+        'derby',
+        'edb',
+        'firebird',
+        'h2',
+        'hsqldb',
+        'ingres',
+        'interbase',
+        'mariadb',
+        'maxdb',
+        'sqlsrv' => 'mssql',
+        'mssqlcompact',
+        'mysqli' => 'mysql',
+        'oci8' => 'oracle',
+        'pervasive',
+        'pgsql' => 'postgresql',
+        'sqlite3' => 'sqlite',
+        'trino',
+    ];
+
+    public static function get(string $attributeName, array $params): string
+    {
+        $method = 'get' . str_replace('.', '', ucwords($attributeName, '.'));
+
+        if (!method_exists(AttributesResolver::class, $method)) {
+            throw new Exception(sprintf('Attribute %s not supported by Doctrine', $attributeName));
+        }
+
+        return self::{$method}($params);
+    }
+
+    /**
+     * Resolve attribute `server.address`
+     */
+    private static function getServerAddress(array $params): string
+    {
+        return $params[1][0]['host'] ?? 'unknown';
+    }
+
+    /**
+     * Resolve attribute `server.port`
+     */
+    private static function getServerPort(array $params): string
+    {
+        return $params[1][0]['port'] ?? 'unknown';
+    }
+
+    /**
+     * Resolve attribute `db.system`
+     */
+    private static function getDbSystem(array $params)
+    {
+        $dbSystem = $params[1][0]['driver'] ?? null;
+
+        if ($dbSystem && strpos($dbSystem, 'pdo_') !== false) {
+            // Remove pdo_ word to ignore it while searching well-known db.system
+            $dbSystem = ltrim($dbSystem, 'pdo_');
+        }
+
+        if (in_array($dbSystem, self::DB_SYSTEMS_KNOWN)) {
+            return $dbSystem;
+        }
+
+        // Fetch the db system using the alias if exists
+        if (isset(self::DB_SYSTEMS_KNOWN[$dbSystem])) {
+            return self::DB_SYSTEMS_KNOWN[$dbSystem];
+        }
+
+        return 'other_sql';
+    }
+
+    /**
+     * Resolve attribute `db.collection.name`
+     */
+    private static function getDbCollectionName(array $params): string
+    {
+        return $params[1][0]['dbname'] ?? 'unknown';
+    }
+
+    /**
+     * Resolve attribute `db.query.text`
+     * No sanitization is implemented because implicitly the query is expected to be expressed as a preparated statement
+     * which happen automatically in Doctrine if parameters are bound to the query.
+     */
+    private static function getDbQueryText(array $params): string
+    {
+        return $params[1][0] ?? 'undefined';
+    }
+
+    private static function getDbNamespace(array $params): string
+    {
+        return $params[1][0]['dbname'] ?? 'unknown';
+    }
+
+    /**
+     * Resolve attribute `db.query.summary`
+     * See https://opentelemetry.io/docs/specs/semconv/database/database-spans/#generating-a-summary-of-the-query-text
+     */
+    public static function getDbQuerySummary(array $params): string
+    {
+        $query = $params[0] ?? null;
+
+        if (!$query) {
+            return '';
+        }
+
+        // Fetch operation name
+        $operationName = explode(' ', $query);
+        $operationName = $operationName[0];
+
+        // Fetch target name
+        $matches = [];
+        preg_match_all('/( from| into| update| join)\s*([a-zA-Z0-9`"[\]_]+)/i', $query, $matches);
+
+        $targetName = null;
+        if (strtolower($operationName) == 'select') {
+            if ($matches[2]) {
+                $targetName = implode(' ', $matches[2]);
+            }
+        } elseif ($matches) {
+            $targetName = $matches[2][0] ?? '';
+        }
+
+        return $operationName . ($targetName ? ' ' . $targetName : '');
+    }
+}
diff --git a/src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php b/src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
new file mode 100644
index 000000000..5534c6100
--- /dev/null
+++ b/src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
@@ -0,0 +1,181 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                ->setSpanKind(SpanKind::KIND_CLIENT)
+                ->setAttribute(TraceAttributes::SERVER_ADDRESS, AttributesResolver::get(TraceAttributes::SERVER_ADDRESS, func_get_args()))
+                ->setAttribute(TraceAttributes::SERVER_PORT, AttributesResolver::get(TraceAttributes::SERVER_PORT, func_get_args()))
+                ->setAttribute(TraceAttributes::DB_SYSTEM, AttributesResolver::get(TraceAttributes::DB_SYSTEM, func_get_args()))
+                ->setAttribute(TraceAttributes::DB_NAMESPACE, AttributesResolver::get(TraceAttributes::DB_NAMESPACE, func_get_args()));
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, AttributesResolver::getDbQuerySummary($params), $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, AttributesResolver::get(TraceAttributes::DB_QUERY_TEXT, func_get_args()));
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'exec',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, AttributesResolver::getDbQuerySummary($params), $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT)
+                    ->setAttribute(TraceAttributes::DB_QUERY_TEXT, AttributesResolver::get(TraceAttributes::DB_QUERY_TEXT, func_get_args()));
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'prepare',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, AttributesResolver::getDbQuerySummary($params), $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT)
+                    ->setAttribute(TraceAttributes::DB_QUERY_TEXT, AttributesResolver::get(TraceAttributes::DB_QUERY_TEXT, func_get_args()));
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'beginTransaction',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::beginTransaction', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'commit',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::commit', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'rollBack',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::rollBack', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+    }
+    private static function makeBuilder(
+        CachedInstrumentation $instrumentation,
+        string $name,
+        string $function,
+        string $class,
+        ?string $filename,
+        ?int $lineno
+    ): SpanBuilderInterface {
+        /** @psalm-suppress ArgumentTypeCoercion */
+        return $instrumentation->tracer()
+                    ->spanBuilder($name)
+                    ->setAttribute(TraceAttributes::CODE_FUNCTION, $function)
+                    ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
+                    ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
+                    ->setAttribute(TraceAttributes::CODE_LINENO, $lineno);
+    }
+    private static function end(?Throwable $exception): void
+    {
+        $scope = Context::storage()->scope();
+        if (!$scope) {
+            return;
+        }
+        $scope->detach();
+        $span = Span::fromContext($scope->context());
+        if ($exception) {
+            $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
+            $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
+        }
+
+        $span->end();
+    }
+}
diff --git a/src/Instrumentation/Doctrine/tests/Integration/DoctrineInstrumentationTest.php b/src/Instrumentation/Doctrine/tests/Integration/DoctrineInstrumentationTest.php
new file mode 100644
index 000000000..36219490c
--- /dev/null
+++ b/src/Instrumentation/Doctrine/tests/Integration/DoctrineInstrumentationTest.php
@@ -0,0 +1,150 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Tests\Instrumentation\Doctrine\Integration;
+
+use ArrayObject;
+use Doctrine\DBAL\DriverManager;
+use OpenTelemetry\API\Instrumentation\Configurator;
+use OpenTelemetry\Context\ScopeInterface;
+use OpenTelemetry\SDK\Trace\ImmutableSpan;
+use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
+use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
+use OpenTelemetry\SDK\Trace\TracerProvider;
+use OpenTelemetry\SemConv\TraceAttributes;
+use PHPUnit\Framework\TestCase;
+
+class DoctrineInstrumentationTest extends TestCase
+{
+    private ScopeInterface $scope;
+    /** @var ArrayObject<int, ImmutableSpan> */
+    private ArrayObject $storage;
+
+    private function createConnection(): \Doctrine\DBAL\Connection
+    {
+        $connectionParams = [
+            'driver' => 'sqlite3',
+            'memory' => true,
+        ];
+
+        $conn = DriverManager::getConnection($connectionParams);
+        // Trigger internal connect
+        $conn->getServerVersion();
+
+        return $conn;
+    }
+
+    private function fillDB(): string
+    {
+        return <<<SQL
+        CREATE TABLE `technology` (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            name VARCHAR(25) NOT NULL,
+            date DATE NOT NULL
+        );
+
+        INSERT INTO technology(`name`, `date`)
+        VALUES
+            ('PHP', '1993-04-05'),
+            ('CPP', '1979-05-06');
+
+        SQL;
+    }
+
+    public function setUp(): void
+    {
+        $this->storage = new ArrayObject();
+        $tracerProvider = new TracerProvider(
+            new SimpleSpanProcessor(
+                new InMemoryExporter($this->storage)
+            )
+        );
+
+        $this->scope = Configurator::create()
+            ->withTracerProvider($tracerProvider)
+            ->activate();
+    }
+
+    public function tearDown(): void
+    {
+        $this->scope->detach();
+    }
+
+    public function test_connection(): void
+    {
+        $this->assertCount(0, $this->storage);
+        $conn = self::createConnection();
+        $this->assertCount(1, $this->storage);
+        $this->assertTrue($conn->isConnected());
+        $span = $this->storage->offsetGet(0);
+        $this->assertSame('Doctrine\DBAL\Driver::connect', $span->getName());
+        $this->assertEquals('sqlite', $span->getAttributes()->get(TraceAttributes::DB_SYSTEM));
+    }
+
+    public function test_connection_exception(): void
+    {
+        $this->expectException(\Doctrine\DBAL\Exception::class);
+        $this->expectExceptionMessageMatches('/The given driver "unknown" is unknown/');
+        /**
+         * @psalm-suppress InvalidArgument
+         * @phpstan-ignore argument.type
+        */
+        DriverManager::getConnection([
+            'driver' => 'unknown',
+        ]);
+    }
+
+    public function test_statement_execution(): void
+    {
+        $connection =  self::createConnection();
+        $statement = self::fillDB();
+
+        $connection->executeStatement($statement);
+        $span = $this->storage->offsetGet(1);
+        $this->assertSame('CREATE technology', $span->getName());
+        $this->assertFalse($connection->isTransactionActive());
+        $this->assertCount(2, $this->storage);
+
+        $connection->prepare('SELECT * FROM `technology`');
+        $span = $this->storage->offsetGet(2);
+        $this->assertSame('SELECT `technology`', $span->getName());
+        $this->assertCount(3, $this->storage);
+
+        $connection->executeQuery('SELECT * FROM `technology`');
+        $span = $this->storage->offsetGet(3);
+        $this->assertSame('SELECT `technology`', $span->getName());
+        $this->assertCount(4, $this->storage);
+    }
+
+    public function test_transaction(): void
+    {
+        $connection =  self::createConnection();
+        $connection->beginTransaction();
+        $span = $this->storage->offsetGet(1);
+        $this->assertSame('Doctrine\DBAL\Driver\Connection::beginTransaction', $span->getName());
+        $this->assertCount(2, $this->storage);
+
+        $statement = self::fillDB();
+        $connection->executeStatement($statement);
+        $span = $this->storage->offsetGet(2);
+        $this->assertSame('CREATE technology', $span->getName());
+        $connection->commit();
+        $span = $this->storage->offsetGet(3);
+        $this->assertSame('Doctrine\DBAL\Driver\Connection::commit', $span->getName());
+        $this->assertCount(4, $this->storage);
+
+        $connection->beginTransaction();
+        $this->assertTrue($connection->isTransactionActive());
+
+        $connection->executeStatement("INSERT INTO technology(`name`, `date`) VALUES('Java', '1995-05-23');");
+        $connection->rollback();
+        $span = $this->storage->offsetGet(6);
+        $this->assertSame('Doctrine\DBAL\Driver\Connection::rollBack', $span->getName());
+        $this->assertCount(7, $this->storage);
+        $this->assertFalse($connection->isTransactionActive());
+
+        $sth = $connection->prepare('SELECT * FROM `technology`');
+        $this->assertSame(2, count($sth->executeQuery()->fetchAllAssociative()));
+    }
+}


Reviews:
user: login: Nevay
body: Hooking driver methods will cause duplicated spans if a middleware like [Symfony Debug Middleware](https://github.com/symfony/doctrine-bridge/tree/3e223cc6869b8677bd74ba508eca77a2e87b9e4d/Middleware/Debug) is used.
An alternative approach to avoid this problem would be to move the implementation into a middleware and use a hook to inject this middleware into every connection; see also [Nevay/otel-instrumentation-doctrine-dbal](https://github.com/Nevay/otel-instrumentation-doctrine-dbal/blob/main/src/DoctrineInstrumentation.php).
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-04T16:25:24Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-07T13:36:33Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-07T13:43:42Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-07T13:46:42Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-07T14:14:34Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-07T14:15:58Z
user: login: ostrolucky
state: COMMENTED
author_association: NONE
submitted_at: 2024-10-11T11:22:02Z
user: login: brettmc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-13T10:30:56Z
user: login: cedricziel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T06:21:59Z
user: login: cedricziel
body: My proposal is to figure out if this instrumentation should depend on the existence of PDO and then define how the two interact. My understanding is that _if_ a PDO is used and the PDO instrumentation is active, there will be 2 nested CLIENT spans for the same interaction. - IMHO this is severely impacting the user experience.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T06:32:45Z
user: login: brettmc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T06:59:07Z
user: login: cedricziel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T07:17:31Z
user: login: cedricziel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T07:18:33Z
user: login: ostrolucky
state: COMMENTED
author_association: NONE
submitted_at: 2024-10-16T07:26:20Z
user: login: brettmc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T11:05:53Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T11:25:30Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T11:31:15Z
user: login: dkarlovi
state: COMMENTED
author_association: NONE
submitted_at: 2024-10-16T11:34:21Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T12:01:39Z
user: login: Nevay
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-16T15:06:46Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-17T08:12:07Z
user: login: dkarlovi
state: COMMENTED
author_association: NONE
submitted_at: 2024-10-17T08:21:00Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-17T08:25:55Z
user: login: dkarlovi
state: COMMENTED
author_association: NONE
submitted_at: 2024-10-23T10:34:13Z
user: login: DominicDetta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-10-23T15:49:00Z
user: login: fabianx-ai
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-22T13:12:51Z
user: login: ChrisLightfootWild
body: Just a couple of tidy-ups from me 👍 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T10:20:23Z
user: login: brettmc
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T02:13:39Z

Review comments:
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, \Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                $scope = Context::storage()->scope();
+                if (!$scope) {
+                    return;
+                }
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'exec',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::exec', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'prepare',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::prepare', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'beginTransaction',
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: Nevay
body: Depending on the driver this might trigger multiple spans for a single operation, e.g. PgSQL will lead to an additional span due to calling [`exec('BEGIN')`](https://github.com/doctrine/dbal/blob/7a8252418689feb860ea8dfeab66d64a56a64df8/src/Driver/PgSQL/Connection.php#L107) in `beginTransaction`.
created_at: 2024-10-04T15:11:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: Nevay
body: IMO we should try to follow semconv even if we don't specify a schema url; [see semconv](https://opentelemetry.io/docs/specs/semconv/attributes-registry/db/):
> db.system has the following list of well-known values. If one of them applies, then the respective value MUST be used
created_at: 2024-10-04T15:42:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: Nevay
body: ```suggestion
                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? null)
                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? null)
                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'other_sql')
                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? null);
```
created_at: 2024-10-04T15:45:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, \Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: Nevay
body: ```suggestion
            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
```

Connection won't be available if `Driver::connect()` throws an exception.
created_at: 2024-10-04T15:46:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, \Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                $scope = Context::storage()->scope();
+                if (!$scope) {
+                    return;
+                }
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: Nevay
body: ```suggestion
```
created_at: 2024-10-04T15:48:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, \Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                $scope = Context::storage()->scope();
+                if (!$scope) {
+                    return;
+                }
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: Nevay
body: ```suggestion
```

> SHOULD be collected by default only if there is sanitization that excludes sensitive information.

Same applies to `::exec()`.
created_at: 2024-10-04T15:51:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, \Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                $scope = Context::storage()->scope();
+                if (!$scope) {
+                    return;
+                }
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'exec',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::exec', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'prepare',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::prepare', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: Nevay
body: ```suggestion
                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? null);
```
created_at: 2024-10-04T15:51:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, \Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                $scope = Context::storage()->scope();
+                if (!$scope) {
+                    return;
+                }
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'exec',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::exec', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'prepare',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::prepare', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'beginTransaction',
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: Do you have an idea how to solve it because sincerely I dont know how to approach it.  When I looked up your repository I dont see any changes. Anyway I dont think it's a problem to be worry about.
created_at: 2024-10-07T13:36:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: Ok, I didnt know there was a convention. I will apply the correct ones.
created_at: 2024-10-07T13:43:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: I prefer to use the default value already used by the [official pdo](https://github.com/opentelemetry-php/contrib-auto-pdo/blob/main/src/PDOInstrumentation.php) at the line 42
created_at: 2024-10-07T13:46:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, \Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                $scope = Context::storage()->scope();
+                if (!$scope) {
+                    return;
+                }
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: I would not use this approach for these reasons: 
- the official pdo repository does not do any sanitize
- the https://github.com/phpmyadmin/sql-parser library you propose focuses on Mysql syntax
- The target applications should use the prepared statements and therefore the problem should not arise
- The convention indicates that it is a SHOULD rule
- I prefer not to manipulate original sql
created_at: 2024-10-07T14:14:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, \Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                $scope = Context::storage()->scope();
+                if (!$scope) {
+                    return;
+                }
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'exec',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::exec', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, mixed $statement, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'prepare',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::prepare', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined');
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: I prefer to use the default value already used by the [official pdo](https://github.com/opentelemetry-php/contrib-auto-pdo/blob/main/src/PDOInstrumentation.php) at the line 96
created_at: 2024-10-07T14:15:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+use OpenTelemetry\Contrib\Instrumentation\Doctrine\DoctrineInstrumentation;
+use OpenTelemetry\SDK\Sdk;
+
+if (class_exists(Sdk::class) && Sdk::isInstrumentationDisabled(DoctrineInstrumentation::NAME) === true) {
+    return;
+}
+
+if (extension_loaded('opentelemetry') === false) {
path: src/Instrumentation/Doctrine/_register.php
user: login: ostrolucky
body: Is this necessary, considering you have ext-opentelemetry as a requirement in composer.json? If you want to allow project to still function without having the extension loaded, you should then also make it as optional dependency in composer.json.
created_at: 2024-10-11T11:18:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: ostrolucky
body: You should call this DBAL instrumentation. Possibly whole package as well. Have you thought about Doctrine ORM? Is that going to be a separate package, or part of this package? If the former, this package should be renamed. If the latter, code should be structured in a way to be ready for future extension to cover doctrine ORM. I'm pretty sure it will be important because object hydration is often the bottleneck.
created_at: 2024-10-11T11:21:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+use OpenTelemetry\Contrib\Instrumentation\Doctrine\DoctrineInstrumentation;
+use OpenTelemetry\SDK\Sdk;
+
+if (class_exists(Sdk::class) && Sdk::isInstrumentationDisabled(DoctrineInstrumentation::NAME) === true) {
+    return;
+}
+
+if (extension_loaded('opentelemetry') === false) {
path: src/Instrumentation/Doctrine/_register.php
user: login: brettmc
body: We've got this test in all auto-instrumentations, because folks will often install with composer's `--ignore-platform-reqs` (reported usages have been for CI pipelines, or a multi-developer project where not all developers care about auto-instrumentation/telemetry). We're open to revisiting this, but AFAIR those are the edge cases to consider.
created_at: 2024-10-13T10:30:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: cedricziel
body: Well I think it's fine to have this umbrella-instrumentation. I'd be happy to add some more ORM instrumentation later, but I think it makes sense to stick with this naming as ORM depends on DBAL
created_at: 2024-10-16T06:21:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: cedricziel
body: How do we think about the duality of PDO and dbal here? Most instrumented methods have a pendant in the PDO instrumentation i.e. - a compatible pdo driver will cause two client spans and the hierarchy will probably be

```
my_func (internal)
 -> connect (client) # from dbal
   -> PDO::__construct (client ) # from pdo
```

IMO the two should be either mutually exclusive or enrich each other. WDYT?
created_at: 2024-10-16T06:26:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: cedricziel
body: The recommended span name here would be something like `SELECT my_table`.

The duplication with the pdo instrumentation is the same here.

https://opentelemetry.io/docs/specs/semconv/database/database-spans/#name
created_at: 2024-10-16T06:29:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: brettmc
body: We do not have a way for two auto-instrumentations to cooperate like this (it's an idea that has come up before, and would be cool). So I think mutually exclusive is the best we have - or at least it's up to the user to install only one instrumentation, I don't think we need to go as far as setting them up as conflicting in composer.
Is documenting that recommendation good enough for now?

edit: a hacky approach would be to have this package check if PDO instrumentation is installed and enabled, and only apply some of its hooks (if it provides any value that PDO itself doesn't)...?
created_at: 2024-10-16T06:59:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: cedricziel
body: Could the PDO instrumentation re-write the doctrine span from CLIENT to internal or bail if it recognizes the parent is already client?
created_at: 2024-10-16T07:17:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: cedricziel
body: To your point - i think documenting it makes the experience worse because users then need to make a whole lot of decisions when instrumenting
created_at: 2024-10-16T07:18:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: ostrolucky
body: Ok. Just a note though: doctrine/orm depends on doctrine/dbal, but not doctrine/mongodb-odm
created_at: 2024-10-16T07:26:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: brettmc
body: > i think documenting it makes the experience worse

I only meant documenting such as "use pdo or doctrine auto-instrumentation, but probably not both" - I expect users to make decisions about which auto-instrumentations to install based on their workload/stack...just installing everything available is probably too noisy.

> Could the PDO instrumentation re-write the doctrine span from CLIENT to internal or bail if it recognizes the parent is already client?

I think the issue here would be that pre/post hooks operate in isolation, so there's currently no way for a post hook to know whether the pre hook created a span or just modified the active span. All the implementations we have assume that `pre` created and activated a span, and `post` just closes whichever is the active span at the time.
We have previously brainstormed whether we could manage some state between a pre and post hook, but we thought it was going to be a hard problem.
created_at: 2024-10-16T11:05:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: > You should call this DBAL instrumentation. Possibly whole package as well. Have you thought about Doctrine ORM? Is that going to be a separate package, or part of this package? If the former, this package should be renamed. If the latter, code should be structured in a way to be ready for future extension to cover doctrine ORM. I'm pretty sure it will be important because object hydration is often the bottleneck.

Right. I didn't think at the possibility to monitor Doctrine ORM Hydration process., only the database invocations.




> Well I think it's fine to have this umbrella-instrumentation. I'd be happy to add some more ORM instrumentation later, but I think it makes sense to stick with this naming as ORM depends on DBAL

I really like that idea. I will maintain the current nomenclature
created_at: 2024-10-16T11:25:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: Ok. Do you know a good agnostic SQL parser to achieve this? The [one I found ](https://github.com/phpmyadmin/sql-parser) is focusing on Mysql syntax.
created_at: 2024-10-16T11:31:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: dkarlovi
body: Doctrine already knows the table name(s).
created_at: 2024-10-16T11:34:21Z
author_association: NONE
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: > I expect users to make decisions about which auto-instrumentations to install based on their workload/stack...just installing everything available is probably too noisy.

I completely agree on this matter.


created_at: 2024-10-16T12:01:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: Nevay
body: > Could the PDO instrumentation ... bail if it recognizes the parent is already client?

Bailing out requires storing additional data within the `Context` to be able to detect duplicate instrumentations, see Javas span suppression strategies:
[Instrumentation span suppression behavior](https://opentelemetry.io/docs/zero-code/java/agent/disable/#instrumentation-span-suppression-behavior)
[SpanSuppressionStrategy.java](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/SpanSuppressionStrategy.java)
[SpanSuppressors.java](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/SpanSuppressors.java)

> AFAIK the idea is to allow the agent to do that

Dropping at the agent is too late as it may lead to broken traces "Dropping a span may lead to orphaned spans if the dropped span is a parent." [ref](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/filterprocessor/README.md#warnings).

> there's currently no way for a post hook to know whether the pre hook created a span or just modified the active span

Scopes attached via `Context::storage()->attach()` implement `ArrayAccess` to allow propagating state from `pre` hook to `post` hook [ref](https://github.com/open-telemetry/opentelemetry-php/pull/675).

```php
hook(
    null,
    'example',
    static function() use ($tracer): void {
        $context = Context::getCurrent();
        if (lcg_value() > .5 /* some suppression condition */) {
            $span = $tracer
                ->spanBuilder('example')
                ->startSpan();
            $context = $span->storeInContext($context);
        }
        $scope = Context::storage()->attach($context);
        $scope[SpanInterface::class] = $span ?? null;
    },
    static function(): void {
        if (!$scope = Context::storage()->scope()) {
            return;
        }
        $scope->detach();
        /** @var SpanInterface|null $span */
        $span = $scope[SpanInterface::class] ?? null;
        $span?->end();
    },
);
```

created_at: 2024-10-16T15:06:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: > Doctrine already knows the table name(s).

With the approach I'm following right now, I dont have this information. Are you referring to Doctrine ORM? I'm creating hooks on the methods of `\Doctrine\DBAL\Driver` and `\Doctrine\DBAL\Driver\Connection`
created_at: 2024-10-17T08:12:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: dkarlovi
body: I see, that's a good point.

The issue with this approach is whichever parser you choose is bound to be quite slow and will probably be slowest part of the call (which includes the actual query), making it unusable for production use. ORM itself has a DQL query parser (which is close enough for high level comparison) and it's basically unusable in prod without it being cached with [ORM query cache](https://www.doctrine-project.org/projects/doctrine-orm/en/3.3/reference/caching.html#query-cache).

Running a random query parser on every DB query is unlikely to be viable.
created_at: 2024-10-17T08:21:00Z
author_association: NONE
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: I think I will use a simple regex expression to identify the `db.operation.name` and `target` from the query text
created_at: 2024-10-17T08:25:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: dkarlovi
body: IMO we could consider the ORM instrumentation even if it's still not here yet.

For example, since ORM will know what the table names are, you could assume they will be put in context by the ORM instrumentation and then only try to detect it if not there? This would future proof this implementation and allow extensions when they happen.
created_at: 2024-10-23T10:34:12Z
author_association: NONE
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
+                $builder
+                    ->setAttribute(TraceAttributes::SERVER_ADDRESS, $params[0]['host'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::SERVER_PORT, $params[0]['port'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_SYSTEM, $params[0]['driver'] ?? 'unknown')
+                    ->setAttribute(TraceAttributes::DB_NAMESPACE, $params[0]['dbname'] ?? 'unknown');
+                $parent = Context::getCurrent();
+                $span = $builder->startSpan();
+                Context::storage()->attach($span->storeInContext($parent));
+            },
+            post: static function (\Doctrine\DBAL\Driver $driver, array $params, ?\Doctrine\DBAL\Driver\Connection $connection, ?Throwable $exception) {
+                self::end($exception);
+            }
+        );
+
+        hook(
+            \Doctrine\DBAL\Driver\Connection::class,
+            'query',
+            pre: static function (\Doctrine\DBAL\Driver\Connection $connection, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver\Connection::query', $function, $class, $filename, $lineno)
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: DominicDetta
body: Sorry for the late response but I'm pretty busy at the moment. 



> IMO we could consider the ORM instrumentation even if it's still not here yet.
> 
> For example, since ORM will know what the table names are, you could assume they will be put in context by the ORM instrumentation and then only try to detect it if not there? This would future proof this implementation and allow extensions when they happen.

I see your point but my focus is to implement hooks for Doctrine DBAL and in the future time permitted we could refactor the functions to behave as you suggested.
created_at: 2024-10-23T15:49:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OpenTelemetry\Contrib\Instrumentation\Doctrine;
+
+use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanBuilderInterface;
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\API\Trace\StatusCode;
+use OpenTelemetry\Context\Context;
+
+use function OpenTelemetry\Instrumentation\hook;
+
+use OpenTelemetry\SemConv\TraceAttributes;
+use Throwable;
+
+class DoctrineInstrumentation
+{
+    public const NAME = 'doctrine';
+
+    public static function register(): void
+    {
+        $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.doctrine');
+
+        hook(
+            \Doctrine\DBAL\Driver::class,
+            'connect',
+            pre: static function (\Doctrine\DBAL\Driver $driver, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
+                /** @psalm-suppress ArgumentTypeCoercion */
+                $builder = self::makeBuilder($instrumentation, 'Doctrine\DBAL\Driver::connect', $function, $class, $filename, $lineno)
+                    ->setSpanKind(SpanKind::KIND_CLIENT);
path: src/Instrumentation/Doctrine/src/DoctrineInstrumentation.php
user: login: fabianx-ai
body: @brettmc Can you post-process the spans and just remove / enrich the duplicated ones?

e.g. Is there something that runs before the exporter?
created_at: 2025-01-22T13:12:51Z
author_association: NONE
diff_hunk: @@ -45,6 +45,7 @@ jobs:
           'Instrumentation/Slim',
           'Instrumentation/Symfony',
           'Instrumentation/Yii',
+          'Instrumentation/Doctrine',
path: .github/workflows/php.yml
user: login: ChrisLightfootWild
body: Please can you move this into alphabetical order?
created_at: 2025-03-08T10:15:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,6 +68,8 @@ jobs:
             php-version: 8.1
           - project: 'Instrumentation/ExtRdKafka'
             php-version: 8.1
+          - project: 'Instrumentation/Doctrine'
+            php-version: 8.1
path: .github/workflows/php.yml
user: login: ChrisLightfootWild
body: As above.
created_at: 2025-03-08T10:16:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -157,4 +160,4 @@ jobs:
         directory: src/${{ matrix.project }}
         files: ./coverage.clover
         flags: ${{ matrix.project }}
-        verbose: false
+        verbose: false
path: .github/workflows/php.yml
user: login: ChrisLightfootWild
body: Revert to newline EOF.
created_at: 2025-03-08T10:16:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: linux-foundation-easycla[bot]
created_at: 2024-10-03T07:05:44Z
body: <a href="https://easycla.lfx.linuxfoundation.org/#/?version=2"><img src="https://s3.amazonaws.com/cla-project-logo-prod/cla-signed.svg" alt="CLA Signed" align="left" height="28" width="328" ></a><br/><br />The committers listed above are authorized under a signed CLA.<ul><li>:white_check_mark: login: DominicDetta / name: domaz  (5feb61d2a92b74c79398a99ebb183f93ff210a1a, fb18a173728969eea12bda60be0c70cdfe455426, 829157c35bc6b03b542ad3685c6118f70102893e, 099b86ac8b4f3504835c5c091e1793cfe42264c3, 32d028b4249213336dace1fe95cf336ec67ce915, 8ce859c4a8e4b0e1b4cedc4e4acfc704bd31c8e9, 2e6c142d7ad231804839124535bffe9740269b22, ff6dd7f4a70ce4555e5c0d825508f71929fe032d, 046e81092b35f646e52763a492858255b567177b, 42e52c46d3c6f319763bb5da6d3e4f1677369958, 011365ef4cc50f353a8bc84d261872ea0d5a7db8, 8229200337e26227949751a42dda7b5deed1686b)</li></ul><!-- Date Modified: 2025-03-10 09:03:35.849639 -->
author_association: NONE
user: login: codecov[bot]
created_at: 2024-10-03T07:40:54Z
body: ## [Codecov](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 80.42%. Comparing base [(`65de07e`)](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/commit/65de07eb6a4ce43e03d69eebe53f8ccbbff3b655?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) to head [(`829157c`)](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/commit/829157c35bc6b03b542ad3685c6118f70102893e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/graphs/tree.svg?width=650&height=150&src=pr&token=CCG4zF1C4T&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)

```diff
@@            Coverage Diff            @@
##               main     #300   +/-   ##
=========================================
  Coverage     80.42%   80.42%           
  Complexity     1502     1502           
=========================================
  Files           128      128           
  Lines          6176     6176           
=========================================
  Hits           4967     4967           
  Misses         1209     1209           
```

| [Flag](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Coverage Δ | |
|---|---|---|
| [Aws](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `85.55% <ø> (ø)` | |
| [Context/Swoole](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `0.00% <ø> (ø)` | |
| [Instrumentation/CakePHP](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `20.27% <ø> (ø)` | |
| [Instrumentation/CodeIgniter](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `73.77% <ø> (ø)` | |
| [Instrumentation/Curl](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `90.42% <ø> (ø)` | |
| [Instrumentation/ExtAmqp](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `89.26% <ø> (ø)` | |
| [Instrumentation/ExtRdKafka](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `86.23% <ø> (ø)` | |
| [Instrumentation/Guzzle](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `69.51% <ø> (ø)` | |
| [Instrumentation/HttpAsyncClient](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `78.31% <ø> (ø)` | |
| [Instrumentation/IO](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `70.68% <ø> (ø)` | |
| [Instrumentation/Laravel](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `62.68% <ø> (ø)` | |
| [Instrumentation/MongoDB](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `74.28% <ø> (ø)` | |
| [Instrumentation/MySqli](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `95.81% <ø> (ø)` | |
| [Instrumentation/OpenAIPHP](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `87.31% <ø> (ø)` | |
| [Instrumentation/PDO](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `90.15% <ø> (ø)` | |
| [Instrumentation/Psr14](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `77.14% <ø> (ø)` | |
| [Instrumentation/Psr15](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `89.41% <ø> (ø)` | |
| [Instrumentation/Psr16](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `97.56% <ø> (ø)` | |
| [Instrumentation/Psr18](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `77.77% <ø> (ø)` | |
| [Instrumentation/Psr3](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `59.49% <ø> (ø)` | |
| [Instrumentation/Psr6](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `97.67% <ø> (ø)` | |
| [Instrumentation/Slim](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `86.30% <ø> (ø)` | |
| [Instrumentation/Symfony](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `84.93% <ø> (ø)` | |
| [Instrumentation/Yii](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `77.68% <ø> (ø)` | |
| [Logs/Monolog](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `100.00% <ø> (ø)` | |
| [Propagation/TraceResponse](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `100.00% <ø> (ø)` | |
| [ResourceDetectors/Azure](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `91.66% <ø> (ø)` | |
| [ResourceDetectors/Container](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `93.02% <ø> (ø)` | |
| [Sampler/RuleBased](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `33.51% <ø> (ø)` | |
| [Shims/OpenTracing](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `92.45% <ø> (ø)` | |
| [Symfony](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | `87.81% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#carryforward-flags-in-the-pull-request-comment) to find out more.

------

[Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300?dropdown=coverage&src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry). Last update [65de07e...829157c](https://app.codecov.io/gh/open-telemetry/opentelemetry-php-contrib/pull/300?dropdown=coverage&src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).

</details>
<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: brettmc
created_at: 2024-10-03T07:42:35Z
body: @DominicDetta one more thing to add is an entry to the top-level `.gitsplit.yaml`. When this is merged, that'll be used to split the package into a read-only repository which we can point packagist at.
You'll also need to add an entry to .github/workflows/php.yml so that the tests will run.
author_association: CONTRIBUTOR
user: login: DominicDetta
created_at: 2024-10-03T08:23:23Z
body: @brettmc I included the Doctrine directory in the files you specified


author_association: CONTRIBUTOR
user: login: DominicDetta
created_at: 2024-10-03T08:41:25Z
body: I agreed the EasyCLA and waiting for the merge approval
author_association: CONTRIBUTOR
user: login: brettmc
created_at: 2024-10-04T02:10:34Z
body: Can you also update workflows/php to exclude 7.4 from the version matrix? The php version requirement is ^8.2, but I think it would work back to 8.0 since it doesn't hook internal functions. Either way, we need to either drop the requirement back to 8.0 or exclude pre-8.2 versions from the test matrix.
author_association: CONTRIBUTOR
user: login: DominicDetta
created_at: 2024-10-04T11:17:51Z
body: it's done
author_association: CONTRIBUTOR
user: login: DominicDetta
created_at: 2024-10-04T12:00:18Z
body: In the end I integrated again the PHP 7.4 version and excluded Doctrine from the pre-8.2 versions tests
author_association: CONTRIBUTOR
user: login: brettmc
created_at: 2024-10-04T13:22:02Z
body: Green build is an excellent start. I'm happy to push this out as a 0.0.1 release. Since this works against doctrine 3 (according to the composer.json), and doesn't hook any internal/extension functions, I think it would probably work in 8.0 and 8.1 as well. But, that's not a blocker to getting this out if you're happy with the PR as it is.
author_association: CONTRIBUTOR
user: login: DominicDetta
created_at: 2024-10-04T13:33:04Z
body: I'm pretty confident it works even with PHP 8.0 version.
I can confirm that the classes `\Doctrine\DBAL\Driver\Connection` and `\Doctrine\DBAL\Driver` exist even in the next major versions of doctrine. (I'm using doctrine 3 at the moment). I'm glad I was able to contribute.
author_association: CONTRIBUTOR
user: login: DominicDetta
created_at: 2024-10-07T14:31:58Z
body: @brettmc @Nevay 
> Hooking driver methods will cause duplicated spans if a middleware like [Symfony Debug Middleware](https://github.com/symfony/doctrine-bridge/tree/3e223cc6869b8677bd74ba508eca77a2e87b9e4d/Middleware/Debug) is used. An alternative approach to avoid this problem would be to move the implementation into a middleware and use a hook to inject this middleware into every connection; see also [Nevay/otel-instrumentation-doctrine-dbal](https://github.com/Nevay/otel-instrumentation-doctrine-dbal/blob/main/src/DoctrineInstrumentation.php).


The duplicate problem is also a common problem with other auto-instrumentations. In fact, we could not use psr-15 as it generated too many spans. I wonder if I really need to fix this problem or is there a way to filter the results.
Does OpenTelemetry have a feature to configure and filter duplicated spans?

author_association: CONTRIBUTOR
user: login: dkarlovi
created_at: 2024-10-09T08:39:57Z
body: > Does OpenTelemetry have a feature to configure and filter duplicated spans?

AFAIK the idea is to allow the agent to do that:
https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/filterprocessor/README.md
author_association: NONE
user: login: brettmc
created_at: 2025-03-03T23:01:00Z
body: @DominicDetta I didn't notice that you'd signed the CLA now. Let's get this merged, can you merge in main and resolve conflicts?
author_association: CONTRIBUTOR
user: login: DominicDetta
created_at: 2025-03-06T09:40:39Z
body: @brettmc I resolved the conflict, I didnt have time to put more effort in this project. I'm too busy at the moment.
author_association: CONTRIBUTOR
user: login: DominicDetta
created_at: 2025-03-07T16:06:40Z
body: @brettmc  Maybe you didnt see but I merged to the main branch
author_association: CONTRIBUTOR
user: login: brettmc
created_at: 2025-03-12T02:18:12Z
body: https://packagist.org/packages/open-telemetry/opentelemetry-auto-doctrine
author_association: CONTRIBUTOR