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

Cloud Monitoring Laravel Package

google/cloud-monitoring

Google Cloud Monitoring API client for PHP, with REST and gRPC support for alert policies, uptime checks, metrics, and dashboards. Install via Composer and authenticate with Google Cloud credentials.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and authenticate. The package is a generated GAPIC client, not a Laravel package — there is no service provider, no config file, and no facade. You wire it yourself.

composer require google/cloud-monitoring

Authentication goes through Google Application Default Credentials. Locally, point at a service-account JSON; in production (GKE/Cloud Run/GCE) prefer Workload Identity or the metadata server and ship no key at all.

GOOGLE_CLOUD_PROJECT=my-project
GOOGLE_APPLICATION_CREDENTIALS=/var/secrets/gcp-sa.json

2. Bind a single client per service in a service provider. Clients are expensive to construct (they hold a gRPC/HTTP channel), so always bind them as singletons:

// app/Providers/MonitoringServiceProvider.php
use Google\Cloud\Monitoring\V3\Client\AlertPolicyServiceClient;
use Google\Cloud\Monitoring\V3\Client\MetricServiceClient;

public function register(): void
{
    $this->app->singleton(MetricServiceClient::class, fn () => new MetricServiceClient([
        'logger' => Log::channel('stack'), // optional, useful for wire-level debugging
    ]));

    $this->app->singleton(AlertPolicyServiceClient::class, fn () => new AlertPolicyServiceClient());
}

3. Where to look first. Open the generated Google\Cloud\Monitoring\V3\Client\*Client classes — they are the documentation. Each public method mirrors a REST method and takes a typed request message. The Google\Cloud\Monitoring\V3\* message classes tell you exactly which fields exist (e.g. AlertPolicy, TimeSeries, UptimeCheckConfig). The official Cloud Monitoring docs explain the semantics (metric types, filter syntax, aggregation) that the PHP classes don't.

4. First useful call: enumerate what already exists. Before writing anything, list alert policies and metric descriptors so you know the resource names and metric types you're dealing with.

use Google\Cloud\Monitoring\V3\Client\AlertPolicyServiceClient;
use Google\Cloud\Monitoring\V3\ListAlertPoliciesRequest;

$client = app(AlertPolicyServiceClient::class);

$request = (new ListAlertPoliciesRequest())
    ->setName(AlertPolicyServiceClient::projectName(config('services.gcp.project_id')));

foreach ($client->listAlertPolicies($request)->iterateAllElements() as $policy) {
    logger()->info($policy->getName(), [
        'display_name' => $policy->getDisplayName(),
        'enabled'      => $policy->getEnabled()?->getValue(),
    ]);
}

Implementation Patterns

Pattern 1 — Emit custom metrics from a middleware or queue listener

The highest-value day-to-day use is writing your own time series: request latency, job duration, queue depth, business counters. Keep the write path in one small service and call it from middleware / JobProcessed / JobFailed listeners.

use Google\Cloud\Monitoring\V3\Client\MetricServiceClient;
use Google\Cloud\Monitoring\V3\CreateTimeSeriesRequest;
use Google\Cloud\Monitoring\V3\{TimeSeries, Metric, MonitoredResource, Point, TimeInterval, TypedValue};
use Google\Protobuf\Timestamp;

final class MetricWriter
{
    public function __construct(private MetricServiceClient $client) {}

    public function gauge(string $type, float $value, array $labels = []): void
    {
        $project = config('services.gcp.project_id');
        $now = new Timestamp(['seconds' => time()]);

        $series = (new TimeSeries())
            ->setMetric((new Metric())->setType($type)->setLabels($labels))
            ->setResource((new MonitoredResource())
                ->setType('global')
                ->setLabels(['project_id' => $project]))
            ->setPoints([
                (new Point())
                    ->setInterval((new TimeInterval())->setEndTime($now))
                    ->setValue((new TypedValue())->setDoubleValue($value)),
            ]);

        $this->client->createTimeSeries(
            (new CreateTimeSeriesRequest())
                ->setName(MetricServiceClient::projectName($project))
                ->setTimeSeries([$series])
        );
    }
}

Buffer writes: setTimeSeries() accepts an array, so collect many points in a terminable middleware or a queued job and flush in one call rather than one call per HTTP request.

Pattern 2 — Health/quota endpoint that reads metrics back

Combine listTimeSeries with an aggregation to produce a compact number for an internal dashboard or a /health/metrics route.

use Google\Cloud\Monitoring\V3\{ListTimeSeriesRequest, TimeInterval, Aggregation};
use Google\Cloud\Monitoring\V3\Aggregation\Aligner;
use Google\Cloud\Monitoring\V3\Aggregation\Reducer;
use Google\Protobuf\Duration;

$request = (new ListTimeSeriesRequest())
    ->setName(MetricServiceClient::projectName($project))
    ->setFilter('metric.type = "custom.googleapis.com/laravel/job_duration_ms" AND resource.type = "global"')
    ->setInterval((new TimeInterval())
        ->setStartTime(new Timestamp(['seconds' => time() - 3600]))
        ->setEndTime(new Timestamp(['seconds' => time()])))
    ->setAggregation((new Aggregation())
        ->setAlignmentPeriod(new Duration(['seconds' => 60]))
        ->setPerSeriesAligner(Aligner::ALIGN_MEAN)
        ->setCrossSeriesReducer(Reducer::REDUCE_MEAN)
        ->setGroupByFields(['metric.label.queue']));

foreach ($client->listTimeSeries($request)->iterateAllElements() as $series) {
    foreach ($series->getPoints() as $point) {
        $value = $point->getValue()->getDoubleValue();
    }
}

Pattern 3 — Alert policies as code

Treat alert policies like migrations: define them in config/monitoring.php, then reconcile with an Artisan command (monitoring:sync-alerts). Match on displayName to decide create vs. update.

$policy = (new AlertPolicy())
    ->setDisplayName('Laravel queue backlog high')
    ->setCombiner(AlertPolicy\ConditionCombinerType::OR)
    ->setConditions([$conditionThreshold])
    ->setNotificationChannels($channelResourceNames)
    ->setEnabled(new BoolValue(['value' => true]));

// Create
$client->createAlertPolicy((new CreateAlertPolicyRequest())
    ->setName(AlertPolicyServiceClient::projectName($project))
    ->setAlertPolicy($policy));

// Update — field mask is mandatory
$client->updateAlertPolicy((new UpdateAlertPolicyRequest())
    ->setAlertPolicy($policy)
    ->setUpdateMask(new FieldMask(['paths' => ['display_name', 'conditions', 'notification_channels']])));

Notification channels are separate resources (NotificationChannelServiceClient); create them once (email, webhook, PagerDuty) and reference them by the projects/{p}/notificationChannels/{id} string in every policy.

Pattern 4 — Operational helpers

  • Uptime checks (UptimeCheckServiceClient) for the public endpoints of each environment; keep the httpCheck path pointed at a Laravel health route.
  • Snoozes (SnoozeServiceClient) triggered from your deploy job so deploys don't page anyone. Snoozes accept a filter matching the policies to suppress.
  • Dashboards live in a different namespace — Google\Cloud\Monitoring\Dashboard\V1\Client\DashboardsServiceClient — even though everything else is ...\Monitoring\V3. Don't waste an hour looking for it in V3.

Pattern 5 — Testable wrapper

The generated clients are final-ish, network-bound, and take protobuf messages, which makes them painful to mock directly. Hide them behind your own narrow interfaces (MetricWriterInterface, AlertPolicyRepositoryInterface) and swap in an in-memory fake in tests. Assert on argument objects with Mockery::on() rather than on return values.

Gotchas and Tips

Package version ≠ API version. The Composer package is 2.x, but the classes live in the V3 namespace because the underlying API is v3. Old V1/V2 surfaces still exist and are deprecated. Always import from Google\Cloud\Monitoring\V3\Client\*.

Create the metric descriptor first. createTimeSeries against a custom.googleapis.com/... type without a descriptor is auto-created in some cases but is documented as discouraged — it produces inconsistent value types. Call createMetricDescriptor() once (in a seeder/Artisan command), and make sure the descriptor's metricKind and valueType match what you write (INT64 vs DOUBLE mismatches are a classic INVALID_ARGUMENT).

Don't put a TypedValue type that contradicts the descriptor, and don't forget resource.labels. A global resource requires a project_id label. Wrong or missing resource labels cause silent drops or 400s.

Protobuf wrappers versus scalars. Some fields are proto3 BoolValue wrappers (AlertPolicy.enabled) — you must pass new BoolValue(['value' => true]) and read ->getValue(). Others are plain scalars. If a setter rejects true, it wants a wrapper.

Timestamps and durations are protobuf messages, not Carbon. Use new Google\Protobuf\Timestamp(['seconds' => $carbon->getTimestamp()]) and new Google\Protobuf\Duration(['seconds' => 60]). Points older than roughly 25 hours, or in the future, are rejected.

Update needs a field mask. updateAlertPolicy without setUpdateMask() will reset fields you didn't send. Paths are snake_case (display_name, notification_channels) even though the PHP setters are camelCase.

Filter strings are a DSL, not SQL. Quote metric types with double quotes, chain with AND, and remember label paths are metric.label.x / resource.label.x. Build them with sprintf and escape user input — a stray quote silently returns zero series rather than an error.

Aggregation is all-or-nothing. perSeriesAligner, crossSeriesReducer, and alignmentPeriod work together; specifying a reducer without an aligner returns errors, and alignment periods below 60s are rejected for most metric kinds.

Quotas and cost. Writes are billed per time series ingested; per-request emission of high-cardinality labels (user ID, request ID, URL with IDs) is the fastest way to blow up spend and hit descriptor/cardinality limits. Keep label sets small and bounded. Project-level limits also apply to alert policies and notification channels — reconcile, don't blindly create.

Use iterateAllElements() / iteratePages(). listTimeSeries and listAlertPolicies are paginated; ->getIterator() on the raw response will only give you the first page.

Handle ApiException explicitly and log getStatus() plus getMetadata() — the metadata carries the field-level reason that the message usually omits.

try {
    $client->createTimeSeries($request);
} catch (ApiException $e) {
    Log::warning('Monitoring write failed', ['status' => $e->getStatus(), 'meta' => $e->getMetadata()]);
}

Transport and debugging. REST is used when the grpc extension is absent; gRPC is faster for streaming/high-volume reads. Pass ['transport' => 'rest'] in ClientOptions to debug with ordinary HTTP tooling, and pass a logger to see request/response pairs. Note that the credentials client option is deprecated since v2.2.0 — prefer ADC plus GOOGLE_APPLICATION_CREDENTIALS, or credentialsConfig where you genuinely need to inject a key path.

Extension point: none. There is no framework hook to customize. Everything you want (batching, sampling, circuit-breaking around GCP outages, tenant labels) must live in your own wrapper class — which is also the seam that keeps your app running when Monitoring is down. Never let a metrics write throw into a user request path; wrap it in rescue() or dispatch it to the queue.

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
cgv/localized-resource