> For the complete documentation index, see [llms.txt](https://docs.p0.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.p0.dev/agentic-access/agentic-gateway/log-provider/gcp-cloud-logging.md).

# Google Cloud Logging

Export gateway activity to Google Cloud Logging and let P0 read it from your project.

The **Google Cloud Logging** log provider reads your gateway's MCP and A2A activity from a Google Cloud project. Use it if you already keep your logs in Google Cloud and want the gateway's activity there too. Otherwise, [VictoriaLogs](/agentic-access/agentic-gateway/log-provider/victorialogs.md) needs less setup.

{% hint style="info" %}
The Google Cloud Logging provider is in preview. It may have limited features or support. Contact P0 before you build on it.
{% endhint %}

## How it works

The batteries-included chart from [Install the batteries-included Helm chart](/agentic-access/agentic-gateway/gateway/deploy/install-the-batteries-included-chart.md) runs an OpenTelemetry collector next to the gateway. When you give the collector a Google Cloud project, it writes the gateway's logs to that project's Cloud Logging under the log name `mcp-activity`, and its traces to Cloud Trace.

P0 reads the entries back with the Cloud Logging API, as P0's own platform service account. This is not the service account from your [Google Cloud](/integrations/resource-integrations/google-cloud.md), and not the service account P0 assigned your gateway at install, so granting either of those read access has no effect. P0 reads only entries from the `mcp-activity` log whose host matches a gateway linked to this provider. That keeps each gateway's activity separate even when several gateways write to the same project.

Setup has four parts:

1. Let the collector write to your project.
2. Turn on the Google Cloud exporter in the chart.
3. Let P0 read the project.
4. Add the log provider in P0.

## Prerequisites

* A registered [Gateway](/agentic-access/agentic-gateway/gateway.md) component, deployed with the batteries-included chart or from the P0 console. See [Install the batteries-included Helm chart](/agentic-access/agentic-gateway/gateway/deploy/install-the-batteries-included-chart.md) and [Install from the P0 console](/agentic-access/agentic-gateway/gateway/deploy/install-from-the-p0-console.md).
* A Google Cloud project to hold the logs, with the Cloud Logging API turned on. It doesn't have to be the project your cluster runs in.
* Permission to grant IAM roles in that project, and to create service accounts there if you use a service account key.

## 1. Let the collector write to your project

The collector needs two roles in the log project. `roles/logging.logWriter` lets it write log entries. `roles/cloudtrace.agent` lets it write traces, which it also sends once the exporter is on. Without it, trace exports fail, although logs still arrive.

How you grant them depends on where your cluster runs.

{% tabs %}
{% tab title="GKE with Workload Identity" %}
On a GKE cluster with Workload Identity Federation turned on, grant the roles straight to the collector's Kubernetes service account. You don't need a Google service account, a key, or a service account annotation.

The chart names the collector's Kubernetes service account `<release>-otel-collector`, where `<release>` is your Helm release name. For a gateway installed from the console, the release name is your gateway identifier.

```bash
PROJECT_ID=<log-project-id>
MEMBER="principal://iam.googleapis.com/projects/<cluster-project-number>/locations/global/workloadIdentityPools/<cluster-project-id>.svc.id.goog/subject/ns/<namespace>/sa/<release>-otel-collector"

for ROLE in roles/logging.logWriter roles/cloudtrace.agent; do
  gcloud projects add-iam-policy-binding "$PROJECT_ID" \
    --member "$MEMBER" \
    --role "$ROLE"
done
```

`<cluster-project-id>` and `<cluster-project-number>` identify the project your GKE cluster runs in, which may differ from the log project. `<namespace>` is the gateway's namespace, `p0-agentic-gateway` by default.
{% endtab %}

{% tab title="Service account key" %}
Use a service account key if your cluster runs outside GKE, or on GKE without Workload Identity. The collector then authenticates as a Google service account whose key you store in a Kubernetes Secret.

Many organizations block key creation with the `iam.disableServiceAccountKeyCreation` organization policy. If `keys create` fails with a precondition error, ask your organization admin for an exception on the log project.

Create the service account, grant it the roles, and store its key in a Secret in the gateway's namespace, under the key `credentials.json`. The chart mounts that key into the collector as its Google credentials.

```bash
PROJECT_ID=<log-project-id>
SA="gateway-otel-collector@$PROJECT_ID.iam.gserviceaccount.com"

gcloud iam service-accounts create gateway-otel-collector --project "$PROJECT_ID"

for ROLE in roles/logging.logWriter roles/cloudtrace.agent; do
  gcloud projects add-iam-policy-binding "$PROJECT_ID" \
    --member "serviceAccount:$SA" \
    --role "$ROLE"
done

gcloud iam service-accounts keys create credentials.json --iam-account "$SA"

kubectl -n <namespace> create secret generic gateway-otel-gcp-credentials \
  --from-file=credentials.json

rm credentials.json
```

Create the Secret before you apply step 2. The collector pod can't start while the Secret it mounts is missing. Treat the key like a password: anyone who has it can write logs and traces to your project.
{% endtab %}
{% endtabs %}

## 2. Turn on the Google Cloud exporter

Add these values to the chart.

{% tabs %}
{% tab title="Terraform (P0 console)" %}
The module the P0 console generates writes its values in Terraform syntax, inside `values = [yamlencode({ ... })]`. Add a `collector` block there, and add `observability` to the existing `ai-gateway` block rather than creating a second one:

```hcl
    collector = {
      gcpProjectId = "<log-project-id>"
      # Service account key only
      # gcpCredentialsSecret = "gateway-otel-gcp-credentials"
    }
    ai-gateway = {
      # ...existing gateway, braekhus, authServer, and accessProxy blocks...
      observability = {
        tracesExporter = ""
      }
    }
```

{% endtab %}

{% tab title="Helm" %}
If you installed the chart with `helm`, add these to your values file:

```yaml
collector:
  gcpProjectId: <log-project-id>
  # Service account key only
  # gcpCredentialsSecret: gateway-otel-gcp-credentials

ai-gateway:
  observability:
    tracesExporter: ""
```

{% endtab %}
{% endtabs %}

* `collector.gcpProjectId` turns on the Google Cloud exporter and sets the project it writes to.
* Set `gcpCredentialsSecret` only if you followed the service account key tab in step 1. On GKE with Workload Identity, leave it out: the collector picks up its identity from the cluster.
* `tracesExporter: ""` turns the gateway's trace export back on. The chart turns it off by default, because the collector has nowhere to send traces until you set `gcpProjectId`.

By default, the collector keeps writing to the bundled VictoriaLogs as well. To send logs to Google Cloud only, also turn VictoriaLogs off by setting `victorialogs.enabled` to `false` and `ai-gateway.accessProxy.victorialogsUrl` to `""`. In Helm values:

```yaml
victorialogs:
  enabled: false

ai-gateway:
  accessProxy:
    victorialogsUrl: ""
```

The generated Terraform already has both blocks. Change `enabled = true` to `false` in its `victorialogs` block, and add `victorialogsUrl = ""` to its `accessProxy` block, rather than adding second copies of either block.

Apply the change with `terraform apply`, or with `helm upgrade` if you installed the chart by hand. Then check that the collector restarted and is running:

```bash
kubectl -n <namespace> get pods | grep otel-collector
```

## 3. Let P0 read the project

P0 reads activity as its own platform service account. On [p0.app](https://p0.app), that account is `prod-gke-cluster-sa@p0-prod.iam.gserviceaccount.com`. If your organization uses a dedicated P0 deployment, ask P0 for the account to grant. Grant it read access to the log project:

```bash
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member "serviceAccount:prod-gke-cluster-sa@p0-prod.iam.gserviceaccount.com" \
  --role roles/logging.viewer
```

`roles/logging.viewer` lets P0 list log entries in the project. A custom role works too, as long as it includes `logging.logEntries.list`. Grant the role on the log project itself: P0 names the project directly when it reads, so the grant must apply to that project.

Don't grant this role to the service account from your Google Cloud integration, or to the one P0 assigned your gateway. P0 doesn't use either of them to read Cloud Logging.

## 4. Add the log provider

1. Navigate to **Integrations** on [p0.app](https://p0.app) and select **Agentic gateway**, then choose the **Log provider** component.
2. Click **Add log provider**, and enter a **Log provider identifier**.
3. Set **Provider** to **Google Cloud Logging**.
4. Enter the **GCP log project ID**: the project where the log entries land. This is the `collector.gcpProjectId` from step 2. If a log sink also routes the entries to a central logging project, still enter the project the collector writes to, and keep the entries in that project's `_Default` bucket. P0 doesn't read entries that were routed to another project.
5. Link each gateway that writes to this project to the new provider, as in [Log provider](/agentic-access/agentic-gateway/log-provider.md#link-a-gateway-to-a-log-provider).

## Verify

Make a tool call through the gateway, then look for it in Cloud Logging. The collector writes all of the gateway's logs to `mcp-activity`, including operational logs, so filter to the inbound calls P0 reads and to your gateway's host:

```bash
gcloud logging read 'logName="projects/<log-project-id>/logs/mcp-activity" AND jsonPayload.event.direction="inbound" AND jsonPayload.host="<gateway-host>"' \
  --project <log-project-id> --limit 5
```

`<gateway-host>` is your gateway's hostname, without `https://`. If your tool call appears here, it also appears on the [Event log](/agentic-access/using-the-gateway/event-log.md).

## Troubleshooting

### Nothing reaches Cloud Logging

The collector can't write to the project. Read its log:

```bash
kubectl -n <namespace> logs deploy/<release>-otel-collector
```

A `PermissionDenied` error means the collector's identity is missing `roles/logging.logWriter` in the log project. On GKE, check that the `principal://` member from step 1 names the cluster's project number, the gateway's namespace, and the collector's Kubernetes service account, and that Workload Identity Federation is turned on for the cluster and its node pool. Confirm the service account name with:

```bash
kubectl -n <namespace> get serviceaccount | grep otel-collector
```

With a service account key, check that the roles were granted to the service account the key belongs to.

### Entries reach Cloud Logging, but the Event log shows an error

P0 can't read the project. Check that P0's platform service account from [#id-3.-let-p0-read-the-project](#id-3.-let-p0-read-the-project "mention") has `roles/logging.viewer` in the project you entered as the **GCP log project ID**, and that the ID matches the project the entries land in. A grant to your Google Cloud integration's service account or to the gateway's service account doesn't count: P0 doesn't read Cloud Logging as either of them.

### Entries reach Cloud Logging, but the Event log is empty

* Check that the gateway is linked to this log provider.
* Check that the **GCP log project ID** is the project the collector writes to, not a central project your log sinks route to.
* Check that none of your gateways are linked to a [VictoriaLogs](/agentic-access/agentic-gateway/log-provider/victorialogs.md) provider. P0 reads from one kind of provider at a time, and prefers VictoriaLogs when both are in use. See [Log provider](/agentic-access/agentic-gateway/log-provider.md#how-p0-reads-activity).
