From aa3464c4e255296a15661cac16cc24852030f51d Mon Sep 17 00:00:00 2001 From: pauldambra Date: Sat, 17 Oct 2026 11:37:49 +0100 Subject: [PATCH 1/2] feat: add optional span context processor Generated-By: PostHog Desktop Task-Id: 5681be44-4986-48db-b140-1bda88f75f96 --- .changeset/shiny-shoes-feel.md | 5 + README.md | 8 + api/public-api.json | 228 +++ composer.json | 3 +- composer.lock | 1700 ++++++++++++++++---- lib/Client.php | 5 +- lib/Internal/SpanContext.php | 67 + lib/OpenTelemetry/ContextSpanProcessor.php | 48 + test/ContextSpanProcessorTest.php | 232 +++ 9 files changed, 1976 insertions(+), 320 deletions(-) create mode 100644 .changeset/shiny-shoes-feel.md create mode 100644 lib/Internal/SpanContext.php create mode 100644 lib/OpenTelemetry/ContextSpanProcessor.php create mode 100644 test/ContextSpanProcessorTest.php diff --git a/.changeset/shiny-shoes-feel.md b/.changeset/shiny-shoes-feel.md new file mode 100644 index 0000000..34b93d9 --- /dev/null +++ b/.changeset/shiny-shoes-feel.md @@ -0,0 +1,5 @@ +--- +"posthog-php": minor +--- + +Add an optional OpenTelemetry context processor for session and person IDs on application spans. diff --git a/README.md b/README.md index 44a19b6..f2e37d0 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ SDK usage examples and code snippets live in the official documentation so they - [Laravel integration docs](https://posthog.com/docs/libraries/laravel) - [Upgrade notes](UPGRADING.md), including GeoIP configuration and compatibility +## OpenTelemetry context + +The optional `PostHog\OpenTelemetry\ContextSpanProcessor` adds session and person IDs to application spans inside this client's request context. Register it on the application's existing OpenTelemetry provider. OpenTelemetry remains an optional application dependency. + +The registered processor also enables request span enrichment when `withContext()` starts. This lookup skips Fibers because uninitialized OpenTelemetry Fiber storage can return another execution path's span. Child spans still use the active Fiber's PostHog context. New Fibers and queue jobs need explicit context propagation. + +Existing span attributes take precedence. The processor preserves the application's provider, sampling, and exporters. See the [PHP tracing guide](https://posthog.com/docs/distributed-tracing/installation/php) and [Laravel guide](https://posthog.com/docs/libraries/laravel) for setup. + ## Questions? ### [Join our Slack community.](https://join.slack.com/t/posthogusers/shared_invite/enQtOTY0MzU5NjAwMDY3LTc2MWQ0OTZlNjhkODk3ZDI3NDVjMDE1YjgxY2I4ZjI4MzJhZmVmNjJkN2NmMGJmMzc2N2U3Yjc3ZjI5NGFlZDQ) diff --git a/api/public-api.json b/api/public-api.json index 63cfd03..86febe6 100644 --- a/api/public-api.json +++ b/api/public-api.json @@ -3396,6 +3396,234 @@ } } }, + "PostHog\\Internal\\SpanContext": { + "type": "class", + "abstract": false, + "final": true, + "readonly": false, + "extends": null, + "implements": [], + "constants": [], + "properties": [], + "methods": { + "enrich": { + "static": true, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + }, + { + "name": "span", + "type": "OpenTelemetry\\API\\Trace\\SpanInterface", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "enrichCurrent": { + "static": true, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "register": { + "static": true, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + }, + { + "name": "processor", + "type": "object", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "unregister": { + "static": true, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + }, + { + "name": "processor", + "type": "object", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + } + } + }, + "PostHog\\OpenTelemetry\\ContextSpanProcessor": { + "type": "class", + "abstract": false, + "final": true, + "readonly": false, + "extends": null, + "implements": [ + "OpenTelemetry\\SDK\\Trace\\SpanProcessorInterface" + ], + "constants": [], + "properties": [], + "methods": { + "__construct": { + "static": false, + "abstract": false, + "final": false, + "returnType": null, + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "forceFlush": { + "static": false, + "abstract": false, + "final": false, + "returnType": "bool", + "parameters": [ + { + "name": "cancellation", + "type": "?OpenTelemetry\\SDK\\Common\\Future\\CancellationInterface", + "byReference": false, + "variadic": false, + "optional": true, + "default": null, + "defaultConstant": null, + "hasDefault": true + } + ] + }, + "onEnd": { + "static": false, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "span", + "type": "OpenTelemetry\\SDK\\Trace\\ReadableSpanInterface", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "onStart": { + "static": false, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "span", + "type": "OpenTelemetry\\SDK\\Trace\\ReadWriteSpanInterface", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + }, + { + "name": "parentContext", + "type": "OpenTelemetry\\Context\\ContextInterface", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "shutdown": { + "static": false, + "abstract": false, + "final": false, + "returnType": "bool", + "parameters": [ + { + "name": "cancellation", + "type": "?OpenTelemetry\\SDK\\Common\\Future\\CancellationInterface", + "byReference": false, + "variadic": false, + "optional": true, + "default": null, + "defaultConstant": null, + "hasDefault": true + } + ] + } + } + }, "PostHog\\PostHog": { "type": "class", "abstract": false, diff --git a/composer.json b/composer.json index aefcb41..da7fe93 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "require-dev": { "phpunit/phpunit": "^11.0", "squizlabs/php_codesniffer": "^4.0", - "ext-zlib": "*" + "ext-zlib": "*", + "open-telemetry/sdk": "^1.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index fcd6c27..0c803bf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "520fd5d31c8da486e608e28c4972f358", + "content-hash": "083abfeb00552b89f931e32a13c0c4ba", "packages": [ { "name": "psr/clock", @@ -383,6 +383,143 @@ } ], "packages-dev": [ + { + "name": "brick/math", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "2effe05d2177c451b86c6a073196a4034c02f211" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/2effe05d2177c451b86c6a073196a4034c02f211", + "reference": "2effe05d2177c451b86c6a073196a4034c02f211", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "phpstan/phpstan": "2.2.13", + "phpstan/phpstan-phpunit": "2.0.18", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/1.0.0" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2026-09-12T10:28:18+00:00" + }, + { + "name": "composer/semver", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "528948210cc46f967a4b98e83b19e76f2b504993" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/528948210cc46f967a4b98e83b19e76f2b504993", + "reference": "528948210cc46f967a4b98e83b19e76f2b504993", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Version comparison library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.5.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-10-05T13:12:12+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.4", @@ -502,314 +639,594 @@ "time": "2025-12-06T11:56:16+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.4", + "name": "nyholm/psr7-server", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" }, "funding": [ { - "url": "https://github.com/theseer", + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", "type": "github" } ], - "time": "2024-03-03T12:33:53+00:00" + "time": "2023-11-08T09:30:43+00:00" }, { - "name": "phar-io/version", - "version": "3.2.1", + "name": "open-telemetry/api", + "version": "1.10.0", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "7c029c4a6fd457094a20569bf98f93d95e9a7559" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7c029c4a6fd457094a20569bf98f93d95e9a7559", + "reference": "7c029c4a6fd457094a20569bf98f93d95e9a7559", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "open-telemetry/context": "^1.4", + "php": "^8.1", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.11" }, "type": "library", + "extra": { + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + }, + "branch-alias": { + "dev-main": "1.8.x-dev" + } + }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" } ], - "description": "Library for handling version information and constraints", + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2022-02-21T01:04:05+00:00" + "time": "2026-07-06T12:28:04+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "11.0.12", + "name": "open-telemetry/context", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", - "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/3c414b246e0dabb7d6145404e6a5e4536ca18d07", + "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^5.7.0", - "php": ">=8.2", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-text-template": "^4.0.1", - "sebastian/code-unit-reverse-lookup": "^4.0.1", - "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.1", - "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.2", - "theseer/tokenizer": "^1.3.1" - }, - "require-dev": { - "phpunit/phpunit": "^11.5.46" + "php": "^8.1", + "symfony/polyfill-php82": "^1.26" }, "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-ffi": "To allow context switching in Fibers" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0.x-dev" + "dev-main": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "Context implementation for OpenTelemetry PHP.", "keywords": [ - "coverage", - "testing", - "xunit" + "Context", + "opentelemetry", + "otel" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", - "type": "tidelift" - } - ], - "time": "2025-12-24T07:01:01+00:00" + "time": "2025-10-19T06:44:33+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "5.1.1", + "name": "open-telemetry/sdk", + "version": "1.15.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + "url": "https://github.com/opentelemetry-php/sdk.git", + "reference": "77e1aa73850154abb86937d52a70883edc3b4547" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", - "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/77e1aa73850154abb86937d52a70883edc3b4547", + "reference": "77e1aa73850154abb86937d52a70883edc3b4547", "shasum": "" }, "require": { - "php": ">=8.2" + "ext-json": "*", + "nyholm/psr7-server": "^1.1", + "open-telemetry/api": "^1.8", + "open-telemetry/context": "^1.4", + "open-telemetry/sem-conv": "^1.38.0", + "php": "^8.1", + "php-http/discovery": "^1.14", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.0.1|^2.0", + "psr/log": "^1.1|^2.0|^3.0", + "ramsey/uuid": "^3.0 || ^4.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php82": "^1.26", + "tbachert/spi": "^1.0.5" }, - "require-dev": { - "phpunit/phpunit": "^11.3" + "suggest": { + "ext-gmp": "To support unlimited number of synchronous metric readers", + "ext-mbstring": "To increase performance of string operations", + "open-telemetry/sdk-configuration": "File-based OpenTelemetry SDK configuration" }, "type": "library", "extra": { + "spi": { + "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig", + "OpenTelemetry\\SDK\\ConfigEnv\\Trace\\SpanSuppressionStrategySemConv", + "OpenTelemetry\\SDK\\ConfigEnv\\Trace\\SpanSuppressionStrategySpanKind", + "OpenTelemetry\\SDK\\ConfigEnv\\Distribution\\DistributionConfigurationSdk" + ], + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" + ], + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + }, "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "1.14.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "Common/Util/functions.php", + "Logs/Exporter/_register.php", + "Metrics/MetricExporter/_register.php", + "Propagation/_register.php", + "Trace/SpanExporter/_register.php", + "Common/Dev/Compatibility/_load.php", + "_autoload.php" + ], + "psr-4": { + "OpenTelemetry\\SDK\\": "." + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "SDK for OpenTelemetry PHP.", "keywords": [ - "filesystem", - "iterator" + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "sdk", + "tracing" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2026-07-14T13:09:54+00:00" + }, + { + "name": "open-telemetry/sem-conv", + "version": "1.44.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sem-conv.git", + "reference": "c94bc7ebacef039cda4f4c9270807dcc58b4e3af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/c94bc7ebacef039cda4f4c9270807dcc58b4e3af", + "reference": "c94bc7ebacef039cda4f4c9270807dcc58b4e3af", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenTelemetry\\SemConv\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Semantic conventions for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "semantic conventions", + "semconv", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2026-09-09T22:06:17+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/theseer", "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", - "type": "tidelift" + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" } ], - "time": "2026-02-02T13:52:54+00:00" + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" }, { - "name": "phpunit/php-invoker", - "version": "5.0.1", + "name": "phpunit/php-code-coverage", + "version": "11.0.12", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { - "php": ">=8.2" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.5.46" }, "suggest": { - "ext-pcntl": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -828,253 +1245,768 @@ "role": "lead" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "process" + "coverage", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-07-03T05:07:44+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { - "name": "phpunit/php-text-template", - "version": "4.0.1", + "name": "phpunit/php-file-iterator", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T13:52:54+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.55", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-02-18T12:37:06+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "template" + "log", + "psr", + "psr-3" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:08:43+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "phpunit/php-timer", - "version": "7.0.1", + "name": "ramsey/collection", + "version": "2.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { - "php": ">=8.2" + "php": "^8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "7.0-dev" + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Ramsey\\Collection\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "A PHP library for representing and manipulating collections.", "keywords": [ - "timer" + "array", + "collection", + "hash", + "map", + "queue", + "set" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:09:35+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { - "name": "phpunit/phpunit", - "version": "11.5.55", + "name": "ramsey/uuid", + "version": "4.9.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + "url": "https://github.com/ramsey/uuid.git", + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2", + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.1", - "phpunit/php-invoker": "^5.0.1", - "phpunit/php-text-template": "^4.0.1", - "phpunit/php-timer": "^7.0.1", - "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.3", - "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.2", - "sebastian/global-state": "^7.0.2", - "sebastian/object-enumerator": "^6.0.1", - "sebastian/recursion-context": "^6.0.3", - "sebastian/type": "^5.1.3", - "sebastian/version": "^5.0.2", - "staabm/side-effects-detector": "^1.0.5" + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "11.5-dev" + "captainhook": { + "force-install": true } }, "autoload": { "files": [ - "src/Framework/Assert/Functions.php" + "src/functions.php" ], - "classmap": [ - "src/" - ] + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } + "MIT" ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "keywords": [ - "phpunit", - "testing", - "xunit" + "guid", + "identifier", + "uuid" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.4" }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2026-02-18T12:37:06+00:00" + "time": "2026-09-16T11:39:30+00:00" }, { "name": "sebastian/cli-parser", @@ -2198,6 +3130,138 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/polyfill-php82", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/843bbe8b2a7934307b749751eede0f111f5d1cfc", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:50:05+00:00" + }, + { + "name": "tbachert/spi", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/Nevay/spi.git", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "composer/semver": "^1.0 || ^2.0 || ^3.0", + "php": "^8.1" + }, + "require-dev": { + "composer/composer": "^2.0", + "infection/infection": "^0.27.9", + "phpunit/phpunit": "^10.5", + "psalm/phar": "^5.18" + }, + "type": "composer-plugin", + "extra": { + "class": "Nevay\\SPI\\Composer\\Plugin", + "branch-alias": { + "dev-main": "1.0.x-dev" + }, + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Nevay\\SPI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Service provider loading facility", + "keywords": [ + "service provider" + ], + "support": { + "issues": "https://github.com/Nevay/spi/issues", + "source": "https://github.com/Nevay/spi/tree/v1.0.5" + }, + "time": "2025-06-29T15:42:06+00:00" + }, { "name": "theseer/tokenizer", "version": "1.3.1", diff --git a/lib/Client.php b/lib/Client.php index ebe498f..bbac1c9 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -2254,7 +2254,10 @@ private function formatDateTime(\DateTimeInterface $date): string */ public function withContext(array $data, callable $fn, array $options = []): mixed { - return RequestContext::withContext($data, $fn, $options, $this->contextKey()); + return RequestContext::withContext($data, function () use ($fn): mixed { + Internal\SpanContext::enrichCurrent($this); + return $fn(); + }, $options, $this->contextKey()); } /** diff --git a/lib/Internal/SpanContext.php b/lib/Internal/SpanContext.php new file mode 100644 index 0000000..f0878d6 --- /dev/null +++ b/lib/Internal/SpanContext.php @@ -0,0 +1,67 @@ +isRecording()) { + return; + } + + $context = $client->getContext(); + if ($context === null) { + return; + } + + $attributes = $span->toSpanData()->getAttributes(); + foreach (['posthogDistinctId' => 'distinctId', 'sessionId' => 'sessionId'] as $attribute => $key) { + $value = $context[$key] ?? null; + if (is_string($value) && $value !== '' && !$attributes->has($attribute)) { + $span->setAttribute($attribute, $value); + } + } + } catch (\Throwable) { + } + } +} diff --git a/lib/OpenTelemetry/ContextSpanProcessor.php b/lib/OpenTelemetry/ContextSpanProcessor.php new file mode 100644 index 0000000..842656f --- /dev/null +++ b/lib/OpenTelemetry/ContextSpanProcessor.php @@ -0,0 +1,48 @@ +shutdown) { + SpanContext::enrich($this->client, $span); + } + } + + public function onEnd(ReadableSpanInterface $span): void + { + } + + public function forceFlush(?CancellationInterface $cancellation = null): bool + { + return !$this->shutdown; + } + + public function shutdown(?CancellationInterface $cancellation = null): bool + { + if ($this->shutdown) { + return false; + } + + $this->shutdown = true; + SpanContext::unregister($this->client, $this); + return true; + } +} diff --git a/test/ContextSpanProcessorTest.php b/test/ContextSpanProcessorTest.php new file mode 100644 index 0000000..2f390d0 --- /dev/null +++ b/test/ContextSpanProcessorTest.php @@ -0,0 +1,232 @@ +client = new Client('test-token', [], new MockedHttpClient('example.com'), 'test'); + } + + public function testExistingRequestSpanAndChildKeepTheirContextAfterScopeEnds(): void + { + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $tracer = $provider->getTracer('test'); + $request = $tracer->spanBuilder('request')->startSpan(); + $scope = $request->activate(); + try { + $child = $this->client->withContext( + $this->client->contextFromHeaders([ + 'X-PostHog-Distinct-Id' => 'browser-user', + 'X-PostHog-Session-Id' => 'browser-session', + ]), + fn () => $tracer->spanBuilder('child')->startSpan(), + ['fresh' => true] + ); + } finally { + $scope->detach(); + } + $late = $tracer->spanBuilder('late')->startSpan(); + foreach ([$request, $child] as $span) { + $span->end(); + $this->assertSame([ + 'posthogDistinctId' => 'browser-user', + 'sessionId' => 'browser-session', + ], $span->toSpanData()->getAttributes()->toArray()); + } + $this->assertSame([], $late->toSpanData()->getAttributes()->toArray()); + $late->end(); + } + + public static function identityCases(): array + { + return [ + 'explicit identities' => [ + ['posthogDistinctId' => 'app-user', 'sessionId' => 'app-session'], + ['distinctId' => 'header-user', 'sessionId' => 'header-session'], + ['posthogDistinctId' => 'app-user', 'sessionId' => 'app-session'], + ], + 'explicit user' => [ + ['posthogDistinctId' => 'app-user'], + ['distinctId' => 'header-user', 'sessionId' => 'header-session'], + ['posthogDistinctId' => 'app-user', 'sessionId' => 'header-session'], + ], + 'empty explicit values' => [ + ['posthogDistinctId' => '', 'sessionId' => ''], + ['distinctId' => 'header-user', 'sessionId' => 'header-session'], + ['posthogDistinctId' => '', 'sessionId' => ''], + ], + 'missing IDs' => [[], [], []], + 'user only' => [[], ['distinctId' => 'user'], ['posthogDistinctId' => 'user']], + 'session only' => [[], ['sessionId' => 'session'], ['sessionId' => 'session']], + ]; + } + + #[DataProvider('identityCases')] + public function testPreservesExplicitAttributesAndOmitsMissingIds( + array $attributes, + array $context, + array $expected + ): void { + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $span = $this->client->withContext( + $context, + fn () => $provider->getTracer('test')->spanBuilder('child')->setAttributes($attributes)->startSpan() + ); + $span->end(); + $this->assertSame($expected, $span->toSpanData()->getAttributes()->toArray()); + } + + public function testRegisteredBridgePreservesCapacity(): void + { + $provider = new TracerProvider( + [new ContextSpanProcessor($this->client)], + spanLimits: (new SpanLimitsBuilder())->setAttributeCountLimit(1)->build() + ); + $span = $provider->getTracer('test')->spanBuilder('request')->setAttribute('app.key', 'keep')->startSpan(); + $scope = $span->activate(); + try { + $this->client->withContext(['distinctId' => 'user', 'sessionId' => 'session'], static fn () => null); + } finally { + $scope->detach(); + $span->end(); + } + $this->assertSame(['app.key' => 'keep'], $span->toSpanData()->getAttributes()->toArray()); + } + + public function testConcurrentFibersAndFreshScopesStayIsolated(): void + { + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $tracer = $provider->getTracer('test'); + $makeFiber = fn (string $id) => new \Fiber(function () use ($id, $tracer) { + $scope = Context::getRoot()->activate(); + try { + return $this->client->withContext( + ['distinctId' => $id, 'sessionId' => $id], + static function () use ($tracer) { + \Fiber::suspend(); + return $tracer->spanBuilder('child')->startSpan(); + }, + ['fresh' => true] + ); + } finally { + $scope->detach(); + } + }); + $first = $makeFiber('first'); + $second = $makeFiber('second'); + $first->start(); + $second->start(); + foreach (['second' => $second, 'first' => $first] as $id => $fiber) { + $fiber->resume(); + $span = $fiber->getReturn(); + $span->end(); + $this->assertSame($id, $span->toSpanData()->getAttributes()->get('sessionId')); + } + $span = $this->client->withContext( + ['sessionId' => 'outer'], + fn () => $this->client->withContext( + ['distinctId' => 'fresh-user'], + fn () => $tracer->spanBuilder('fresh')->startSpan(), + ['fresh' => true] + ) + ); + $span->end(); + $this->assertSame(['posthogDistinctId' => 'fresh-user'], $span->toSpanData()->getAttributes()->toArray()); + $this->assertNull($this->client->getContext()); + } + + public function testClientIsolationAndFailureCleanup(): void + { + $other = new Client('other-token', [], new MockedHttpClient('example.com'), 'test'); + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $tracer = $provider->getTracer('test'); + try { + $other->withContext(['sessionId' => 'other-session'], function () use ($tracer): void { + $span = $tracer->spanBuilder('other-client')->startSpan(); + $span->end(); + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + }); + $this->client->withContext(['sessionId' => 'failed-session'], static function (): void { + throw new \RuntimeException('test failure'); + }); + $this->fail('Expected the application exception'); + } catch (\RuntimeException $exception) { + $this->assertSame('test failure', $exception->getMessage()); + } + $span = $tracer->spanBuilder('after-failure')->startSpan(); + $span->end(); + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + } + + public function testUninitializedFiberCannotChangeMainSpan(): void + { + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $main = $provider->getTracer('test')->spanBuilder('main')->startSpan(); + $scope = $main->activate(); + try { + $fiber = new \Fiber(fn () => $this->client->withContext( + ['distinctId' => 'fiber-user', 'sessionId' => 'fiber-session'], + static fn () => null + )); + $fiber->start(); + $this->assertTrue($fiber->isTerminated()); + } finally { + $scope->detach(); + $main->end(); + } + $this->assertSame([], $main->toSpanData()->getAttributes()->toArray()); + } + + public function testDefaultContextDoesNotChangeActiveSpan(): void + { + $provider = TracerProvider::builder()->build(); + $span = $provider->getTracer('test')->spanBuilder('request')->startSpan(); + $scope = $span->activate(); + try { + $this->client->withContext(['sessionId' => 'unregistered'], static fn () => null); + } finally { + $scope->detach(); + $span->end(); + } + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + } + + public function testContextLookupFailureDoesNotBreakApplicationTracing(): void + { + $client = $this->createMock(Client::class); + $client->method('getContext')->willThrowException(new \RuntimeException('lookup failure')); + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($client))->build(); + $span = $provider->getTracer('test')->spanBuilder('application')->startSpan(); + $this->assertTrue($span->isRecording()); + $span->end(); + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + } + + public function testProcessorShutdownStopsNewChildEnrichment(): void + { + $processor = new ContextSpanProcessor($this->client); + $provider = TracerProvider::builder()->addSpanProcessor($processor)->build(); + $this->assertTrue($processor->forceFlush()); + $this->assertTrue($processor->shutdown()); + $this->assertFalse($processor->shutdown()); + $span = $this->client->withContext( + ['sessionId' => 'session'], + fn () => $provider->getTracer('test')->spanBuilder('after-shutdown')->startSpan() + ); + $span->end(); + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + } +} From f6701b38326233d82aefbeb51979974b83b0198d Mon Sep 17 00:00:00 2001 From: pauldambra Date: Sat, 17 Oct 2026 23:58:54 +0100 Subject: [PATCH 2/2] fix: skip span context for disabled php clients Generated-By: PostHog Desktop Task-Id: bfd18848-e498-4352-a58a-b9ba85de4198 --- README.md | 2 ++ lib/Internal/SpanContext.php | 7 ++++++- test/ContextSpanProcessorTest.php | 33 +++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f2e37d0..a28b2ff 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ SDK usage examples and code snippets live in the official documentation so they The optional `PostHog\OpenTelemetry\ContextSpanProcessor` adds session and person IDs to application spans inside this client's request context. Register it on the application's existing OpenTelemetry provider. OpenTelemetry remains an optional application dependency. +A client with no project key keeps request context but adds no span attributes. A valid-key client with the `noop` consumer still adds them. + The registered processor also enables request span enrichment when `withContext()` starts. This lookup skips Fibers because uninitialized OpenTelemetry Fiber storage can return another execution path's span. Child spans still use the active Fiber's PostHog context. New Fibers and queue jobs need explicit context propagation. Existing span attributes take precedence. The processor preserves the application's provider, sampling, and exporters. See the [PHP tracing guide](https://posthog.com/docs/distributed-tracing/installation/php) and [Laravel guide](https://posthog.com/docs/libraries/laravel) for setup. diff --git a/lib/Internal/SpanContext.php b/lib/Internal/SpanContext.php index f0878d6..6cc7da0 100644 --- a/lib/Internal/SpanContext.php +++ b/lib/Internal/SpanContext.php @@ -50,7 +50,7 @@ public static function enrich(Client $client, SpanInterface $span): void } $context = $client->getContext(); - if ($context === null) { + if ($context === null || !self::isEnabled($client)) { return; } @@ -64,4 +64,9 @@ public static function enrich(Client $client, SpanInterface $span): void } catch (\Throwable) { } } + + private static function isEnabled(Client $client): bool + { + return \Closure::bind(fn (): bool => $this->enabled === true, $client, Client::class)(); + } } diff --git a/test/ContextSpanProcessorTest.php b/test/ContextSpanProcessorTest.php index 2f390d0..cee4a90 100644 --- a/test/ContextSpanProcessorTest.php +++ b/test/ContextSpanProcessorTest.php @@ -50,6 +50,39 @@ public function testExistingRequestSpanAndChildKeepTheirContextAfterScopeEnds(): $late->end(); } + public static function disabledClientKeys(): array + { + return ['missing' => [null, false], 'empty' => ['', false], 'blank' => [" \t ", false], + 'enabled noop consumer' => ['test-token', true]]; + } + + #[DataProvider('disabledClientKeys')] + public function testClientStateControlsContextAndSpanEnrichment(?string $apiKey, bool $enabled): void + { + $client = new Client($apiKey, ['consumer' => 'noop']); + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($client))->build(); + $tracer = $provider->getTracer('test'); + $request = $tracer->spanBuilder('request')->setAttribute('app.key', 'keep')->startSpan(); + $scope = $request->activate(); + try { + $child = $client->withContext( + ['distinctId' => 'user', 'sessionId' => 'session'], + function () use ($client, $tracer) { + $this->assertSame('user', $client->getContext()['distinctId'] ?? null); + return $tracer->spanBuilder('child')->startSpan(); + } + ); + } finally { + $scope->detach(); + $request->end(); + } + $child->end(); + $ids = $enabled ? ['posthogDistinctId' => 'user', 'sessionId' => 'session'] : []; + $this->assertSame(['app.key' => 'keep'] + $ids, $request->toSpanData()->getAttributes()->toArray()); + $this->assertSame($ids, $child->toSpanData()->getAttributes()->toArray()); + $this->assertNull($client->getContext()); + } + public static function identityCases(): array { return [