Python'dan Cloud Logging'e Yapılı Log Göndermek
Selamlar, bu yazımda Python uygulamasından Google Cloud Logging'e yapılı (structured) JSON log akıtmayı baştan sona bakacağız. Logging sade bir konu gibi görünür, ta ki gece yarısı bir incident'a kadar; o noktada düz string log'lar regex'le boğuşturur, JSON'a geçtiğinde ise filtreleri saniyede yazarsın. Hadi başlayalım.
Neden yapılı log?
Düz metin log'ta 'Kullanici 123 icin odeme basarisiz' yazdığında, bunu ileride bulmak için tekrar metni taramak zorundasın. Onun yerine {"action": "payment", "user_id": "123", "result": "failed"} yazarsan, Cloud Logging'in Logs Explorer'ında jsonPayload.user_id="123" AND jsonPayload.result="failed" filtresiyle direkt yakalarsın. Bu kadar basit bir farkla incident response süresi yarıya iner. Bence Python projelerinde gerçekten en küçük yatırımla en büyük geri dönüşü veren işlerden biri.
Kurulum
Önce Cloud Logging client kütüphanesini kuruyoruz:
pip install google-cloud-logging
Kimlik doğrulaması için ya GOOGLE_APPLICATION_CREDENTIALS environment variable'ını service account JSON'una yönlendirin, ya da GKE/Cloud Run gibi Workload Identity destekleyen bir ortamdaysanız hiçbir şey yapmanıza gerek yok, client otomatik bulur.
Standart logging modülüyle entegrasyon
En temiz yol, Python'un kendi logging modülüne bir handler bağlamak. Tek satırla halloluyor:
import google.cloud.logging
import logging
client = google.cloud.logging.Client()
client.setup_logging()
logger = logging.getLogger('orders')
logger.info({
'action': 'order_completed',
'order_id': 'ORD-789',
'total': 159.99,
'currency': 'TRY',
})
setup_logging() root logger'a Cloud Logging handler'ını ekler; ondan sonra üçüncü parti kütüphanelerin log'ları bile GCP'ye akar. logger.info çağrısına string yerine dict verdiğimizde Cloud Logging bunu jsonPayload alanı olarak saklar. Severity mapping da otomatik: INFO -> INFO, WARNING -> WARNING, ERROR -> ERROR şeklinde Python seviyeleri Cloud Logging severity'lerine birebir eşleniyor.
Client'i dogrudan kullanmak
Daha fazla kontrol istediğinizde, standart logging'i atlayıp client'i doğrudan kullanabilirsiniz. log_struct ile severity ve labels'i tek çağrıda verirsiniz:
from google.cloud import logging as cloud_logging
client = cloud_logging.Client()
gcp_logger = client.logger('payment-service')
gcp_logger.log_struct(
{
'event': 'rate_limit_approaching',
'current_rate': 950,
'limit': 1000,
},
severity='WARNING',
labels={
'environment': 'production',
'service': 'payment-api',
},
)
Labels filter'da resource boyutunda ayrım yapmak için çok işe yarıyor. Servis adını her log'a label olarak koyarsanız, sonradan tek bir servisin tüm log'larını çekmek tek tıkla oluyor.
Trace ile korelasyon
Mikroservis mimarisinde tek bir kullanıcı isteği birkaç servisten geçer; bunların log'larını aynı kovada görmek istersiniz. Cloud Logging logging.googleapis.com/trace alanını gördüğünde aynı trace id'ye sahip kayıtları otomatik gruplar:
def handle_request(request):
trace_header = request.headers.get('X-Cloud-Trace-Context', '')
trace_id = trace_header.split('/')[0] if trace_header else None
trace = f'projects/{client.project}/traces/{trace_id}' if trace_id else None
logger.info(
{'action': 'request_start', 'path': request.path},
extra={'json_fields': {'logging.googleapis.com/trace': trace}},
)
Cloud Run, GKE Ingress veya Load Balancer arkasındaysanız X-Cloud-Trace-Context header'ı ücretsiz geliyor, sadece okumanız yeter.
Cloud Run'da kisayol
Cloud Run veya GKE üzerindeysek aslında client kütüphanesine bile gerek yok. stdout'a JSON yazmak yeterli, log agent geri kalanını halleder:
import json, sys
def log_json(severity, message, **kwargs):
print(json.dumps({'severity': severity, 'message': message, **kwargs}), flush=True)
log_json('ERROR', 'db timeout', database='orders', timeout_ms=30000)
Şahsi kanaatim, Cloud Run kullanıyorsanız bu yolu tercih edin: bağımlılık azalır, soğuk başlangıç hızlanır, sonuç aynı.
Performans tarafi
setup_logging() arka planda BackgroundThreadTransport kullanıyor; yani log'lar asenkron olarak batch halinde gönderiliyor, request thread'ini bloklamıyor. Yüksek trafikte bile genelde bu varsayılan iş görür. Çok yoğun bir job çalıştırıyorsanız client.setup_logging(log_level=logging.INFO) ile DEBUG kayıtlarını filtreleyin, aksi halde transport kuyruğu şişer.
Sik karsilasilan tuzaklar
- Severity'yi unutmak: stdout'a JSON atarken
severityalanını koymazsanız her şeyDEFAULTolarak gelir, alarm kurmak zorlaşır. - PII'yi log'a sokmak: structured log filtre kolaylığı verir ama aynı kolaylık bir veri sızıntısında size geri döner.
password,token, kart numarası alanlarını mask'leyin. - Senkron yazma: kendi handler'ınızı yazıp her log'da HTTP çağrısı yapmak request latency'sini bitirir; client'in batch transport'unu kullanın.
- Trace id'yi sabit tutmamak: aynı request içinde trace değişirse korelasyon dağılır. Trace'i request scope'unda bir context variable'a yazın.
Kapanis
Bu yazıda Python'dan Cloud Logging'e yapılı log göndermenin pratik yollarını gördük: setup_logging() ile en hızlı entegrasyon, log_struct ile tam kontrol, Cloud Run'da stdout JSON ile sıfır bağımlılık. Bence çoğu proje için ilk yol fazlasıyla yeter, diğerlerine ihtiyacınız olduğunda zaten farkına varırsınız. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
