-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: GRPC Exporter #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds a Swoole coroutine-based OTLP/gRPC transport and wires new OTLP gRPC exporters for traces/metrics/logs into the package configuration, aiming to avoid blocking ext-grpc behavior in Swoole workers.
Changes:
- Introduces
SwooleGrpcTransport+SwooleGrpcTransportFactoryusingSwoole\Coroutine\Http2\Client. - Adds OTLP gRPC exporter factories for traces, metrics, and logs using the new transport.
- Updates default published config to expose new
otlp_grpcexporters and updates Composer dependencies.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
src/Transport/SwooleGrpcTransport.php |
Implements gRPC framing + HTTP/2 request/response handling for OTLP export. |
src/Transport/SwooleGrpcTransportFactory.php |
Parses endpoints/headers and constructs SwooleGrpcTransport. |
src/Factory/Trace/Exporter/OtlpGrpcTraceExporterFactory.php |
Adds trace exporter factory using the new gRPC transport. |
src/Factory/Metric/Exporter/OtlpGrpcMetricExporterFactory.php |
Adds metric exporter factory using the new gRPC transport. |
src/Factory/Log/Exporter/OtlpGrpcLogExporterFactory.php |
Adds log exporter factory using the new gRPC transport. |
publish/open-telemetry.php |
Registers new otlp_grpc exporters and changes an instrumentation default (ignore_paths). |
tests/Unit/Transport/SwooleGrpcTransportTest.php |
Unit tests for transport lifecycle + closed-send behavior. |
tests/Unit/Transport/SwooleGrpcTransportFactoryTest.php |
Unit tests for endpoint parsing and validation (currently has failing assertions). |
tests/Unit/Factory/*/Exporter/OtlpGrpc*ExporterFactoryTest.php |
Smoke tests verifying exporter factories produce the correct exporter interface types. |
composer.json |
Adds prefer-stable and requires hyperf/grpc-client. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private function parseEndpoint(string $endpoint): array | ||
| { | ||
| $parts = parse_url($endpoint); | ||
|
|
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parse_url() can return false for malformed endpoints; accessing $parts['scheme'] on false will emit warnings. Consider explicitly handling false (and/or non-array) return values and throwing InvalidArgumentException before checking keys.
| if (! is_array($parts)) { | |
| throw new InvalidArgumentException('Endpoint is malformed and could not be parsed'); | |
| } |
| 'retry_delay' => (int) env('OTEL_TRACES_RETRY_DELAY_MS', 100), | ||
| 'max_retries' => (int) env('OTEL_TRACES_RETRY_MAX', 3), |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new gRPC exporter options use flat retry_delay/max_retries keys, while the existing OTLP/HTTP exporters in the same config file use a nested retry array. This inconsistency makes configuration harder to reason about; consider aligning the shape (either flatten or nest consistently) and updating the corresponding factories accordingly.
| 'retry_delay' => (int) env('OTEL_TRACES_RETRY_DELAY_MS', 100), | |
| 'max_retries' => (int) env('OTEL_TRACES_RETRY_MAX', 3), | |
| 'retry' => [ | |
| 'delay_ms' => (int) env('OTEL_TRACES_RETRY_DELAY_MS', 100), | |
| 'max_retries' => (int) env('OTEL_TRACES_RETRY_MAX', 3), | |
| ], |
| return new LogsExporter( | ||
| (new SwooleGrpcTransportFactory())->create( | ||
| endpoint: $endpoint, | ||
| contentType: 'application/x-protobuf', | ||
| headers: $options['headers'] ?? [], | ||
| compression: $options['compression'] ?? TransportFactoryInterface::COMPRESSION_GZIP, | ||
| timeout: $options['timeout'] ?? 10, | ||
| retryDelay: $options['retry_delay'] ?? 100, | ||
| maxRetries: $options['max_retries'] ?? 3, |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These factories pass a magic string 'application/x-protobuf' to the transport factory. Since SwooleGrpcTransportFactory validates against ContentTypes::PROTOBUF, use the ContentTypes::PROTOBUF constant here to avoid accidental mismatch if the upstream constant changes.
| $this->expectException(InvalidArgumentException::class); | ||
| $this->expectExceptionMessage('not a valid gRPC method'); | ||
|
|
||
| $factory->create( | ||
| endpoint: 'http://localhost:4317/invalid-path', | ||
| contentType: ContentTypes::PROTOBUF, | ||
| ); |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expectExceptionMessage('not a valid gRPC method') doesn’t match the actual exception text (Endpoint path is not a valid gRPC method: "..."), so this test will fail. Adjust the assertion to match the full message or use a partial/regex match.
| public function create( | ||
| string $endpoint, | ||
| string $contentType = ContentTypes::PROTOBUF, | ||
| array $headers = [], | ||
| $compression = null, | ||
| float $timeout = 10., | ||
| int $retryDelay = 100, | ||
| int $maxRetries = 3, | ||
| ?string $cacert = null, | ||
| ?string $cert = null, | ||
| ?string $key = null, | ||
| ): TransportInterface { | ||
| if ($contentType !== ContentTypes::PROTOBUF) { | ||
| throw new InvalidArgumentException( | ||
| sprintf('Unsupported content type "%s", gRPC transport supports only %s', $contentType, ContentTypes::PROTOBUF) | ||
| ); | ||
| } | ||
|
|
||
| $parsed = $this->parseEndpoint($endpoint); | ||
|
|
||
| $grpcHeaders = $this->buildHeaders($headers); | ||
|
|
||
| $compressionType = null; | ||
| if ($compression !== null) { | ||
| $compressionType = is_array($compression) ? ($compression[0] ?? null) : $compression; | ||
| } | ||
|
|
||
| return new SwooleGrpcTransport( | ||
| host: $parsed['host'], | ||
| port: $parsed['port'], | ||
| method: $parsed['method'], | ||
| headers: $grpcHeaders, | ||
| timeout: $timeout, | ||
| ssl: $parsed['ssl'], | ||
| compression: $compressionType, | ||
| ); |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
retryDelay, maxRetries, and TLS params ($cacert, $cert, $key) are accepted by create() but ignored when constructing SwooleGrpcTransport. Either implement retry/TLS handling (e.g., client set() options) or remove/mark these params as unsupported to avoid misleading configuration.
| "hyperf/database": "~3.1.0", | ||
| "hyperf/di": "~3.1.0", | ||
| "hyperf/event": "~3.1.0", | ||
| "hyperf/grpc-client": "~3.1.0", |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hyperf/grpc-client is added to require, but the new implementation uses Swoole\Coroutine\Http2\Client directly and there are no references to the Hyperf gRPC client in the codebase. If it’s not needed, remove this dependency to avoid extra install surface and transitive constraints.
| "hyperf/grpc-client": "~3.1.0", |
| 'ignore_paths' => [ | ||
| // '/^\/health$/', | ||
| '/^\/health$/', | ||
| ], |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change enables '/^\/health$/' in ignore_paths, which alters default instrumentation behavior and is not described in the PR summary (which focuses on gRPC exporter/transport). Either revert this unrelated config change or call it out explicitly in the PR description/release notes.
| return new SpanExporter( | ||
| (new SwooleGrpcTransportFactory())->create( | ||
| endpoint: $endpoint, | ||
| contentType: 'application/x-protobuf', | ||
| headers: $options['headers'] ?? [], | ||
| compression: $options['compression'] ?? TransportFactoryInterface::COMPRESSION_GZIP, | ||
| timeout: $options['timeout'] ?? 10, |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These factories pass a magic string 'application/x-protobuf' to the transport factory. Since SwooleGrpcTransportFactory validates against ContentTypes::PROTOBUF, use the ContentTypes::PROTOBUF constant here to avoid accidental mismatch if the upstream constant changes.
| return new MetricExporter( | ||
| transport: (new SwooleGrpcTransportFactory())->create( | ||
| endpoint: $endpoint, | ||
| contentType: 'application/x-protobuf', | ||
| headers: $options['headers'] ?? [], | ||
| compression: $options['compression'] ?? TransportFactoryInterface::COMPRESSION_GZIP, | ||
| timeout: $options['timeout'] ?? 10, | ||
| retryDelay: $options['retry_delay'] ?? 100, | ||
| maxRetries: $options['max_retries'] ?? 3, |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These factories pass a magic string 'application/x-protobuf' to the transport factory. Since SwooleGrpcTransportFactory validates against ContentTypes::PROTOBUF, use the ContentTypes::PROTOBUF constant here to avoid accidental mismatch if the upstream constant changes.
| $this->expectException(InvalidArgumentException::class); | ||
| $this->expectExceptionMessage('Unsupported content type'); | ||
|
|
||
| $factory->create( | ||
| endpoint: 'http://localhost:4317/opentelemetry.proto.collector.trace.v1.TraceService/Export', | ||
| contentType: 'application/json', | ||
| ); |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expectExceptionMessage('Unsupported content type') will fail because the factory throws Unsupported content type "%s", gRPC transport supports only %s (full message must match). Update the test to assert the full message or switch to expectExceptionMessageMatches()/assertStringContainsString()-style matching.
Description
This PR adds a native gRPC transport implementation using Swoole's HTTP/2 coroutine client, providing non-blocking async telemetry export.
The standard open-telemetry/transport-grpc package uses
ext-grpcwhich is blocking and has no Swoole hook support. This means gRPC calls block the entire worker, defeating the purpose of using Swoole coroutines.Type of change
Checklist
feat: add SQS aspect)composer testpasses locally