Structured Logging
Structured Logging and JSON Log Design
Desain structured logging untuk Java/JAX-RS enterprise systems: JSON log schema, standard fields, timestamp, level, logger, service, environment, version, request, actor, tenant, business key, error fields, dependency fields, privacy, queryability, dan review checklist.
Cheatsheet Observability Part 006 — Structured Logging
Tujuan Part Ini
Part ini membahas structured logging sebagai fondasi log yang dapat dicari, dikorelasikan, dipakai di dashboard, dipakai saat incident, dan direview secara konsisten.
Fokusnya adalah desain log schema untuk sistem Java/JAX-RS / Jakarta RESTful Web Services di lingkungan enterprise yang melibatkan:
- HTTP ingress
- JAX-RS resource
- service layer
- PostgreSQL via JDBC/MyBatis/JPA
- Kafka/RabbitMQ
- Redis
- Camunda/workflow
- NGINX/ingress/API gateway
- Docker/Kubernetes
- AWS/Azure/on-prem/hybrid deployment
Setelah membaca part ini, Anda harus mampu:
- membedakan log message dan log event
- mendesain JSON log field yang stabil
- memahami standard fields untuk production logs
- menentukan field teknis, request, trace, actor, tenant, business, dependency, error, dan performance
- menghindari PII leakage dan secret leakage
- menghindari log yang tidak queryable
- memahami kapan field cocok untuk log tetapi tidak cocok untuk metric label
- mereview structured log dalam PR/architecture review
Ringkasan Mental Model
Structured logging berarti log ditulis sebagai data, bukan hanya teks.
Plain text log:
2026-07-11 14:55:12 ERROR Order submission failed for order O-91821 because fulfillment timed out
Structured log:
{
"timestamp": "2026-07-11T14:55:12.432Z",
"level": "ERROR",
"service": "order-service",
"environment": "prod",
"event": "order.submission.failed",
"message": "Order submission failed due to downstream fulfillment timeout",
"correlation_id": "corr-20260711-8891",
"trace_id": "7f1d0b1c9f1e4b5d98c7",
"span_id": "a93b12f9d441",
"tenant_id": "tenant-123",
"order_id": "O-91821",
"dependency": "fulfillment-service",
"error_code": "FULFILLMENT_TIMEOUT",
"retryable": true,
"attempt": 3,
"latency_ms": 5000,
"exception_class": "java.net.SocketTimeoutException"
}
Perbedaannya bukan hanya format. Perbedaannya adalah kemampuan bertanya.
Dengan structured log, engineer bisa query:
event = order.submission.failed
AND dependency = fulfillment-service
AND tenant_id = tenant-123
AND timestamp between incident window
Atau:
trace_id = 7f1d0b1c9f1e4b5d98c7
Atau:
error_code = FULFILLMENT_TIMEOUT
AND service = order-service
AND version = 2026.07.11-1842
Structured logging membuat log menjadi evidence, bukan sekadar narasi.
Kenapa Structured Logging Penting?
Di production incident, pertanyaan biasanya spesifik:
Service mana yang gagal?
Versi mana yang deploy saat error mulai naik?
Request mana yang terdampak?
Tenant mana yang terdampak?
Quote/order mana yang stuck?
Dependency mana yang timeout?
Apakah error retryable?
Apakah failure terjadi setelah Kafka publish atau sebelum DB commit?
Apakah actor tertentu melakukan action tertentu?
Apakah failure hanya di endpoint tertentu?
Apakah semua pod terdampak atau hanya pod versi tertentu?
Plain text bisa menjawab sebagian, tetapi dengan biaya query tinggi dan parsing rapuh.
Structured logging menjawab dengan field stabil.
Namun structured logging juga punya risiko:
- field terlalu banyak
- field tidak konsisten antar service
- field berubah tanpa governance
- data sensitif terstruktur dan mudah dicari oleh pihak yang tidak berhak
- volume log meningkat
- log backend indexing cost meningkat
- field high-cardinality dipakai sembarangan
- log schema berbeda antara app, gateway, dan worker
Jadi structured logging bukan hanya “ubah log jadi JSON”. Structured logging adalah desain schema observability.
Log Event vs Message
Structured log minimal punya dua konsep:
event = identifier stabil untuk machine/query/dashboard
message = kalimat manusiawi untuk pembaca
Contoh:
{
"event": "quote.pricing.failed",
"message": "Quote pricing failed due to pricing engine timeout"
}
event sebaiknya stabil. message boleh berubah agar lebih jelas.
Event Naming
Gunakan pola yang konsisten.
Contoh pattern:
<noun>.<action>.<outcome>
Contoh:
quote.pricing.started
quote.pricing.completed
quote.pricing.failed
order.submission.started
order.submission.completed
order.submission.failed
kafka.message.publish.failed
rabbitmq.message.consume.failed
redis.cache.miss
workflow.task.completed
http.request.completed
Event name yang baik:
- pendek tetapi jelas
- stabil
- lowercase
- memakai delimiter konsisten
- tidak menyimpan ID unik
- tidak menyimpan error message mentah
- tidak berubah karena wording message berubah
Buruk:
FailedToSubmitOrderForOrder91821
Order failed because timeout on fulfillment-service
ERROR_ORDER_FULFILLMENT_SERVICE_TIMEOUT_TENANT_123
Masalahnya:
- sulit query stabil
- mencampur event dengan data
- high-cardinality
- susah distandardisasi
Standard Field Groups
Structured log sebaiknya dipikirkan sebagai beberapa kelompok field.
1. Time and severity fields
2. Service/runtime fields
3. Execution/source fields
4. Request/correlation fields
5. Trace fields
6. Actor/security fields
7. Tenant/customer fields
8. Business entity fields
9. Dependency fields
10. Error fields
11. Performance fields
12. Deployment/release fields
13. Platform fields
Tidak semua log event harus punya semua field. Tetapi setiap field yang dipakai harus punya arti stabil.
1. Time and Severity Fields
Field minimal:
{
"timestamp": "2026-07-11T15:02:41.981Z",
"level": "INFO"
}
timestamp
Rekomendasi umum:
Gunakan ISO-8601.
Gunakan UTC untuk production logs.
Sertakan millisecond atau precision yang cukup.
Jangan bergantung pada timezone lokal pod/node.
Contoh baik:
2026-07-11T15:02:41.981Z
Contoh raw lokal yang berisiko:
2026-07-11 22:02:41
Masalah timestamp lokal:
- korelasi antar region sulit
- korelasi dengan cloud logs sulit
- daylight saving/timezone ambiguity
- incident timeline bisa salah
level
Field level harus konsisten:
TRACE
DEBUG
INFO
WARN
ERROR
Hindari variasi:
warning
WARNINGS
Error
ERR
Level discipline dibahas detail di part khusus, tetapi structured log harus memastikan level bisa difilter dengan tepat.
2. Service and Runtime Fields
Field yang membantu mengetahui log berasal dari mana:
{
"service": "quote-order-service",
"service_namespace": "quote-order",
"environment": "prod",
"region": "ap-southeast-1",
"runtime": "java",
"runtime_version": "17",
"host": "node-abc",
"pod": "quote-order-service-7c89f6dbd8-nplq2"
}
Field penting:
| Field | Fungsi |
|---|---|
service | Nama service |
environment | dev/test/staging/prod |
region | region/cloud location |
service_namespace | grouping domain/team jika ada |
runtime | java/node/go/etc |
runtime_version | Java version |
pod | Kubernetes pod |
host | host/node jika relevan |
Dalam OpenTelemetry, sebagian field ini sering disebut resource attributes. Dalam log backend, field bisa datang dari aplikasi, collector, Kubernetes metadata, atau cloud metadata.
Internal verification:
Apakah service/env/version datang dari aplikasi atau collector?
Apakah field konsisten antara logs, metrics, traces?
Apakah nama service sama di dashboard, traces, dan alerts?
3. Deployment and Release Fields
Untuk incident setelah deployment, field release sangat penting.
{
"version": "2026.07.11-1842",
"commit_sha": "a1b2c3d4",
"build_id": "build-9182",
"image_digest": "sha256:abc123...",
"config_version": "config-20260711-03"
}
Field ini membantu menjawab:
Apakah error hanya terjadi di versi baru?
Apakah semua replica menjalankan image yang sama?
Apakah config berubah bersamaan dengan deploy?
Apakah rollback mengurangi error?
Jika field release tidak tersedia, incident debugging sering bergantung pada ingatan atau deployment notes manual.
4. Source and Execution Fields
Field source membantu menemukan code path.
{
"logger": "com.example.order.service.OrderSubmissionService",
"thread": "http-nio-8080-exec-11",
"class": "OrderSubmissionService",
"method": "submitOrder"
}
Namun hati-hati dengan class dan method otomatis dari caller location. Beberapa logging framework bisa menghitung caller data, tetapi biayanya bisa mahal karena perlu stack inspection.
Praktisnya:
loggerbiasanya cukupthreadberguna untuk debugging thread pool/MDCclass/methodeksplisit tidak selalu perlu- caller location otomatis harus direview performance impact-nya
5. Request and HTTP Fields
Untuk JAX-RS service, HTTP request log biasanya membutuhkan:
{
"http_method": "POST",
"http_route": "/api/v1/orders/{orderId}/submit",
"http_path": "/api/v1/orders/O-91821/submit",
"http_status": 504,
"http_scheme": "https",
"client_ip": "203.0.113.10",
"user_agent": "internal-client/2.1",
"request_id": "req-7731"
}
Route Template vs Raw Path
Gunakan route template untuk grouping:
/api/v1/orders/{orderId}/submit
Raw path:
/api/v1/orders/O-91821/submit
Raw path berguna untuk debugging spesifik, tetapi berisiko:
- high-cardinality
- mengandung business ID
- mengandung PII jika path buruk
- sulit aggregation
Untuk logs, raw path masih bisa berguna jika aman. Untuk metrics labels, raw path biasanya harus dihindari dan diganti route template.
Header Logging
Jangan log semua header.
Buruk:
{
"headers": {
"authorization": "Bearer eyJ...",
"cookie": "SESSION=..."
}
}
Lebih aman:
{
"user_agent": "internal-client/2.1",
"x_request_id": "req-7731",
"content_type": "application/json"
}
Gunakan allowlist.
6. Correlation and Trace Fields
Field korelasi adalah inti production debugging.
{
"correlation_id": "corr-20260711-8891",
"request_id": "req-7731",
"trace_id": "7f1d0b1c9f1e4b5d98c7",
"span_id": "a93b12f9d441",
"parent_span_id": "bc91f20a11",
"causation_id": "evt-7712"
}
Field yang umum:
| Field | Fungsi |
|---|---|
request_id | Identitas satu request HTTP |
correlation_id | Mengikat flow end-to-end |
causation_id | Menjelaskan event/action penyebab event berikutnya |
trace_id | ID distributed trace |
span_id | ID span saat ini |
parent_span_id | Parent span jika tersedia |
Untuk Java/JAX-RS, field ini biasanya masuk lewat:
- JAX-RS filter
- Servlet filter
- OpenTelemetry context
- MDC
- message header propagation
- background job context initialization
Log tanpa correlation field sering menjadi dead-end saat incident.
7. Actor, Tenant, and Security Fields
Enterprise systems sering multi-tenant dan punya user/action context.
{
"tenant_id": "tenant-123",
"actor_id": "user-918",
"actor_type": "user",
"auth_subject": "sub-abc123",
"client_id": "quote-ui",
"delegated_actor_id": "support-agent-7"
}
Pertanyaan yang bisa dijawab:
Tenant mana terdampak?
User/action mana yang memicu change?
Apakah action dilakukan oleh user, system, batch job, atau integration client?
Apakah ada impersonation/delegation?
Privacy concern:
- hindari email/name jika tidak perlu
- gunakan stable internal ID bila aman
- jangan log token/claims lengkap
- jangan log semua JWT payload
- jangan log raw security context
Audit logging akan dibahas khusus. Application log boleh membawa actor context untuk correlation, tetapi audit event punya standar lebih ketat.
8. Business Entity Fields
Untuk CPQ/order management, business key sangat penting.
Contoh:
{
"quote_id": "Q-77821",
"order_id": "O-91821",
"customer_id": "C-10021",
"account_id": "A-7123",
"product_catalog_id": "CAT-2026-07",
"process_instance_id": "camunda-918273",
"workflow_task_id": "task-871"
}
Business key membantu incident debugging karena customer impact sering bukan “request ID”, tetapi:
quote mana?
order mana?
approval mana?
fulfillment mana?
fallout mana?
process instance mana?
Namun business key juga bisa sensitif.
Prinsip:
Gunakan business key yang diperlukan untuk support/debugging.
Pastikan tidak mengandung PII langsung.
Pastikan field aman untuk log access policy.
Jangan gunakan business key sebagai metric label tanpa governance.
9. Dependency Fields
Saat memanggil dependency, log harus menjelaskan dependency mana dan hasilnya.
{
"dependency_type": "http",
"dependency": "fulfillment-service",
"dependency_operation": "POST /api/v1/fulfillment/orders",
"dependency_status": "timeout",
"dependency_http_status": 504,
"latency_ms": 5000,
"attempt": 3,
"retryable": true
}
Untuk PostgreSQL:
{
"dependency_type": "database",
"dependency": "postgresql",
"db_operation": "select",
"db_table": "orders",
"latency_ms": 842
}
Untuk Kafka:
{
"dependency_type": "kafka",
"messaging_system": "kafka",
"topic": "order-submitted",
"partition": 3,
"offset": 9918281,
"message_id": "evt-7712"
}
Untuk RabbitMQ:
{
"dependency_type": "rabbitmq",
"messaging_system": "rabbitmq",
"exchange": "order.events",
"queue": "fulfillment.order.submitted",
"routing_key": "order.submitted",
"redelivered": false
}
Untuk Redis:
{
"dependency_type": "redis",
"redis_command": "GET",
"cache_name": "quote-pricing-cache",
"cache_result": "miss",
"latency_ms": 12
}
Jangan log SQL parameter, Redis key mentah, message payload, atau header sensitif tanpa policy.
10. Error Fields
Error log harus punya field eksplisit.
{
"error_code": "FULFILLMENT_TIMEOUT",
"error_category": "dependency_timeout",
"exception_class": "java.net.SocketTimeoutException",
"exception_message": "Read timed out",
"retryable": true,
"expected_error": false,
"stack_trace": "java.net.SocketTimeoutException: Read timed out\n..."
}
Field penting:
| Field | Fungsi |
|---|---|
error_code | Kode stabil untuk query dan response mapping |
error_category | Kategori failure |
exception_class | Class exception |
exception_message | Pesan exception, harus aman |
retryable | Apakah retry masuk akal |
expected_error | Apakah failure bagian dari flow normal |
stack_trace | Stack trace jika perlu |
Exception Message Privacy
Exception message bisa mengandung sensitive data.
Contoh:
Invalid password for user john@example.com
SQL error for parameter ssn=...
Authorization failed token=...
Jangan menganggap exception message aman.
11. Performance Fields
Performance field membantu debugging latency dan saturation.
{
"latency_ms": 842,
"duration_ms": 842,
"db_latency_ms": 231,
"redis_latency_ms": 12,
"downstream_latency_ms": 500,
"queue_wait_ms": 120,
"attempt": 2
}
Field latency harus jelas:
latency_ms milik operasi apa?
request total?
dependency call?
DB query?
message processing?
job duration?
Hindari field ambigu:
{
"time": 842
}
Gunakan unit di nama field atau schema.
12. Platform Fields
Beberapa field platform bisa datang dari Kubernetes/cloud collector.
{
"k8s_namespace": "prod-quote-order",
"k8s_pod": "order-service-7c89f6dbd8-nplq2",
"k8s_container": "app",
"k8s_node": "node-17",
"cloud_provider": "aws",
"cloud_region": "ap-southeast-1"
}
Field ini membantu menjawab:
Apakah issue hanya di satu pod?
Apakah issue hanya di satu node?
Apakah issue hanya di satu region?
Apakah issue terkait rollout sebagian?
Internal verification:
Apakah platform metadata ditambahkan oleh app, collector, atau backend?
Apakah field names konsisten dengan metrics/traces?
Apakah field tersedia di log query?
Field Naming Convention
Pilih convention yang konsisten.
Untuk log JSON, snake_case sering mudah dibaca dan umum:
trace_id
span_id
correlation_id
request_id
tenant_id
actor_id
order_id
latency_ms
error_code
exception_class
Hindari campuran:
traceId
trace_id
TraceID
trace-id
Campuran field name menyebabkan:
- query sulit
- dashboard rusak
- parser rumit
- alert tidak konsisten
- engineer bingung saat incident
Prefix Field Jika Perlu
Gunakan prefix untuk domain field tertentu:
http_method
http_status
http_route
messaging_system
messaging_topic
db_operation
redis_command
k8s_pod
cloud_region
Prefix membantu menghindari ambiguity.
JSON Log Schema Minimal untuk Java/JAX-RS Service
Contoh baseline schema:
{
"timestamp": "2026-07-11T15:21:44.291Z",
"level": "INFO",
"service": "quote-order-service",
"environment": "prod",
"version": "2026.07.11-1842",
"logger": "com.example.order.api.OrderResource",
"thread": "http-nio-8080-exec-7",
"event": "http.request.completed",
"message": "HTTP request completed",
"trace_id": "7f1d0b1c9f1e4b5d98c7",
"span_id": "a93b12f9d441",
"correlation_id": "corr-20260711-8891",
"request_id": "req-7731",
"tenant_id": "tenant-123",
"actor_id": "user-918",
"http_method": "POST",
"http_route": "/api/v1/orders/{orderId}/submit",
"http_status": 202,
"order_id": "O-91821",
"latency_ms": 842
}
Baseline ini bukan standard universal. Ia harus disesuaikan dengan standar internal team.
Yang penting adalah setiap field punya definisi.
Example: Request Start Log
Request start log membantu melihat request masuk, tetapi tidak selalu perlu untuk semua endpoint jika request completed log sudah cukup.
{
"timestamp": "2026-07-11T15:24:01.102Z",
"level": "INFO",
"service": "quote-order-service",
"environment": "prod",
"event": "http.request.started",
"message": "HTTP request started",
"trace_id": "7f1d0b1c9f1e4b5d98c7",
"span_id": "a93b12f9d441",
"correlation_id": "corr-1001",
"request_id": "req-1001",
"tenant_id": "tenant-123",
"http_method": "POST",
"http_route": "/api/v1/orders/{orderId}/submit",
"client_ip": "203.0.113.10",
"user_agent": "quote-ui/3.2"
}
Review concern:
Apakah request start log volume terlalu tinggi?
Apakah request completed log sudah cukup?
Apakah client_ip policy aman?
Apakah user_agent perlu?
Apakah route template tersedia?
Example: Request Completed Log
Request completed log biasanya lebih penting karena mengandung outcome dan latency.
{
"timestamp": "2026-07-11T15:24:01.944Z",
"level": "INFO",
"service": "quote-order-service",
"environment": "prod",
"version": "2026.07.11-1842",
"event": "http.request.completed",
"message": "HTTP request completed",
"trace_id": "7f1d0b1c9f1e4b5d98c7",
"span_id": "a93b12f9d441",
"correlation_id": "corr-1001",
"request_id": "req-1001",
"tenant_id": "tenant-123",
"actor_id": "user-918",
"http_method": "POST",
"http_route": "/api/v1/orders/{orderId}/submit",
"http_status": 202,
"order_id": "O-91821",
"latency_ms": 842
}
Pertanyaan debugging yang bisa dijawab:
Request berhasil atau gagal?
Berapa latency?
Endpoint mana?
Tenant/order mana?
Trace mana yang harus dibuka?
Versi service mana?
Example: Dependency Timeout Log
{
"timestamp": "2026-07-11T15:28:11.004Z",
"level": "ERROR",
"service": "order-service",
"environment": "prod",
"event": "dependency.http.timeout",
"message": "HTTP dependency timed out",
"trace_id": "7f1d0b1c9f1e4b5d98c7",
"span_id": "b712bb9a12",
"correlation_id": "corr-1001",
"tenant_id": "tenant-123",
"order_id": "O-91821",
"dependency_type": "http",
"dependency": "fulfillment-service",
"dependency_operation": "POST /api/v1/fulfillment/orders",
"timeout_ms": 5000,
"latency_ms": 5001,
"attempt": 3,
"retryable": true,
"error_code": "FULFILLMENT_TIMEOUT",
"exception_class": "java.net.SocketTimeoutException"
}
Review concern:
Apakah dependency_operation terlalu detail?
Apakah raw URL mengandung ID/token?
Apakah retry attempt benar?
Apakah error_code stabil?
Apakah stack trace perlu atau trace sudah cukup?
Example: Kafka Publish Log
{
"timestamp": "2026-07-11T15:31:22.118Z",
"level": "INFO",
"service": "order-service",
"environment": "prod",
"event": "kafka.message.published",
"message": "Kafka message published",
"trace_id": "7f1d0b1c9f1e4b5d98c7",
"correlation_id": "corr-1001",
"causation_id": "order-submit-command-991",
"tenant_id": "tenant-123",
"order_id": "O-91821",
"messaging_system": "kafka",
"topic": "order-submitted",
"partition": 3,
"offset": 9918281,
"message_id": "evt-7712"
}
Jangan log payload penuh secara default. Payload bisa besar, sensitif, dan mahal.
Example: Redis Cache Miss Log
Cache miss umumnya tidak perlu INFO untuk setiap request jika volume tinggi. Bisa berupa DEBUG, metric, atau sampled log.
{
"timestamp": "2026-07-11T15:35:00.001Z",
"level": "DEBUG",
"service": "pricing-service",
"environment": "prod",
"event": "redis.cache.miss",
"message": "Redis cache miss",
"trace_id": "7f1d0b1c9f1e4b5d98c7",
"correlation_id": "corr-1001",
"tenant_id": "tenant-123",
"quote_id": "Q-77821",
"cache_name": "quote-pricing-cache",
"redis_command": "GET",
"latency_ms": 8
}
Review concern:
Apakah cache miss lebih cocok sebagai metric?
Apakah key mentah disembunyikan?
Apakah log volume terkendali?
Apakah DEBUG production disabled by default?
Example: Workflow State Transition Log
{
"timestamp": "2026-07-11T15:41:12.771Z",
"level": "INFO",
"service": "workflow-service",
"environment": "prod",
"event": "order.state.transitioned",
"message": "Order state transitioned",
"correlation_id": "corr-1001",
"tenant_id": "tenant-123",
"order_id": "O-91821",
"process_instance_id": "proc-8812",
"previous_state": "VALIDATED",
"new_state": "FULFILLMENT_PENDING",
"transition_reason": "order_validated",
"actor_type": "system"
}
Ini mendekati audit/lifecycle observability. Untuk compliance-grade audit, perlu standar audit log khusus.
Structured Logging di Java Code
Pattern 1: Parameterized Message + MDC
MDC.put("correlation_id", correlationId);
MDC.put("tenant_id", tenantId);
MDC.put("order_id", orderId);
try {
log.info("Order submission started");
orderService.submit(orderId);
log.info("Order submission completed");
} catch (OrderSubmissionException ex) {
log.error("Order submission failed error_code={}", ex.errorCode(), ex);
throw ex;
} finally {
MDC.clear();
}
Kelebihan:
- sederhana
- context otomatis masuk semua log
- cocok untuk request-scoped fields
Kekurangan:
- event-specific fields sulit jika semua hanya message
- perlu encoder yang include MDC
- async/thread propagation harus benar
Pattern 2: Fluent Key-Value Logging
log.atInfo()
.addKeyValue("event", "order.submission.completed")
.addKeyValue("order_id", orderId)
.addKeyValue("tenant_id", tenantId)
.addKeyValue("latency_ms", latencyMs)
.log("Order submission completed");
Kelebihan:
- field event-specific eksplisit
- tidak semua field harus masuk MDC
- lebih dekat ke structured logging
Kekurangan:
- butuh dukungan framework/encoder
- bisa tidak muncul sebagai JSON field jika backend tidak mendukung
Pattern 3: Domain Log Event Helper
log.info("event=order.submission.completed order_id={} tenant_id={} latency_ms={}",
orderId, tenantId, latencyMs);
Ini masih pseudo-structured. Bisa dicari, tetapi bukan JSON field sejati kecuali parser backend memecahnya.
Gunakan hanya jika stack internal belum mendukung true structured fields.
MDC Field vs Event Field
Tidak semua field cocok masuk MDC.
MDC cocok untuk context yang berlaku sepanjang request/job/message:
correlation_id
trace_id
span_id
request_id
tenant_id
actor_id
quote_id/order_id jika request scoped
process_instance_id jika workflow scoped
Event-specific field lebih baik ditambahkan pada log event tertentu:
latency_ms
http_status
dependency
error_code
attempt
retryable
previous_state
new_state
cache_result
Jika semua field dimasukkan ke MDC, risiko:
- context bocor antar event
- stale field muncul pada log yang tidak relevan
- MDC terlalu besar
- cleanup rumit
- async propagation makin mahal
Structured Logging and OpenTelemetry
OpenTelemetry membawa trace context dan resource attributes. Log sebaiknya bisa dikorelasikan dengan trace.
Minimum fields:
trace_id
span_id
service.name atau service
deployment.environment atau environment
service.version atau version
Jika memakai OTel log bridge, pastikan:
Log record membawa trace_id/span_id.
Resource attributes konsisten dengan traces/metrics.
MDC tidak konflik dengan OTel context.
Collector tidak menghapus field penting.
Field naming masih cocok dengan log backend query.
Structured logs dan traces saling melengkapi:
Trace menjelaskan path dan latency.
Log menjelaskan decision, domain state, error context, dan business key.
Jangan menganggap trace menggantikan log. Jangan juga menganggap log menggantikan trace.
Structured Logging and Metrics
Log field boleh lebih spesifik daripada metric label.
Contoh:
{
"order_id": "O-91821"
}
Untuk log, order_id bisa berguna saat support/debugging jika policy mengizinkan.
Untuk metric label, order_id hampir selalu buruk karena high-cardinality.
Prinsip:
Field log bisa dipakai untuk forensic query.
Metric label harus aman untuk aggregation.
Jangan otomatis mengubah semua log fields menjadi metric labels.
Queryability: Desain Field Berdasarkan Pertanyaan
Structured fields harus berasal dari pertanyaan production.
Contoh pertanyaan dan field yang dibutuhkan:
| Pertanyaan | Field |
|---|---|
| Error terjadi di service mana? | service, environment |
| Error mulai setelah deploy apa? | version, commit_sha, image_digest |
| Endpoint mana yang gagal? | http_route, http_method, http_status |
| Tenant mana terdampak? | tenant_id |
| Order mana stuck? | order_id, state, process_instance_id |
| Dependency mana timeout? | dependency, dependency_type, error_code |
| Request ini trace-nya apa? | trace_id, span_id |
| Apakah retry terjadi? | attempt, retryable |
| Apakah issue hanya pod tertentu? | k8s_pod, k8s_node |
Jangan menambah field hanya karena “mungkin berguna”. Tambahkan field karena ada pertanyaan debugging, audit, support, reliability, atau compliance yang jelas.
Privacy and Security Rules
Structured logging membuat data lebih mudah dicari. Ini baik untuk debugging, buruk jika data sensitif ikut masuk.
Data yang Tidak Boleh Dilog Mentah
password
access token
refresh token
Authorization header
Cookie
session ID
API key
secret
private key
full JWT
full request body tanpa approval
payment detail
personal identifier sensitif
SQL parameters sensitif
Redis key mentah jika mengandung user/business data
message payload sensitif
Prefer Allowlist
Buruk:
log.info("headers={}", headers);
Baik:
log.atInfo()
.addKeyValue("content_type", safeContentType)
.addKeyValue("user_agent", safeUserAgent)
.addKeyValue("request_id", requestId)
.log("Request metadata captured");
Masking vs Redaction
Masking = sebagian nilai masih terlihat, misalnya ****1234
Redaction = nilai dihapus/diganti sepenuhnya, misalnya [REDACTED]
Untuk token/secrets, gunakan redaction total.
Cost Concern
Structured logging bisa meningkatkan biaya karena:
- field lebih banyak
- indexing lebih banyak
- query lebih sering
- JSON lebih besar dari plain text
- high-volume event jadi mahal
- stack trace besar
- duplicate fields dari app dan collector
Pertanyaan cost review:
Apakah log ini muncul pada setiap request?
Berapa ukuran rata-rata log event?
Apakah field ini diindex?
Apakah field ini diperlukan untuk query?
Apakah event ini lebih cocok menjadi metric?
Apakah perlu sampling?
Apakah DEBUG log akan aktif di production?
Structured logging tidak berarti semua hal dilog. Structured logging berarti hal yang dilog dibentuk sebagai data berkualitas.
Correctness Concern
Structured log bisa salah secara halus.
Contoh:
{
"event": "order.submission.completed",
"http_status": 500
}
Event dan status bertentangan.
Atau:
{
"tenant_id": "tenant-A",
"order_id": "order from tenant-B"
}
Context bocor akibat MDC tidak dibersihkan.
Atau:
{
"latency_ms": 300,
"message": "dependency timed out after 5000ms"
}
Latency field salah operasi.
Correctness log penting karena log sering menjadi evidence RCA. Evidence yang salah lebih buruk daripada evidence yang hilang.
Common Anti-Patterns
Anti-pattern 1: JSON Message, Not JSON Log
{
"message": "event=order.failed order_id=O-1 error=timeout"
}
Ini bukan structured logging sejati karena data penting hanya string di dalam message.
Anti-pattern 2: Field Name Tidak Konsisten
{"trace_id":"abc"}
{"traceId":"abc"}
{"TraceID":"abc"}
Akibatnya query dan dashboard tidak stabil.
Anti-pattern 3: Logging Whole Object
{
"order": {
"customerName": "...",
"price": "...",
"internalNotes": "..."
}
}
Risiko PII, volume, dan schema tidak stabil.
Anti-pattern 4: Raw Exception Message sebagai Error Code
{
"error_code": "Read timed out after 5000 milliseconds calling https://.../orders/O-91821"
}
error_code harus stabil, bukan pesan dinamis.
Anti-pattern 5: Business ID di Event Name
{
"event": "order.O-91821.failed"
}
Event name menjadi high-cardinality dan tidak bisa diagregasi.
Anti-pattern 6: Semua Header Dilog
{
"headers": "..."
}
Risiko token/cookie leakage sangat tinggi.
Anti-pattern 7: Log Schema Berubah Tanpa Review
Mengganti correlation_id menjadi corrId bisa merusak:
- saved query
- dashboard
- alert
- runbook
- incident playbook
- downstream parser
Structured Logging Review Checklist
Event Design
[ ] Event name stabil.
[ ] Event name tidak mengandung ID unik.
[ ] Message manusiawi tetapi bukan satu-satunya source data.
[ ] Outcome event jelas: started/completed/failed/skipped/retried.
[ ] Event tidak duplicate dengan event lain tanpa alasan.
Field Design
[ ] Field names konsisten.
[ ] Timestamp punya timezone.
[ ] Level konsisten.
[ ] service/environment/version tersedia.
[ ] logger/thread tersedia jika dibutuhkan.
[ ] correlation_id/request_id/trace_id/span_id tersedia.
[ ] tenant_id/actor_id/business key tersedia jika relevan dan aman.
[ ] dependency/error/performance fields jelas.
HTTP/JAX-RS
[ ] http_method tersedia.
[ ] http_route memakai template jika memungkinkan.
[ ] http_status tersedia pada completion/error.
[ ] latency_ms jelas.
[ ] raw path/query/header/body tidak dilog sembarangan.
Error
[ ] error_code stabil.
[ ] exception_class tersedia.
[ ] stack trace tersedia saat perlu.
[ ] exception message aman.
[ ] retryable/expected_error jelas jika relevan.
[ ] Error tidak dilog duplicate di banyak layer.
Privacy
[ ] Tidak ada Authorization header.
[ ] Tidak ada Cookie/session ID.
[ ] Tidak ada token/API key/password.
[ ] Tidak ada request body sensitif.
[ ] Tidak ada SQL parameter sensitif.
[ ] Tidak ada Redis key mentah yang sensitif.
[ ] Masking/redaction sesuai policy.
Cost and Operability
[ ] Log event tidak terlalu high-volume tanpa alasan.
[ ] Field yang diindex memang diperlukan.
[ ] Event yang lebih cocok metric tidak dilog berlebihan.
[ ] Stack trace tidak duplicate.
[ ] Log bisa dipakai untuk incident query.
[ ] Log bisa dikorelasikan dengan trace/dashboard.
Internal Verification Checklist
Gunakan checklist ini di service/team internal.
Log Schema Standard
[ ] Apakah ada standard field names internal?
[ ] Apakah service/environment/version field distandardisasi?
[ ] Apakah trace_id/span_id/correlation_id/request_id field names sudah baku?
[ ] Apakah tenant_id/actor_id/business key boleh masuk application logs?
[ ] Apakah ada reserved fields?
[ ] Apakah ada schema version untuk log?
Logging Framework Output
[ ] Apakah Logback/Log4j2 encoder menghasilkan JSON valid per line?
[ ] Apakah MDC fields masuk sebagai top-level fields?
[ ] Apakah exception stack trace terformat dengan benar?
[ ] Apakah key-value fluent logging didukung?
[ ] Apakah field dari app tidak konflik dengan field collector?
Query and Dashboard
[ ] Field apa yang dipakai di saved query?
[ ] Field apa yang dipakai di dashboard?
[ ] Field apa yang dipakai di alert?
[ ] Apakah perubahan field name butuh migration?
[ ] Apakah log backend melakukan indexing field tertentu?
Security and Privacy
[ ] Apakah ada secure logging guideline?
[ ] Apakah ada allowlist header yang boleh dilog?
[ ] Apakah body logging policy jelas?
[ ] Apakah ada masking/redaction utility?
[ ] Apakah log access control sesuai sensitivitas field?
[ ] Apakah audit log dipisahkan dari application log?
Domain/Business Context
[ ] Apakah quote_id/order_id/process_instance_id muncul di log yang tepat?
[ ] Apakah state transition punya log/audit event?
[ ] Apakah fulfillment/fallout/amendment/cancellation punya business event?
[ ] Apakah business key aman untuk dicari oleh support/engineer?
Platform Context
[ ] Apakah Kubernetes pod/namespace/deployment labels masuk ke log backend?
[ ] Apakah cloud region/provider/account/subscription context tersedia?
[ ] Apakah deployment version/commit/image digest tersedia?
[ ] Apakah log fields konsisten dengan trace resource attributes?
PR Review Questions
Saat review PR yang menambah/mengubah log, tanyakan:
Pertanyaan production apa yang log ini bantu jawab?
Apakah event name stabil?
Apakah field names mengikuti standard?
Apakah context request/trace/correlation tersedia?
Apakah business key perlu dan aman?
Apakah log ini berada di level yang tepat?
Apakah log ini akan muncul terlalu sering?
Apakah log ini mengandung sensitive data?
Apakah exception dilog dengan stack trace yang benar?
Apakah log ini duplicate dengan boundary log lain?
Apakah metric/trace/audit lebih tepat daripada log?
Apakah perubahan field bisa merusak dashboard/alert/query?
Kesimpulan
Structured logging adalah disiplin desain signal. Ia membuat log menjadi data yang bisa dipakai untuk query, correlation, dashboard, alert support, incident timeline, dan RCA.
Prinsip utama:
Event name harus stabil.
Message harus manusiawi tetapi bukan satu-satunya data.
Field harus punya definisi.
Timestamp dan timezone harus benar.
Correlation dan trace fields wajib untuk debugging distributed system.
Business key membantu support tetapi harus privacy-aware.
Dependency dan error fields harus eksplisit.
Raw headers/body/payload/object harus dihindari.
Log schema adalah contract operasional.
Perubahan field perlu review karena bisa merusak observability downstream.
Di part berikutnya, kita akan membahas log level discipline: kapan memakai TRACE, DEBUG, INFO, WARN, ERROR; bagaimana membedakan expected vs unexpected error; dan bagaimana mencegah log noise, missing log, serta alert fatigue.
You just completed lesson 06 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.