Cloud Logging : le parcours d'une entrée de log
Le gain du jour : savoir lire une entrée de log champ par champ, comprendre où elle va après avoir été écrite, et régler toi-même combien de temps on la garde et combien elle coûte.
Cloud Logging, c’est quoi ?
Cloud Logging est le service de Google Cloud qui reçoit, range et conserve les logs. Un log est une ligne horodatée qui raconte un événement : « requête reçue », « VM créée », « accès refusé ». Chaque ligne stockée s’appelle une entrée de log (log entry).
Tu n’as presque rien à installer. Sur Cloud Run, Cloud Run functions, GKE ou App
Engine, tout ce que ton code écrit sur stdout et stderr part tout seul vers Cloud
Logging. Un simple print() en Python suffit pour produire une entrée.
D’où viennent les logs
Trois familles alimentent ton projet :
| Famille | Qui l’écrit | Exemple |
|---|---|---|
| Logs applicatifs | Ton code, via stdout / stderr ou une bibliothèque cliente | run.googleapis.com/stderr sur Cloud Run |
| Logs de plateforme | Les services Google, sur leur propre fonctionnement | Requêtes HTTP reçues par Cloud Run, logs Cloud SQL |
| Audit logs | Cloud Audit Logs, pour savoir « qui a fait quoi, où, quand » | Quelqu’un a modifié un rôle IAM |
Les audit logs se déclinent en quatre types. Retiens surtout lesquels sont actifs par défaut :
| Type | Ce qu’il enregistre | Peut-on le couper ? |
|---|---|---|
| Admin Activity | Les modifications de configuration (créer une VM, changer un rôle) | Non, toujours écrit |
| System Event | Les actions de Google lui-même sur tes ressources | Non, toujours écrit |
| Data Access | Les lectures de configuration et les accès aux données | Désactivé par défaut (sauf BigQuery) |
| Policy Denied | Les accès refusés par une politique de sécurité | Non désactivable, mais on peut l’exclure du stockage |
L’anatomie d’une entrée : le LogEntry
Toutes les entrées ont la même enveloppe, le type LogEntry. Voici une entrée
écrite par un service Cloud Run :
{
"logName": "projects/mon-projet/logs/run.googleapis.com%2Fstderr",
"resource": {
"type": "cloud_run_revision",
"labels": { "service_name": "api-panier", "location": "europe-west1" }
},
"timestamp": "2026-11-05T09:42:17.318Z",
"receiveTimestamp": "2026-11-05T09:42:17.502Z",
"severity": "ERROR",
"insertId": "6729f1a90004d2b1",
"labels": { "instanceId": "00f4b2…" },
"httpRequest": { "requestMethod": "POST", "status": 500 },
"trace": "projects/mon-projet/traces/4bf92f3577b34da6a3ce929d0e0e4736",
"spanId": "a3ce929d0e0e4736",
"jsonPayload": { "message": "quantity must be >= 0", "userId": "u-042" }
}
logName: le nom complet du log, de la formeprojects/ID/logs/LOG_ID. LeLOG_IDest encodé pour une URL : le/devient%2F.resource: la ressource surveillée (monitored resource) qui a produit l’entrée.resource.typedit quel genre de ressource (cloud_run_revision,k8s_container,gce_instance),resource.labelsdit laquelle.timestamp: le moment où l’événement a eu lieu. C’est lui qui compte pour l’âge de l’entrée et donc pour la rétention.receiveTimestamp: le moment où Cloud Logging l’a reçue. Champ en lecture seule. Un grand écart entre les deux signale un retard d’envoi.severity: le niveau de gravité (voir ci-dessous).insertId: un identifiant unique, utilisé pour dédoublonner les entrées.labels: des paires clé/valeur ajoutées par le système ou par toi.httpRequest: la méthode, l’URL, le statut, la latence d’une requête HTTP.traceetspanId: relient l’entrée à une trace Cloud Trace. Toutes les lignes d’une même requête partagent le mêmetrace.
Le contenu lui-même est dans un seul de ces trois champs :
| Champ | Contenu | Quand tu le vois |
|---|---|---|
textPayload | Une chaîne de texte brut | Un print("hello") classique |
jsonPayload | Un objet JSON libre | Ton code écrit une ligne JSON sur stdout |
protoPayload | Un message protobuf typé (@type) | Les audit logs (AuditLog) |
Un log en jsonPayload est dit structuré. On peut filtrer sur un champ précis
(jsonPayload.userId="u-042") et même l’indexer. Un textPayload se cherche, mais
ne s’indexe pas.
Les niveaux de severity, du plus faible au plus grave, avec leur valeur
numérique : DEFAULT (0), DEBUG (100), INFO (200), NOTICE (300), WARNING
(400), ERROR (500), CRITICAL (600), ALERT (700), EMERGENCY (800). Une
entrée sans niveau reçoit DEFAULT.
Le trajet : du code au bucket
Une entrée ne va pas directement dans un stockage. Elle passe d’abord par le Log Router. On construit le chemin pas à pas.
D’abord, une source écrit une entrée, qui arrive à l’API Cloud Logging :
🐍 Ton code (stdout) ─┐
☁️ Services Google ─┼──▶ 📥 API Cloud Logging
🛡 Cloud Audit Logs ─┘
Ensuite, le Log Router compare l’entrée aux filtres de chaque sink du projet :
Sources ──▶ 📥 API ──▶ 🔀 Log Router
│
├─ sink _Required
├─ sink _Default
└─ sink(s) que tu crées
Enfin, chaque sink dont le filtre correspond envoie une copie vers sa destination :
Sources ──▶ 📥 API ──▶ 🔀 Log Router
│
├─ sink _Required ──▶ 🪣 bucket _Required (400 j, fixe)
├─ sink _Default ──▶ 🪣 bucket _Default (30 j, réglable)
└─ sink "erreurs" ──▶ 📊 BigQuery / 🗄 Cloud Storage /
📨 Pub/Sub / 🪣 autre bucket /
🏢 autre projet
Un sink a trois éléments : une destination, un filtre d’inclusion (vide = tout prendre) et zéro ou plusieurs filtres d’exclusion (jusqu’à 50 par sink). Une entrée qui correspond à plusieurs sinks est copiée dans chacune de leurs destinations.
| Destination | Pour quoi faire |
|---|---|
| Log bucket | Garder et chercher dans le Logs Explorer |
| BigQuery | Analyser en SQL sur de longues périodes |
| Cloud Storage | Archiver pas cher, pour la conformité |
| Pub/Sub | Envoyer à un autre outil (SIEM, Splunk, un service maison) |
| Un autre projet Google Cloud | Centraliser : les sinks de ce projet re-routent les entrées |
Les deux buckets créés pour toi
Chaque projet reçoit deux sinks et deux buckets du même nom :
_Required: Admin Activity, System Event et Access Transparency, entre autres. Rétention 400 jours, non modifiable. Impossible de le supprimer ou de changer ce qu’il reçoit._Default: tout le reste, dont tes logs applicatifs, Data Access et Policy Denied. Rétention 30 jours par défaut, réglable de 1 à 3650 jours. Son sink peut être modifié, recevoir des exclusions, ou être désactivé.
# Voir les buckets et leur rétention (colonne RETENTION_DAYS)
gcloud logging buckets list
# Garder les logs du bucket _Default pendant un an
gcloud logging buckets update _Default --location=global --retention-days=365
# Lister les logs qui contiennent des entrées dans le projet
gcloud logging logs list
Allonger la rétention ne s’applique qu’à l’avenir : une entrée déjà supprimée ne revient pas.
Créer un sink
Exemple : garder les erreurs dans un bucket dédié. On crée le bucket, puis le sink :
gcloud logging buckets create erreurs --location=global
gcloud logging sinks create sink-erreurs \
logging.googleapis.com/projects/mon-projet/locations/global/buckets/erreurs \
--log-filter='severity>=ERROR' \
--description="Copie des erreurs dans le bucket erreurs"
Les options utiles : --log-filter (inclusion), --exclusion (répétable), et
--description. Si la réponse contient un champ writerIdentity, ce compte de
service doit recevoir le droit d’écrire dans la destination (par exemple
roles/bigquery.dataEditor pour BigQuery, roles/pubsub.publisher pour Pub/Sub).
gcloud ne le fait pas pour toi.
Tu paies au volume de logs stocké dans les log buckets, avec une franchise mensuelle par projet (50 GiB historiquement) et un surcoût au-delà de 30 jours de rétention. Le prix exact par GiB : à vérifier sur la page tarifs de Google Cloud Observability.
Le levier principal est l’exclusion : une entrée exclue de tous les sinks n’est
pas stockée. Candidats classiques : les health checks, les logs DEBUG en
production, les Data Access audit logs d’un projet de dev. Les logs du bucket
_Required, eux, ne peuvent pas être exclus. La page Logs Storage de la console
montre le volume du mois et une projection.
Métriques et alertes basées sur les logs
Deux usages vont au-delà de la lecture :
- Métrique basée sur les logs : compte les entrées qui correspondent à un filtre
et en fait une série temporelle dans Cloud Monitoring. Exemple :
gcloud logging metrics create error_count --log-filter="severity>=ERROR". - Alerte basée sur les logs : te prévient dès qu’une entrée correspond à un
filtre, par exemple
httpRequest.status=500sur ton service.
Qui a le droit de faire quoi
| Rôle | Ce qu’il permet |
|---|---|
roles/logging.viewer | Lire les logs, sauf les Data Access audit logs du bucket _Default |
roles/logging.privateLogViewer | Tout ce que fait viewer, plus les Data Access audit logs |
roles/logging.configWriter | Créer et modifier sinks, buckets, vues, métriques basées sur les logs |
roles/logging.admin | Toutes les fonctions de Cloud Logging |
Donne viewer par défaut. Les Data Access logs peuvent révéler qui lit quelles
données : privateLogViewer se réserve à ceux qui en ont besoin.
Vérifie ta compréhension
Ton équipe veut garder les logs de son API pendant 90 jours au lieu de 30. Quelle est la bonne action ?
Une entrée a un timestamp à 09:42:17 et un receiveTimestamp à 09:47:03. Que peux-tu en conclure ?
À toi de jouer
Lance
gcloud logging buckets listsur ton projet et note la rétention de_Requiredet de_Default.Lance
gcloud logging logs listet repère un log applicatif, un log de plateforme et un audit log (cloudaudit.googleapis.com%2Factivity).Dans le Logs Explorer, déplie une entrée et identifie son
resource.type, saseverityet lequel des trois champs*Payloadelle utilise.Écris (sans l’appliquer) le filtre d’exclusion qui retirerait les requêtes de health check de ton service.
🗂 Fiche de référence associée : Google Cloud et Cloud Logging — antisèche.