Series MapLesson 06 / 62
Focus mode active/Press Alt+Shift+R to toggle/Esc to exit
Start HereOrdered learning track

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.

11 min read2150 words
PrevNext
Lesson 0662 lesson track01–12 Start Here
#structured-logging#json-log#log-schema#correlation+1 more

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:

FieldFungsi
serviceNama service
environmentdev/test/staging/prod
regionregion/cloud location
service_namespacegrouping domain/team jika ada
runtimejava/node/go/etc
runtime_versionJava version
podKubernetes pod
hosthost/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:

  • logger biasanya cukup
  • thread berguna untuk debugging thread pool/MDC
  • class/method eksplisit 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:

FieldFungsi
request_idIdentitas satu request HTTP
correlation_idMengikat flow end-to-end
causation_idMenjelaskan event/action penyebab event berikutnya
trace_idID distributed trace
span_idID span saat ini
parent_span_idParent 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:

FieldFungsi
error_codeKode stabil untuk query dan response mapping
error_categoryKategori failure
exception_classClass exception
exception_messagePesan exception, harus aman
retryableApakah retry masuk akal
expected_errorApakah failure bagian dari flow normal
stack_traceStack 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:

PertanyaanField
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.

Lesson Recap

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.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.