Logging Query Language & Debug Patterns
Tại sao LQL là kỹ năng không thể thiếu
Logs Explorer là công cụ bạn dùng mỗi ngày khi có incident. Trong những moment đó, tốc độ tìm ra root cause phụ thuộc trực tiếp vào khả năng viết query nhanh và chính xác.
Logging Query Language (LQL) không phải SQL, không phải regex, không phải Prometheus PromQL — nó có syntax riêng với semantics đặc thù. Hiểu đúng cách indexing hoạt động, tại sao một query nhanh hơn query khác, và cách dùng các hàm đặc biệt như sample() và source() là sự khác biệt giữa tìm được nguyên nhân trong 5 phút hay 50 phút.
Internal Model: Cách LQL Evaluate Queries
Index và non-indexed fields
Cloud Logging index một số fields nhất định để tăng tốc query. Khi bạn filter theo indexed field, query chạy nhanh hơn và tốn ít resource hơn.
Fields được index (fast lookup):
resource.type— quan trọng nhất, luôn filter theo field này khi có thểresource.labels.*— tất cả resource label keyslogName— tên log (ví dụ:cloudaudit.googleapis.com/activity)severity— log severity leveltimestamp— luôn được index (range scan)insertId— unique entry ID
Fields KHÔNG được index (full scan):
textPayload— plain text payloadjsonPayload.*(hầu hết subfields) — JSON fields thường không được indexlabels.*— custom labels không được index trừ một số trường hợp
Hệ quả thực tế: Query resource.type="k8s_container" AND jsonPayload.level="ERROR" sẽ chạy nhanh hơn đáng kể so với jsonPayload.level="ERROR" không có resource filter, vì phần resource.type sử dụng index để thu hẹp tập entry cần scan.
Cú Pháp Cơ Bản LQL
Field paths và toán tử
# Equality
resource.type = "gce_instance"
severity = "ERROR"
# Inequality
severity != "DEBUG"
httpRequest.status != 200
# Comparison (numeric)
httpRequest.status >= 500
httpRequest.status < 600
# Contains (substring match)
jsonPayload.message : "connection refused"
# Regex match
jsonPayload.message =~ "timeout|TIMEOUT|Timeout"
# Prefix match
logName =~ "projects/my-project/logs/.*"Toán tử : vs =: Đây là điểm gây nhầm lẫn nhiều nhất.
field = "value"— exact equality matchfield : "value"— contains match (field chứa chuỗi con "value")
Với string fields như jsonPayload.message:
jsonPayload.message = "connection refused"— chỉ match khi message chính xác là "connection refused"jsonPayload.message : "connection refused"— match khi message chứa "connection refused"
Trong thực tế, : được dùng nhiều hơn cho text search. = dùng cho categorical values.
Boolean logic
# AND (implicit — bởi newline hoặc space)
resource.type = "k8s_container"
severity = "ERROR"
# Explicit AND
resource.type = "k8s_container" AND severity = "ERROR"
# OR
severity = "ERROR" OR severity = "CRITICAL"
# NOT
NOT severity = "DEBUG"
# Grouping
(severity = "ERROR" OR severity = "CRITICAL") AND resource.type = "k8s_container"Thứ tự ưu tiên: NOT > AND > OR. Luôn dùng ngoặc khi kết hợp AND và OR để tránh nhầm lẫn.
Kiểm tra existence
# Field tồn tại (có giá trị khác null)
httpRequest.requestUrl IS NOT NULL
# Field không tồn tại
NOT httpRequest.requestUrl IS NOT NULL
# Tương đương với cách ngắn hơn
httpRequest.requestUrl:"" # Match mọi entry có httpRequest.requestUrlHàm Đặc Biệt Trong LQL
log_id() — filter theo log name
# Chỉ audit activity logs
log_id("cloudaudit.googleapis.com/activity")
# Container logs từ Kubernetes
log_id("stdout") OR log_id("stderr")
# GKE system logs
log_id("events")log_id(name) tương đương với logName = ".../<name>" nhưng ngắn hơn và không cần biết full path.
source() — filter theo resource hierarchy
# Tất cả logs từ folder Engineering và children
source("folders/123456789")
# Tất cả logs từ org
source("organizations/987654321")
# Logs từ project cụ thể
source("projects/my-project")source() đặc biệt hữu ích khi query từ aggregated log bucket hoặc từ Log Analytics trên centralized bucket.
sample() — random sampling
# Giữ 10% entries ngẫu nhiên (deterministic dựa trên insertId)
sample(insertId, 0.1)
# Kết hợp với filter — lấy 5% error logs
severity = "ERROR" AND sample(insertId, 0.05)sample() dùng hash của field được chỉ định để chọn ngẫu nhiên. Vì dùng hash, cùng entry sẽ luôn có cùng kết quả — nếu entry A được giữ với sample(insertId, 0.1), nó luôn được giữ với sampling rate đó.
Hữu ích khi muốn reduce cost của exclusion filter mà vẫn giữ statistical sample.
SEARCH() — full-text search
# Tìm entries có text "OOM" ở bất kỳ đâu
SEARCH("OOM")
# Tìm entries có "database" hoặc "connection"
SEARCH("database") OR SEARCH("connection")SEARCH() là full-text search qua tất cả string fields. Chậm hơn filter theo field cụ thể nhưng hữu ích khi không biết log ở field nào.
Kỹ Thuật Correlate Logs Across Services
Trace ID correlation
Cách hiệu quả nhất để correlate logs từ nhiều services là qua trace ID. Khi một request đi qua API Gateway → Service A → Service B → Database, tất cả components có thể share cùng một trace ID.
Với W3C Trace Context (được GCP hỗ trợ natively), log entries có field trace chứa trace ID dạng:
projects/my-project/traces/abc123def456...Query để tìm tất cả logs của một request:
trace = "projects/my-project/traces/abc123def456789012345678901234ab"Trong Logs Explorer, click vào trace ID link sẽ tự động filter tất cả log entries liên quan.
Nếu application không propagate trace context: Có thể dùng custom request ID. Log entry cần chứa jsonPayload.request_id = "REQ-12345" và filter:
jsonPayload.request_id = "REQ-12345"Nhưng điều này chỉ work nếu tất cả services đều log request ID theo cùng field name.
Timestamp-based correlation
Khi không có trace context, timestamp range là fallback:
# Tìm tất cả logs quanh thời điểm incident
timestamp >= "2024-03-15T14:00:00Z"
timestamp <= "2024-03-15T14:05:00Z"
severity >= "WARNING"Kết hợp với source() để lấy từ nhiều projects:
source("folders/engineering-folder-id")
timestamp >= "2024-03-15T14:00:00Z"
timestamp <= "2024-03-15T14:05:00Z"
severity >= "ERROR"Correlation qua resource labels
GKE logs có resource labels đồng nhất. Nếu bạn biết Pod name, có thể correlate tất cả logs từ Pod đó:
resource.type = "k8s_container"
resource.labels.pod_name = "payment-service-7f9b8d4-xkp2q"Hoặc correlation theo namespace để xem toàn bộ namespace trong một khoảng thời gian:
resource.type = "k8s_container"
resource.labels.namespace_name = "payment"
timestamp >= "2024-03-15T14:00:00Z"
timestamp <= "2024-03-15T14:05:00Z"Debug Patterns Thực Tế
Pattern 1: Tìm root cause của error spike
Tình huống: Alert báo 5xx rate tăng đột ngột lúc 14:00.
Bước 1: Tìm errors theo resource type
severity >= "ERROR"
timestamp >= "2024-03-15T13:58:00Z"
timestamp <= "2024-03-15T14:05:00Z"Bước 2: Xác định resource type có nhiều errors nhất (dùng Group By trong Logs Explorer hoặc Log Analytics)
Bước 3: Drill down vào service cụ thể
resource.type = "k8s_container"
resource.labels.container_name = "payment-api"
severity >= "ERROR"
timestamp >= "2024-03-15T13:58:00Z"Bước 4: Tìm trace ID của request thất bại, trace sang các services khác
Pattern 2: Debug GKE Pod không start được
# Tìm events liên quan đến pod
resource.type = "k8s_pod"
resource.labels.pod_name =~ "my-app-.*"
resource.labels.namespace_name = "production"
# Hoặc xem events từ kubelet
resource.type = "k8s_node"
jsonPayload.MESSAGE =~ "my-app"Kết hợp với cluster events:
resource.type = "k8s_cluster"
jsonPayload.reason = "Failed"
jsonPayload.involvedObject.name =~ "my-app-.*"Pattern 3: Audit trail cho security investigation
Khi cần biết ai đã làm gì trong một project:
# Tất cả admin actions của một user
log_id("cloudaudit.googleapis.com/activity")
protoPayload.authenticationInfo.principalEmail = "suspicious-user@company.com"
timestamp >= "2024-03-01T00:00:00Z"# Tất cả IAM changes
log_id("cloudaudit.googleapis.com/activity")
protoPayload.methodName =~ "setIamPolicy|modifyPolicy"# Secret Manager access (cần Data Access Audit Logs bật)
log_id("cloudaudit.googleapis.com/data_access")
resource.type = "secretmanager.googleapis.com/Secret"
protoPayload.authenticationInfo.principalEmail != "service-account@project.iam.gserviceaccount.com"Pattern 4: Tìm memory leak hoặc OOM kill
# OOMKill events từ Kubernetes
resource.type = "k8s_pod"
jsonPayload.reason = "OOMKilling"
# Node pressure eviction
resource.type = "k8s_node"
jsonPayload.message =~ "Evicted|evict"
# Container restart do OOM
resource.type = "k8s_container"
jsonPayload.message =~ "OOM|out of memory"Pattern 5: Database slow queries
# Cloud SQL slow query log
resource.type = "cloudsql_database"
log_id("cloudsql.googleapis.com/mysql-slow.log")
jsonPayload.query_time > 1.0 # queries > 1 giây
# Kết hợp với audit để biết user
log_id("cloudsql.googleapis.com/mysql-slow.log") OR log_id("cloudaudit.googleapis.com/data_access")
resource.type = "cloudsql_database"
resource.labels.database_id = "my-project:my-instance"
timestamp >= "2024-03-15T14:00:00Z"Log Analytics: SQL cho Complex Queries
Khi LQL không đủ mạnh (cần GROUP BY, aggregation, JOIN), Log Analytics cung cấp SQL interface:
-- Top 10 endpoints với error rate cao nhất trong 24 giờ
SELECT
httpRequest.requestUrl,
COUNT(*) as total_requests,
COUNTIF(httpRequest.status >= 500) as error_count,
ROUND(COUNTIF(httpRequest.status >= 500) * 100.0 / COUNT(*), 2) as error_rate_pct
FROM
`my-project._Default._AllLogs`
WHERE
timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 24 HOUR)
AND resource.type = "http_load_balancer"
AND httpRequest.requestUrl IS NOT NULL
GROUP BY
httpRequest.requestUrl
HAVING
total_requests > 100 -- Loại bỏ endpoints ít traffic
ORDER BY
error_rate_pct DESC
LIMIT 10-- Phân tích lỗi theo giờ để tìm pattern
SELECT
EXTRACT(HOUR FROM timestamp AT TIME ZONE "Asia/Ho_Chi_Minh") as hour_vn,
severity,
COUNT(*) as count
FROM
`my-project._Default._AllLogs`
WHERE
DATE(timestamp) = CURRENT_DATE()
AND resource.type = "k8s_container"
AND resource.labels.namespace_name = "production"
GROUP BY
hour_vn, severity
ORDER BY
hour_vn, severityTips Tối Ưu Query Performance
1. Luôn filter theo timestamp range trước
Timestamp là indexed field. Filter theo time range thu hẹp đáng kể số entries cần scan. Không bao giờ query mà không có time range khi đang troubleshoot production.
2. Filter theo resource.type ngay sau timestamp
# Tốt
timestamp >= "2024-03-15T14:00:00Z"
resource.type = "k8s_container"
severity = "ERROR"
# Kém hơn (thiếu resource.type filter)
timestamp >= "2024-03-15T14:00:00Z"
severity = "ERROR"
jsonPayload.component = "payment-service"3. Tránh leading wildcard trong regex
# Chậm: leading wildcard
jsonPayload.message =~ ".*connection refused.*"
# Nhanh hơn: contains match
jsonPayload.message : "connection refused"4. Giới hạn time window trong Log Analytics
Log Analytics query được tính phí theo volume scanned. Với 100 GB logs/ngày và không có time filter, một query có thể scan TB data và tốn tiền.
Luôn dùng:
WHERE timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)hoặc cho Log Analytics bucket:
WHERE DATE(timestamp) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)