diff --git a/docker-compose.yaml b/docker-compose.yaml index 2d3ad109f..2e3630375 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,9 +14,10 @@ services: PHP_IDE_CONFIG: ${PHP_IDE_CONFIG:-''} RABBIT_HOST: ${RABBIT_HOST:-rabbitmq} KAFKA_HOST: ${KAFKA_HOST:-kafka} + MONGODB_HOST: ${MONGODB_HOST:-mongodb} + MONGODB_PORT: ${MONGODB_PORT:-27017} MYSQL_HOST: ${MYSQL_HOST:-mysql} - zipkin: image: openzipkin/zipkin-slim ports: @@ -63,6 +64,12 @@ services: volumes: - ./docker/kafka/update_run.sh:/tmp/update_run.sh + mongodb: + image: mongo:4 + hostname: mongodb + ports: + - "27017:27017/tcp" + mysql: image: mysql:8.0 hostname: mysql diff --git a/src/Instrumentation/MongoDB/composer.json b/src/Instrumentation/MongoDB/composer.json index 47bb507f7..df7c245b8 100644 --- a/src/Instrumentation/MongoDB/composer.json +++ b/src/Instrumentation/MongoDB/composer.json @@ -6,10 +6,9 @@ "homepage": "https://opentelemetry.io/docs/php", "readme": "./README.md", "license": "Apache-2.0", - "minimum-stability": "dev", "require": { "php": ">=7.4", - "ext-mongodb": "*", + "ext-mongodb": "^1.13", "ext-json": "*", "mongodb/mongodb": "^1.15", "open-telemetry/api": "^1.0", @@ -40,7 +39,8 @@ }, "config": { "allow-plugins": { - "php-http/discovery": false + "php-http/discovery": false, + "tbachert/spi": false } } } diff --git a/src/Instrumentation/MongoDB/phpstan.neon.dist b/src/Instrumentation/MongoDB/phpstan.neon.dist index 0bcc76f16..a68e7ce2b 100644 --- a/src/Instrumentation/MongoDB/phpstan.neon.dist +++ b/src/Instrumentation/MongoDB/phpstan.neon.dist @@ -7,3 +7,8 @@ parameters: paths: - src - tests + ignoreErrors: + - + message: "#Call to an undefined method .*#" + paths: + - src/MongoDBInstrumentationSubscriber.php diff --git a/src/Instrumentation/MongoDB/src/MongoDBInstrumentationSubscriber.php b/src/Instrumentation/MongoDB/src/MongoDBInstrumentationSubscriber.php index e2f63edb2..b94f6cddd 100644 --- a/src/Instrumentation/MongoDB/src/MongoDBInstrumentationSubscriber.php +++ b/src/Instrumentation/MongoDB/src/MongoDBInstrumentationSubscriber.php @@ -9,6 +9,16 @@ use MongoDB\Driver\Monitoring\CommandStartedEvent; use MongoDB\Driver\Monitoring\CommandSubscriber; use MongoDB\Driver\Monitoring\CommandSucceededEvent; +use MongoDB\Driver\Monitoring\SDAMSubscriber; +use MongoDB\Driver\Monitoring\ServerChangedEvent; +use MongoDB\Driver\Monitoring\ServerClosedEvent; +use MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent; +use MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent; +use MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent; +use MongoDB\Driver\Monitoring\ServerOpeningEvent; +use MongoDB\Driver\Monitoring\TopologyChangedEvent; +use MongoDB\Driver\Monitoring\TopologyClosedEvent; +use MongoDB\Driver\Monitoring\TopologyOpeningEvent; use OpenTelemetry\API\Instrumentation\CachedInstrumentation; use OpenTelemetry\API\Trace\Span; use OpenTelemetry\API\Trace\SpanBuilderInterface; @@ -18,13 +28,17 @@ use OpenTelemetry\SemConv\TraceAttributes; use Throwable; -final class MongoDBInstrumentationSubscriber implements CommandSubscriber +final class MongoDBInstrumentationSubscriber implements CommandSubscriber, SDAMSubscriber { private CachedInstrumentation $instrumentation; /** * @var Closure(object):?string */ private Closure $commandSerializer; + /** + * @var array>> + */ + private array $serverAttributes = []; /** * @param (callable(object):?string) $commandSerializer @@ -41,16 +55,26 @@ public function __construct(CachedInstrumentation $instrumentation, callable $co }; } + /** + * @psalm-suppress MixedAssignment,MixedArrayTypeCoercion,MixedArrayOffset,MixedArgument + */ public function commandStarted(CommandStartedEvent $event): void { $command = $event->getCommand(); $collectionName = MongoDBCollectionExtractor::extract($command); $databaseName = $event->getDatabaseName(); $commandName = $event->getCommandName(); - $server = $event->getServer(); - $info = $server->getInfo(); - $port = $server->getPort(); - $host = $server->getHost(); + /** @phpstan-ignore-next-line */ + if (version_compare(phpversion('mongodb'), '1.20.0', '>=')) { + $host = $event->getHost(); + $port = $event->getPort(); + } else { + $server = $event->getServer(); + $host = $server->getHost(); + $port = $server->getPort(); + } + $attributes = $this->serverAttributes[$host][$port] ?? []; + $isSocket = str_starts_with($host, '/'); /** @psalm-suppress RiskyTruthyFalsyComparison **/ $scopedCommand = ($collectionName ? $collectionName . '.' : '') . $commandName; @@ -65,17 +89,10 @@ public function commandStarted(CommandStartedEvent $event): void ->setAttribute(TraceAttributes::NETWORK_TRANSPORT, $isSocket ? 'unix' : 'tcp') ->setAttribute(TraceAttributes::DB_STATEMENT, ($this->commandSerializer)($command)) ->setAttribute(TraceAttributes::DB_MONGODB_COLLECTION, $collectionName) - ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_MASTER, $info['ismaster'] ?? null) - ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_READ_ONLY, $info['readOnly'] ?? null) - ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_CONNECTION_ID, $info['connectionId'] ?? null) ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_REQUEST_ID, $event->getRequestId()) ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_OPERATION_ID, $event->getOperationId()) - ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_MAX_WIRE_VERSION, $info['maxWireVersion'] ?? null) - ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_MIN_WIRE_VERSION, $info['minWireVersion'] ?? null) - ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_MAX_BSON_OBJECT_SIZE_BYTES, $info['maxBsonObjectSize'] ?? null) - ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_MAX_MESSAGE_SIZE_BYTES, $info['maxMessageSizeBytes'] ?? null) - ->setAttribute(MongoDBTraceAttributes::DB_MONGODB_MAX_WRITE_BATCH_SIZE, $info['maxWriteBatchSize'] ?? null); - + ->setAttributes($attributes) + ; $parent = Context::getCurrent(); $span = $builder->startSpan(); Context::storage()->attach($span->storeInContext($parent)); @@ -118,4 +135,61 @@ private static function endSpan(?Throwable $exception = null): void $span->end(); } + + /** + * @todo In a load-balanced scenario, the hello response may be empty. + */ + public function serverChanged(ServerChangedEvent $event): void + { + $host = $event->getHost(); + $port = $event->getPort(); + $info = $event->getNewDescription()->getHelloResponse(); + $attributes = [ + MongoDBTraceAttributes::DB_MONGODB_MASTER => $info['ismaster'] ?? null, + MongoDBTraceAttributes::DB_MONGODB_READ_ONLY => $info['readOnly'] ?? null, + MongoDBTraceAttributes::DB_MONGODB_CONNECTION_ID => $info['connectionId'] ?? null, + MongoDBTraceAttributes::DB_MONGODB_MAX_WIRE_VERSION => $info['maxWireVersion'] ?? null, + MongoDBTraceAttributes::DB_MONGODB_MIN_WIRE_VERSION => $info['minWireVersion'] ?? null, + MongoDBTraceAttributes::DB_MONGODB_MAX_BSON_OBJECT_SIZE_BYTES => $info['maxBsonObjectSize'] ?? null, + MongoDBTraceAttributes::DB_MONGODB_MAX_MESSAGE_SIZE_BYTES => $info['maxMessageSizeBytes'] ?? null, + MongoDBTraceAttributes::DB_MONGODB_MAX_WRITE_BATCH_SIZE => $info['maxWriteBatchSize'] ?? null, + ]; + $this->serverAttributes[$host][$port] = $attributes; + } + + public function serverOpened(ServerOpeningEvent $event): void + { + } + + public function serverClosed(ServerClosedEvent $event): void + { + } + + public function serverOpening(ServerOpeningEvent $event): void + { + } + + public function serverHeartbeatFailed(ServerHeartbeatFailedEvent $event): void + { + } + + public function serverHeartbeatStarted(ServerHeartbeatStartedEvent $event): void + { + } + + public function serverHeartbeatSucceeded(ServerHeartbeatSucceededEvent $event): void + { + } + + public function topologyChanged(TopologyChangedEvent $event): void + { + } + + public function topologyClosed(TopologyClosedEvent $event): void + { + } + + public function topologyOpening(TopologyOpeningEvent $event): void + { + } } diff --git a/src/Instrumentation/MongoDB/src/MongoDBTraceAttributes.php b/src/Instrumentation/MongoDB/src/MongoDBTraceAttributes.php index 807bd4352..22456402c 100644 --- a/src/Instrumentation/MongoDB/src/MongoDBTraceAttributes.php +++ b/src/Instrumentation/MongoDB/src/MongoDBTraceAttributes.php @@ -4,6 +4,10 @@ namespace OpenTelemetry\Contrib\Instrumentation\MongoDB; +/** + * @todo These attributes are not part of the specification and should be removed before this package goes stable, + * unless they are on the way to being added to the specification. See https://github.com/open-telemetry/opentelemetry-specification/blob/v1.40.0/specification/telemetry-stability.md + */ interface MongoDBTraceAttributes { public const DB_MONGODB_MASTER = 'db.mongodb.master'; diff --git a/src/Instrumentation/MongoDB/tests/Integration/MongoDBInstrumentationTest.php b/src/Instrumentation/MongoDB/tests/Integration/MongoDBInstrumentationTest.php index 824fc5608..6675495a7 100644 --- a/src/Instrumentation/MongoDB/tests/Integration/MongoDBInstrumentationTest.php +++ b/src/Instrumentation/MongoDB/tests/Integration/MongoDBInstrumentationTest.php @@ -22,6 +22,9 @@ class MongoDBInstrumentationTest extends TestCase { private const DATABASE_NAME = 'db'; private const COLLECTION_NAME = 'coll'; + private string $host; + private int $port; + private string $uri; private ScopeInterface $scope; /** @var ArrayObject */ private ArrayObject $storage; @@ -29,6 +32,9 @@ class MongoDBInstrumentationTest extends TestCase public function setUp(): void { + $this->host = $_SERVER['MONGODB_HOST'] ?? '127.0.0.1'; + $this->port = (int) ($_SERVER['MONGODB_PORT'] ?? 27017); + $this->uri = "mongodb://$this->host:$this->port"; /** @psalm-suppress MixedPropertyTypeCoercion */ $this->storage = new ArrayObject(); $tracerProvider = new TracerProvider( @@ -49,7 +55,7 @@ public function tearDown(): void public function test_mongodb_find_one(): void { - $manager = new Manager('mongodb://127.0.0.1:27017'); + $manager = new Manager($this->uri); $find = new FindOne(self::DATABASE_NAME, self::COLLECTION_NAME, ['a' => 'b']); @@ -67,8 +73,8 @@ public function test_mongodb_find_one(): void self::assertSame(self::DATABASE_NAME, $attributes->get(TraceAttributes::DB_NAME)); self::assertSame('find', $attributes->get(TraceAttributes::DB_OPERATION)); self::assertSame(self::COLLECTION_NAME, $attributes->get(TraceAttributes::DB_MONGODB_COLLECTION)); - self::assertSame('127.0.0.1', $attributes->get(TraceAttributes::SERVER_ADDRESS)); - self::assertSame(27017, $attributes->get(TraceAttributes::SERVER_PORT)); + self::assertSame($this->host, $attributes->get(TraceAttributes::SERVER_ADDRESS)); + self::assertSame($this->port, $attributes->get(TraceAttributes::SERVER_PORT)); self::assertSame('tcp', $attributes->get(TraceAttributes::NETWORK_TRANSPORT)); self::assertTrue($attributes->get(MongoDBTraceAttributes::DB_MONGODB_MASTER)); self::assertFalse($attributes->get(MongoDBTraceAttributes::DB_MONGODB_READ_ONLY));