Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Transport Grpc Laravel Package

open-telemetry/transport-grpc

gRPC transport for OpenTelemetry PHP, enabling OTLP trace, metric, and log export over gRPC. Integrates with the OpenTelemetry SDK and supports efficient protobuf-based telemetry delivery to collectors and observability backends.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites you must verify before anything else.

php -m | grep -E 'grpc|protobuf'   # both should print
php -i | grep grpc                # confirm extension version

If grpc is missing, stop. There is no pure-PHP fallback for this package — composer require grpc/grpc installs only the Grpc\* stub classes, not the C-core extension that actually opens a channel. On Debian-based images it is pecl install grpc && docker-php-ext-enable grpc; on Alpine you need grpc built against the same OpenSSL/zlib as PHP.

Install.

composer require open-telemetry/transport-grpc \
                 open-telemetry/exporter-otlp \
                 open-telemetry/sdk \
                 open-telemetry/opentelemetry-auto-laravel

transport-grpc is only a transport factory. It does no exporting by itself — it is injected into SpanExporter/MetricExporter/LogRecordExporter from open-telemetry/exporter-otlp, which in turn is constructed by the SDK's ExporterFactory.

Where to look first.

  1. vendor/open-telemetry/transport-grpc/src/GrpcTransportFactory.php — the entire package. Read create() and its default method path; that signature is your integration contract.
  2. vendor/open-telemetry/sdk/Common/Export/ExporterFactory.php — this is what actually chooses gRPC vs HTTP based on OTEL_EXPORTER_OTLP_PROTOCOL. In a normal Laravel setup you never touch GrpcTransportFactory by hand.
  3. Your Collector's debug exporter output — the ground truth that spans are arriving at all.

First use case: HTTP request traces to a local Collector.

Run a Collector with a debug exporter exposing 4317, then in .env:

OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
OTEL_SERVICE_NAME=checkout-api
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=local
OTEL_TRACES_SAMPLER=parentbased_always_on

Hit a route, watch your docker compose logs -f otel-collector. Once you see a span with a trace_id, the transport is proven and everything after that is instrumentation work, not plumbing.

If you are not using auto-instrumentation, the manual equivalent in a service provider is:

use OpenTelemetry\Contrib\Grpc\GrpcTransportFactory;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Trace\{TracerProvider, SpanProcessor\BatchSpanProcessor};

$transport = (new GrpcTransportFactory())->create(
    endpoint: 'otel-collector:4317',
    method: '/opentelemetry.proto.collector.trace.v1.TraceService/Export',
    headers: ['authorization' => 'Bearer '.config('observability.token')],
);

$provider = new TracerProvider(
    new BatchSpanProcessor(new SpanExporter($transport))
);

Bind the resulting TracerProviderInterface into the container, and register a shutdown hook (see Gotchas).

Implementation Patterns

Pattern 1 — environment-driven transport selection (preferred). Treat the transport as configuration, never as code. OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf and grpc are interchangeable at the ExporterFactory seam; the instrumentation above it is identical. This gives you a one-line rollback if the gRPC path misbehaves in production and lets staging run HTTP while production runs gRPC during a migration.

Signal-specific overrides exist and are the usual source of confusion: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, ..._METRICS_ENDPOINT, ..._LOGS_ENDPOINT beat the generic variable. Use them when metrics go to a different Collector pipeline than traces.

Pattern 2 — bootstrap in a service provider, flush on terminate. For php-fpm, register the provider in register() and flush on shutdown:

// AppServiceProvider::register()
$this->app->singleton(TracerProviderInterface::class, fn () => /* build */);

// AppServiceProvider::boot()
register_shutdown_function(fn () => $this->app->make(TracerProviderInterface::class)->shutdown());

For a persistent worker (Octane, Horizon, queue:work), do not rely on register_shutdown_function for per-job flushes. Flush on the framework's own lifecycle events instead — Octane's RequestTerminated/WorkerStopping, and Illuminate\Queue\Events\JobProcessed for queue workers — otherwise the last batch of a long-lived worker sits in memory until the process dies, which may be hours.

Pattern 3 — batch processor tuning is your latency control. The exporter runs inside the request/worker lifecycle. Configure deliberately:

OTEL_BSP_MAX_QUEUE_SIZE=2048
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512
OTEL_BSP_SCHEDULE_DELAY=5000
OTEL_BSP_EXPORT_TIMEOUT=10000
OTEL_EXPORTER_OTLP_TIMEOUT=10
OTEL_EXPORTER_OTLP_COMPRESSION=gzip

On Octane with sustained throughput, a longer SCHEDULE_DELAY and larger batch amortizes the channel far better than on php-fpm, where each request pays channel + TLS setup. If you measure tail latency regressions after enabling gRPC on php-fpm, that is the expected physics, not a bug — reduce batch size or move to HTTP with connection reuse.

Pattern 4 — headers for auth are per-transport, not per-vendor. Collector gateways and vendors want bearer tokens or x-honeycomb-team-style headers. OTEL_EXPORTER_OTLP_HEADERS is a comma-separated key=value list; values containing commas or = need care, so prefer building the array in code when the token is arbitrary:

$transport = (new GrpcTransportFactory())->create(
    endpoint: config('observability.endpoint'),
    method: '/opentelemetry.proto.collector.trace.v1.TraceService/Export',
    headers: ['authorization' => 'Bearer '.config('observability.token')],
);

Pattern 5 — one Collector pipeline per concern. Because gRPC lands on the Collector, not a vendor, put sampling and fan-out there. App-side you emit everything (parentbased_always_on locally), and the Collector does tail sampling, attribute scrubbing, and multi-destination routing to Tempo + your vendor during a cutover. This is the pattern that makes the "swap the APM vendor" argument real rather than rhetorical.

Pattern 6 — instrumentation sequencing. In a Laravel app the high-value order is: HTTP server spans → queue job spans (with trace context propagation through the job payload) → outbound HTTP client spans → Eloquent/Redis → custom business spans (checkout.submit, sync.run). The first two give you cross-service traces; the rest is detail. Budget framework-level auto-instrumentation as work you own — the OTel PHP Laravel instrumentation is thinner than the ecosystem's marketing suggests.

Pattern 7 — verify with grpcurl before blaming PHP.

grpcurl -plaintext collector:4317 list
grpcurl -plaintext -d '{}' collector:4317 \
  opentelemetry.proto.collector.trace.v1.TraceService/Export

If list fails, the problem is network/TLS/Collector, not your exporter — and you've saved an afternoon of reading SDK source.

Gotchas and Tips

The package is a read-only subtree split. Stars and "0 dependents" reflect packaging, not health: issues, PRs and advisories live in open-telemetry/opentelemetry-php. File nothing here, and don't wire support tooling to this repo's activity.

Pin the SDK, exporter, and transport to the same release train. These are split from one monorepo and versioned together. Mismatched minors produce interface drift in TransportFactoryInterface and in exporter constructor signatures — the single most common failure mode reported for this stack. Treat bumps as one atomic composer update open-telemetry/* and put it on a calendar with a named owner.

ext-grpc blocks PHP upgrades. The PECL extension historically lags new PHP minors. If your CI matrix and base images build the extension from source, a PHP 8.x bump means validating the extension first — plan it as a dependency, not an afterthought. Also confirm OpenSSL/zlib flags match across local, CI, and prod images; a green local suite has repeatedly masked a missing extension in production.

ext-protobuf present vs absent changes serialization. Without the C extension, google/protobuf falls back to pure-PHP Google\Protobuf\Internal, which is dramatically slower and has had behaviour differences from the C implementation. Verify the same mode is in use everywhere or you get "works in staging, garbage in prod" symptoms. Check php -m | grep protobuf in every environment, including Octane and Horizon containers.

gRPC needs HTTP/2 end to end. Proxies, ALBs and nginx defaults that terminate HTTP/1.1 will break the channel. AWS ALB needs a gRPC target group; nginx needs grpc_pass/grpc_set_header and http2 in listen. If your Collector sits behind a standard web ingress you almost certainly want http:// on an internal network or a dedicated TCP passthrough — not a path-based rule.

4317 vs 4318. OTLP/gRPC is 4317, OTLP/HTTP is 4318. Pointing the gRPC transport at 4318 yields a confusing hang or an immediate channel error, never a clean "wrong protocol" message.

TLS is credentials-based, not scheme-based. The grpc C-core takes host:port plus a ChannelCredentials object. Do not assume prefixing the endpoint with https:// negotiates TLS; the transport's endpoint parsing may strip the scheme. Set credentials explicitly (Grpc\ChannelCredentials::createSsl($caBundle)) or set the insecure flag for internal plaintext, and use OTEL_EXPORTER_OTLP_CERTIFICATE for a custom CA bundle.

The method path must match the signal. SpanExporter, MetricExporter and LogRecordExporter each need their own service path (...TraceService/Export, ...MetricsService/Export, ...LogsService/Export). When constructing a transport manually, copy-pasting a trace path into a metrics exporter produces silent drops or UNIMPLEMENTED — use ExporterFactory and avoid hand-rolling.

A blocking exporter can add tail latency to user traffic. Fail open: telemetry loss is preferable to a 10-second export timeout in a checkout request. Set OTEL_EXPORTER_OTLP_TIMEOUT low, cap the batch queue, and confirm the batch processor drops on a full queue rather than blocking the request thread.

Forked workers need their channels rebuilt. Horizon and some queue setups fork after boot; a gRPC channel created pre-fork and used post-fork is undefined behaviour. Initialize the provider in the WorkerStarting/job-boot path, not only in AppServiceProvider::register().

Logs over OTLP are the weakest signal. Emit traces first; PHP OTel log support is less mature than tracing and a dedicated error tracker gives more value per hour of integration. Metrics are fine but you will define every instrument yourself.

OTEL_PHP_AUTOLOAD_ENABLED=true is a Composer-level hook. It works through an autoloaded _register.php that runs before Laravel boots. That means config-repository values and .env loaded by Laravel are not available to it — all OTel configuration must be real process environment variables at the php-fpm/container level, not just in .env. A missing env in the container manifests as a silently no-op tracer with zero errors.

Debugging checklist, in order: php -m | grep grpc → grpcurl list against the endpoint → Collector debug exporter output → OTEL_LOG_LEVEL=debug for SDK internal logging → check that spans are sampled (parentbased_always_on temporarily) → check the shutdown flush actually runs (shutdown() returns true). Ninety percent of "no traces in the backend" tickets resolve in the first two steps.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager