Verified12 commandsAI-assisted

Logging, Monitoring & Governance

.md

Verified against Google Cloud SDK 553.0.0, flags verified via `gcloud logging read --help`, `gcloud · official docs

What it is and where it fits 🎯#

This page bundles two things that share an audience more than a mechanism: observability (Cloud Logging, Cloud Monitoring, Error Reporting, what's happening in your workloads right now) and governance (Organization Policy, folders, billing, Cloud Asset Inventory, what's allowed to exist and who's paying for it across an entire organization). Both are the operator's-eye-view layer sitting above the individual services covered on pages 02-05, the same combined "monitoring and governance" split this site's Azure CLI page 04 uses. This page assumes the project/org/IAM hierarchy diagram from page 01; a folder or organization referenced below is the same resource hierarchy introduced there.

Core concepts: where an org policy actually applies#

Diagram

An Organization Policy set at the organization node is inherited by every folder and project underneath it, the same inheritance shape IAM role bindings have on page 01. Unlike most IAM bindings, a policy can be narrowed or overridden at a lower node (a sandbox folder explicitly allowing something the org otherwise denies), which is the deliberate design for "safe by default, opt out where genuinely needed" rather than "deny with no escape hatch."

Cloud Logging: reading log entries#

gcloud logging read "resource.type=gce_instance AND severity>=ERROR" --limit=20 --freshness=1d
gcloud logging read "resource.type=k8s_container AND resource.labels.namespace_name=default" --limit=50
gcloud logging read 'timestamp>="2026-09-17T00:00:00Z"' --project=my-project-id --format=json

The filter syntax is the same Cloud Logging query language the Console's own Logs Explorer uses, worth learning once since it transfers directly between the CLI and the UI. --freshness (default 1d) bounds how far back gcloud searches before giving up, worth widening for an incident investigation into something that happened further back than a day ago, and narrowing for a tight, fast query against very recent activity.

Cloud Logging: sinks (routing logs somewhere else)#

gcloud logging sinks create audit-to-bigquery \
  bigquery.googleapis.com/projects/my-project-id/datasets/audit_logs \
  --log-filter='protoPayload.serviceName="iam.googleapis.com"' \
  --project=my-project-id

gcloud logging sinks create org-security-logs \
  storage.googleapis.com/security-log-archive \
  --organization=<organization-id> --include-children \
  --log-filter='severity>=WARNING'

gcloud logging sinks list --project=my-project-id
gcloud logging sinks describe audit-to-bigquery --project=my-project-id --format="value(writerIdentity)"

A sink routes logs matching its filter to BigQuery, Cloud Storage, or Pub/Sub, useful for long-term retention beyond Cloud Logging's own retention window, or for feeding a SIEM. --include-children on an org- or folder-level sink is what actually makes it apply to every project underneath, an org-level sink created without it captures only organization-level log entries themselves, not the projects' logs, a genuinely easy mistake to make. sinks describe's writerIdentity field is the service account the sink publishes as, that identity needs write access on the destination (a BigQuery dataset, a bucket) or the sink silently fails to deliver anything.

Cloud Logging: logs-based metrics#

gcloud logging metrics create failed-login-count \
  --description="Count of failed login attempts" \
  --log-filter='jsonPayload.event="login_failed"'

gcloud logging metrics list
gcloud logging metrics describe failed-login-count

A logs-based metric turns a log filter into a real Cloud Monitoring metric, counting matching entries over time, which can then back an alerting policy the same way any other Monitoring metric can. This is the bridge between "I can see this in the logs" and "I get paged when this happens too often", without needing the application itself to emit a custom metric for something already visible in its logs.

Cloud Monitoring: alerting policies#

gcloud monitoring policies create \
  --display-name="High error rate: checkout-api" \
  --condition-display-name="5xx rate > 5%" \
  --condition-filter='resource.type="cloud_run_revision" AND resource.labels.service_name="checkout-api" AND metric.type="run.googleapis.com/request_count" AND metric.labels.response_code_class="5xx"' \
  --aggregation='{"alignmentPeriod": "300s", "perSeriesAligner": "ALIGN_RATE"}' \
  --duration=300s --if=any --notification-channels=<channel-id>

gcloud monitoring policies list
gcloud monitoring policies describe <policy-id>

--condition-filter is a Monitoring Query Language filter identifying the exact metric/resource combination to watch, --duration is how long the condition must hold true before the policy actually fires (avoiding a single noisy blip triggering a page), and --notification-channels is where the alert actually goes, which has to already exist as a channel, created separately below.

Cloud Monitoring: notification channels, dashboards, and uptime checks#

gcloud beta monitoring channels create \
  --display-name="Platform team PagerDuty" --type=pagerduty \
  --channel-labels=service_key=<pagerduty-integration-key>

gcloud monitoring dashboards create --config-from-file=checkout-dashboard.json

gcloud monitoring uptime create checkout-api-uptime \
  --resource-type=uptime-url --resource-labels=host=checkout.example.com \
  --protocol=https --path=/healthz --period=60

Note

gcloud monitoring channels create is currently only available under alpha/beta release tracks (not yet GA at the time of writing), confirmed against a real gcloud monitoring channels invocation returning "available in one or more alternate release tracks." gcloud monitoring policies create, gcloud monitoring dashboards create, and gcloud monitoring uptime create are all GA, no prefix needed. Run gcloud components install beta first if beta monitoring channels reports a missing component.

A dashboard's --config-from-file takes the same JSON layout the Console's "Dashboard JSON editor" exports, the practical workflow is usually build one visually in the Console once, export it, then check that JSON into version control and manage it via this command from then on rather than hand-writing the layout.

Error Reporting#

gcloud beta error-reporting events list --project=my-project-id --limit=20
gcloud beta error-reporting events list --service=checkout-api --limit=20

Error Reporting automatically groups and surfaces exceptions from Cloud Logging entries that look like stack traces, no separate SDK integration required for most runtimes. Its gcloud surface is currently beta-only and read-focused, for anything beyond listing recent grouped errors, the Console's Error Reporting view or the underlying API directly is the more complete interface.

Organizations, folders, and billing#

gcloud organizations list
gcloud organizations describe <organization-id>

gcloud resource-manager folders create --display-name=engineering --organization=<organization-id>
gcloud resource-manager folders list --organization=<organization-id>
gcloud resource-manager folders move <folder-id> --organization=<organization-id>

gcloud billing accounts list
gcloud billing projects link my-project-id --billing-account=<billing-account-id>
gcloud billing projects describe my-project-id --format="value(billingEnabled,billingAccountName)"

Folders exist purely for organizing projects (and other folders) under shared IAM/policy inheritance, per the core-concepts diagram, they hold no resources of their own. A project with billingEnabled: false is the single most common reason a brand-new project can't create billable resources at all, billing projects describe is the fast way to confirm that before spending time debugging what looks like a permissions error but is actually a missing billing link.

Organization Policy: constraining what's allowed#

cat > deny-public-ip.yaml <<'EOF'
name: projects/my-project-id/policies/compute.vmExternalIpAccess
spec:
  rules:
  - denyAll: true
EOF
gcloud org-policies set-policy deny-public-ip.yaml

gcloud org-policies describe compute.vmExternalIpAccess --project=my-project-id --effective
gcloud org-policies delete compute.vmExternalIpAccess --project=my-project-id
# The older, simpler form for a boolean (on/off) constraint — still valid, less flexible than the YAML form
gcloud resource-manager org-policies enable-enforce compute.disableSerialPortAccess --project=my-project-id

org-policies set-policy (no resource-manager prefix) is the current API surface, taking a full YAML/JSON policy spec that supports allow lists, deny lists, and conditional rules; resource-manager org-policies enable-enforce/disable-enforce is the older, boolean-constraint-only shortcut, still functional but unable to express anything beyond a flat on/off. --effective on describe shows the policy actually in force after inheritance from any parent folder/org is applied, which can differ from what's set directly on the project itself, exactly the override shape in the core-concepts diagram.

Important

A newly-created project inherits every org policy already set above it in the hierarchy, immediately, with no grace period. A team that provisions a project expecting to configure it freely before any guardrails apply is a common source of "why is this VM creation being denied, I haven't set any policy on this project" confusion, the policy was never on the project, it's inherited from the folder or organization the project was created under.

Cloud Asset Inventory: searching resources across a whole scope#

gcloud asset search-all-resources \
  --scope=organizations/<organization-id> \
  --asset-types=compute.googleapis.com/Instance \
  --query="state:RUNNING"

gcloud asset search-all-iam-policies \
  --scope=organizations/<organization-id> \
  --query="policy:roles/owner"

search-all-resources/search-all-iam-policies query a maintained inventory rather than calling every service's own list API across every project live, meaningfully faster for an org-wide question ("every running Compute instance across all 40 projects", "every IAM binding granting roles/owner anywhere in the org") than scripting a per-project loop over gcloud compute instances list. Both require the cloudasset.assets.searchAll* permission on the scope queried, typically granted at the organization level to a platform/security team rather than per-project.

Real-world scenario: org-wide public-IP lockdown with a documented sandbox exception#

A platform team needs to deny public IPs on Compute instances org-wide, except in an explicitly designated sandbox folder where experimentation needs unrestricted internet-facing test VMs:

cat > deny-public-ip-org.yaml <<'EOF'
name: organizations/<organization-id>/policies/compute.vmExternalIpAccess
spec:
  rules:
  - denyAll: true
EOF
gcloud org-policies set-policy deny-public-ip-org.yaml

cat > allow-public-ip-sandbox.yaml <<'EOF'
name: folders/<sandbox-folder-id>/policies/compute.vmExternalIpAccess
spec:
  rules:
  - allowAll: true
EOF
gcloud org-policies set-policy allow-public-ip-sandbox.yaml

gcloud org-policies describe compute.vmExternalIpAccess --folder=<sandbox-folder-id> --effective

The final describe --effective call confirms the override actually took, rather than trusting that setting the folder-level policy alone was sufficient, worth doing before telling the sandbox team it's safe to proceed.

Real-world scenario: incident response — finding every resource an over-privileged service account touched#

A service account was discovered with an unexpectedly broad roles/editor binding at the organization level; the incident response team needs to scope the actual blast radius before revoking it:

  • gcloud asset search-all-iam-policies --scope=organizations/<org-id> --query="policy:serviceAccount:<sa-email>" to confirm every resource where that identity holds a binding, not just the one that was found
  • gcloud logging read 'protoPayload.authenticationInfo.principalEmail="<sa-email>"' --organization=<org-id> --freshness=30d to see what it actually did with that access, not just what it was theoretically allowed to do
  • Narrow or revoke the binding only after both queries complete, revoking first risks losing the audit trail needed to fully understand the exposure
  • File a logs-based metric + alerting policy for future bindings matching the same overly-broad pattern, so the next occurrence pages someone instead of being found by accident

CI/CD integration recipe: failing a pipeline on a new org-policy violation#

# .github/workflows/policy-check.yml
name: Org policy compliance check
on:
  pull_request:
    paths: ["terraform/**"]

permissions:
  id-token: write
  contents: read

jobs:
  policy-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/github-pool/providers/github-provider
          service_account: policy-checker@my-project-id.iam.gserviceaccount.com
      - uses: google-github-actions/setup-gcloud@v2
      - run: |
          gcloud asset search-all-resources --scope=projects/my-project-id \
            --asset-types=compute.googleapis.com/Instance --query="state:RUNNING" \
            --format="value(name)" | tee running-instances.txt
          echo "Instance count: $(wc -l < running-instances.txt)"

A pipeline like this is a starting point for a broader policy-as-code gate, real enforcement of Terraform plans against org policy typically layers a dedicated tool (Conftest/OPA, covered on this site's security & compliance cheat sheets) on top of gcloud's own read commands rather than scripting the comparison logic by hand.

Common pitfalls#

  • Creating an org/folder-level log sink without --include-children. It captures only that node's own log entries, not the projects underneath it, a near-empty sink is the usual symptom.
  • Assuming a project starts with no org policy applied. It inherits everything set above it immediately, see the IMPORTANT callout, describe --effective is how to see what's actually in force.
  • Forgetting a dead sink's writerIdentity needs its own IAM grant on the destination. A sink that exists but was never granted write access on its BigQuery dataset/bucket fails silently, logged nowhere a casual look would find.
  • Reaching for resource-manager org-policies enable-enforce when a conditional or list-based constraint is actually needed. It only expresses a flat boolean; use org-policies set-policy with a full YAML spec for anything more nuanced.
  • Provisioning a project with no billing account linked and assuming resource creation failures are a permissions problem. Check billing projects describe first.

Exit codes#

0 success, non-zero on any API/validation error, an org-policy violation encountered while creating a resource (an org policy denying public IPs, say) surfaces as a normal non-zero exit from the resource creation command itself (gcloud compute instances create), not from any command on this page, the denial happens at the point something violates the policy, not when the policy is set.

When to reach for something else#

For declarative, reviewable org-policy, folder, and billing-link provisioning across an entire organization, prefer Terraform's google_org_policy_policy/google_folder/google_billing_project_info resources over a growing shell script of the commands on this page, consistent with the IaC guidance on every earlier page. For a full-featured SIEM or long-term security analytics beyond what a logs-based metric and a BigQuery sink can reasonably cover, export logs into a dedicated security platform rather than trying to make Cloud Logging alone serve that role.