Java Logging Ecosystem
Java Logging Ecosystem: SLF4J, Logback, Log4j2, JUL, and JAX-RS Integration
Ekosistem logging Java untuk aplikasi enterprise JAX-RS: SLF4J, Logback, Log4j2, JUL, bridge libraries, async appender, JSON encoder, MDC, Marker, parameterized logging, exception logging, konfigurasi, dan runtime log level.
Cheatsheet Observability Part 005 — Java Logging Ecosystem
Tujuan Part Ini
Part ini membahas ekosistem logging Java yang umum ditemui di enterprise backend system, khususnya aplikasi Java 17+ / JAX-RS / Jakarta RESTful Web Services.
Fokusnya bukan menghafal semua library, tetapi memahami lapisan-lapisan logging runtime:
- logging API
- logging implementation
- binding/provider
- bridge library
- logger name
- appender/handler
- encoder/layout
- structured JSON output
- MDC
- Marker
- async appender
- exception logging
- runtime log level
- integrasi dengan JAX-RS, Servlet container, dependency library, dan log collector
Setelah membaca part ini, Anda harus mampu:
- membedakan SLF4J, Logback, Log4j2, JUL, dan bridge library
- membaca dependency logging di aplikasi Java dan mendeteksi konflik binding
- memahami bagaimana log keluar dari code sampai masuk ke stdout/container/log backend
- memilih pola logging yang aman untuk production
- memahami kapan async logging membantu dan kapan berbahaya
- memahami peran MDC dan Marker tanpa mencampurnya dengan business logic
- mereview konfigurasi logging Java dari sudut correctness, performance, privacy, cost, dan operability
Ringkasan Mental Model
Di Java, logging bukan satu komponen tunggal. Logging adalah pipeline kecil di dalam aplikasi.
Application code
-> Logging API
-> Logging implementation/provider
-> Logger configuration
-> Appender/handler
-> Encoder/layout
-> stdout/file/network
-> container runtime
-> log collector/agent
-> log backend/search platform
-> dashboard/alert/query
Contoh konkret:
Your JAX-RS resource
-> org.slf4j.Logger
-> Logback
-> AsyncAppender
-> Logstash JSON Encoder
-> stdout
-> Kubernetes container log
-> Fluent Bit / Vector / agent
-> log backend
Atau:
Your service class
-> SLF4J API
-> Log4j2 provider
-> ConsoleAppender
-> JSON layout
-> stdout
Kesalahan umum adalah mengira logger.info(...) langsung berarti log sudah benar. Padahal kualitas log bergantung pada semua lapisan berikut:
- apakah API dan implementation cocok?
- apakah ada binding lebih dari satu?
- apakah library lain memakai JUL, Log4j, atau Commons Logging?
- apakah bridge library benar?
- apakah log keluar sebagai JSON valid?
- apakah MDC ikut terbawa?
- apakah stack trace diparse benar?
- apakah async queue bisa drop log?
- apakah stdout ditangkap container dengan benar?
- apakah log collector menjaga field JSON?
- apakah log backend memetakan field sesuai ekspektasi?
Senior engineer tidak hanya menulis log. Senior engineer memahami logging supply chain.
Logging API vs Logging Implementation
Dua konsep utama:
| Layer | Fungsi | Contoh |
|---|---|---|
| Logging API | Interface yang dipakai application code | SLF4J API, JUL API |
| Logging implementation | Engine yang benar-benar menulis log | Logback, Log4j2, JUL |
API adalah kontrak. Implementation adalah runtime behavior.
Di modern Java application, pattern yang umum:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void submitOrder(String orderId) {
log.info("Submitting order order_id={}", orderId);
}
}
Code di atas memakai SLF4J API. Tetapi output akhirnya bergantung pada provider atau binding yang ada di runtime.
Contoh implementation:
- Logback
- Log4j2
- JUL
- tinylog
- provider lain yang compatible dengan SLF4J
Kenapa API dan implementation dipisah?
Karena library ecosystem Java besar dan historis. Banyak library ingin logging tanpa memaksa aplikasi memakai backend tertentu.
Dengan abstraction seperti SLF4J:
- application code tidak tergantung langsung ke Logback atau Log4j2
- framework/library bisa log melalui API yang sama
- implementation bisa diganti lewat dependency/config
- formatting/output/pipeline bisa dikontrol di satu tempat
Namun abstraction juga menambah risiko:
- multiple binding
- bridge loop
- dependency conflict
- log hilang karena provider salah
- format tidak konsisten antar module
SLF4J
SLF4J adalah logging facade. Ia menyediakan API logging yang umum dipakai library dan application code.
Contoh penggunaan:
private static final Logger log = LoggerFactory.getLogger(QuotePricingService.class);
public PriceResult priceQuote(String quoteId) {
log.info("quote pricing started quote_id={}", quoteId);
try {
PriceResult result = pricingEngine.calculate(quoteId);
log.info("quote pricing completed quote_id={} total_amount={}", quoteId, result.totalAmount());
return result;
} catch (PricingException ex) {
log.error("quote pricing failed quote_id={} error_code={}", quoteId, ex.errorCode(), ex);
throw ex;
}
}
SLF4J menyediakan:
tracedebuginfowarnerror- parameterized logging
- marker support
- MDC API
- fluent logging API pada versi baru
Kelebihan SLF4J
- menjadi standard de facto di banyak Java application
- menghindari direct dependency ke implementation
- mendukung parameterized logging
- bisa diarahkan ke Logback, Log4j2, atau provider lain
- memudahkan library agar netral terhadap backend logging
Hal yang harus diperhatikan
SLF4J sendiri bukan log backend. Jika provider tidak ada atau salah, output bisa tidak sesuai ekspektasi.
Checklist:
Apakah aplikasi memakai SLF4J API?
Apakah runtime memiliki tepat satu provider/binding utama?
Apakah semua framework/library logs diarahkan ke provider yang sama?
Apakah versi SLF4J sesuai dengan provider?
Apakah ada warning saat startup tentang multiple providers?
Logback
Logback adalah implementation yang sering dipakai bersama SLF4J.
Konfigurasi umum memakai logback.xml atau logback-spring.xml jika memakai Spring ecosystem. Untuk JAX-RS non-Spring, biasanya logback.xml berada di classpath.
Contoh konfigurasi sederhana:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Untuk production containerized system, console/stdout output biasanya lebih disukai daripada file lokal karena Kubernetes/container runtime akan menangkap stdout/stderr.
Logback dengan JSON Encoder
Untuk structured logging, Logback sering dikombinasikan dengan JSON encoder seperti logstash-logback-encoder.
Contoh konseptual:
<configuration>
<appender name="JSON_STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdc>true</includeMdc>
<customFields>{"service":"quote-order-service"}</customFields>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="JSON_STDOUT" />
</root>
</configuration>
Yang perlu direview:
- apakah output satu JSON object per line?
- apakah MDC field ikut masuk?
- apakah field name sesuai standard internal?
- apakah timestamp UTC/ISO-8601?
- apakah stack trace diparse sebagai field, bukan merusak JSON?
- apakah custom fields seperti service/environment/version tersedia?
Risiko Logback
- konfigurasi XML tidak termuat karena classpath salah
- encoder tidak menghasilkan JSON valid
- async appender queue penuh
- stack trace terlalu besar
- logger level terlalu verbose
- sensitive data masuk dari
%msg - MDC tidak ikut karena encoder tidak include MDC
Log4j2
Log4j2 adalah logging implementation lain yang kuat dan umum di enterprise Java.
Kelebihan Log4j2:
- asynchronous logger yang powerful
- konfigurasi fleksibel
- layout/encoder kaya
- performance bagus bila dikonfigurasi benar
- plugin ecosystem luas
Contoh konfigurasi konseptual JSON layout:
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<JsonLayout compact="true" eventEol="true" properties="true" />
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
Jika aplikasi memakai SLF4J API dengan Log4j2 backend, dependency biasanya perlu provider SLF4J-to-Log4j2 yang sesuai.
Risiko Log4j2
- salah memilih bridge/provider
- async logging tidak dipahami failure mode-nya
- configuration reload tidak dikontrol
- old vulnerable version masih dipakai
- JSON layout tidak sesuai schema internal
- property/MDC tidak muncul karena layout tidak include context data
Internal verification penting:
Cek versi Log4j2.
Cek security baseline internal.
Cek provider SLF4J yang aktif.
Cek apakah ada bridge loop.
Cek JSON layout dan context data.
Cek async logger policy.
JUL: java.util.logging
JUL adalah logging bawaan JDK.
Beberapa library atau container bisa memakai JUL, misalnya:
- legacy Java library
- beberapa runtime/container
- framework lama
- internal library lama
JUL bukan salah, tetapi sering menyebabkan log terpisah dari pipeline utama jika tidak dijembatani.
Contoh masalah:
Application code -> SLF4J -> Logback -> JSON stdout
Library X -> JUL -> default console formatter -> plain text stderr
Akibatnya:
- format tidak konsisten
- field context tidak ada
- log level tidak dikontrol dari config utama
- log backend menerima dua bentuk log berbeda
- incident query menjadi sulit
Solusi umum adalah menggunakan bridge JUL ke SLF4J, tetapi harus diverifikasi agar tidak membuat loop.
Bridge Libraries
Bridge library mengalihkan log dari API/framework lama ke backend utama.
Contoh jenis bridge:
| Bridge | Fungsi |
|---|---|
jul-to-slf4j | JUL logs diarahkan ke SLF4J |
jcl-over-slf4j | Apache Commons Logging diarahkan ke SLF4J |
log4j-over-slf4j | Log4j 1.x API diarahkan ke SLF4J |
log4j-to-slf4j | Log4j2 API diarahkan ke SLF4J |
log4j-slf4j2-impl | SLF4J API diarahkan ke Log4j2 implementation |
Bridge harus dipahami sebagai routing, bukan magic.
Bridge Loop
Bridge loop terjadi ketika log diarahkan berputar antar API.
Contoh buruk konseptual:
SLF4J -> Log4j2
Log4j2 -> SLF4J
Akibatnya bisa fatal:
- infinite recursion
- startup failure
- stack overflow
- duplicate logs
- unpredictable behavior
Dependency Review untuk Bridge
Saat review dependency tree, cari:
slf4j-api
logback-classic
log4j-core
log4j-api
log4j-to-slf4j
log4j-slf4j2-impl
jul-to-slf4j
jcl-over-slf4j
commons-logging
Pertanyaan review:
Apa logging API utama aplikasi?
Apa implementation utama aplikasi?
Library mana yang memakai logging API lain?
Bridge mana yang dipakai?
Apakah ada lebih dari satu SLF4J provider?
Apakah ada bridge dua arah?
Apakah startup log menunjukkan warning logging?
Logger Naming
Logger name biasanya memakai nama class:
private static final Logger log = LoggerFactory.getLogger(OrderSubmissionResource.class);
Output logger name membantu menemukan source code log.
Contoh:
{
"logger": "com.example.order.api.OrderSubmissionResource",
"message": "order submission failed"
}
Logger per Class vs Shared Logger
Pattern yang disarankan:
private static final Logger log = LoggerFactory.getLogger(CurrentClass.class);
Hindari logger global seperti:
private static final Logger log = LoggerFactory.getLogger("APP");
Karena logger global menghilangkan lokasi source yang penting saat debugging.
Logger Name untuk Package Level Control
Logger name berbasis package memungkinkan konfigurasi level per namespace:
<logger name="com.csg.quoteorder.pricing" level="DEBUG" />
<logger name="com.csg.quoteorder.integration" level="INFO" />
<logger name="org.hibernate.SQL" level="WARN" />
Dalam incident, runtime/package-specific log level bisa berguna, tetapi harus dikontrol karena dapat meningkatkan volume log secara ekstrem.
Parameterized Logging
Gunakan parameterized logging, bukan string concatenation.
Baik:
log.info("order submitted order_id={} tenant_id={}", orderId, tenantId);
Kurang baik:
log.info("order submitted order_id=" + orderId + " tenant_id=" + tenantId);
Parameterized logging lebih baik karena:
- menghindari string construction saat level tidak aktif
- lebih konsisten
- lebih aman untuk performance
- memudahkan structured logging jika encoder mendukung argument capture
Namun parameterized logging bukan otomatis structured logging. Ini masih message text kecuali framework/encoder mengubah key-value menjadi field.
SLF4J Fluent API Awareness
Pada SLF4J versi baru, fluent API bisa mendukung key-value style:
log.atInfo()
.addKeyValue("event", "order.submission.completed")
.addKeyValue("order_id", orderId)
.addKeyValue("tenant_id", tenantId)
.log("Order submission completed");
Tetapi apakah key-value benar-benar muncul sebagai JSON fields bergantung pada backend dan encoder.
Internal verification:
Cek apakah addKeyValue muncul sebagai field di log backend.
Cek field name yang dihasilkan.
Cek apakah field bisa difilter/query.
Cek apakah field tidak hanya masuk ke message string.
Exception Logging
Exception logging harus menyimpan stack trace dengan benar.
Benar:
try {
paymentClient.reservePayment(orderId);
} catch (PaymentTimeoutException ex) {
log.error("payment reservation timed out order_id={} dependency=payment-service", orderId, ex);
throw ex;
}
Salah:
log.error("payment reservation timed out " + ex.getMessage());
Masalahnya:
- stack trace hilang
- root cause sulit ditemukan
- exception class tidak jelas
- nested cause tidak terlihat
Juga salah atau berisiko:
log.error("payment reservation failed", ex);
throw new RuntimeException(ex);
Jika layer atas juga log stack trace, hasilnya duplicate stack trace.
Prinsip Exception Logging
Gunakan prinsip berikut:
Log exception pada boundary yang memiliki context operasional cukup.
Jangan log exception berulang di setiap layer.
Tambahkan business key dan dependency context.
Jangan buang stack trace.
Jangan bocorkan sensitive data dari exception message.
Boundary yang umum masuk akal:
- JAX-RS ExceptionMapper
- message consumer top-level handler
- background job top-level handler
- workflow worker boundary
- dependency adapter saat mengubah exception menjadi domain-specific failure
MDC: Mapped Diagnostic Context
MDC adalah context map yang melekat pada execution thread dan otomatis ikut ke log jika encoder/layout mendukung.
Contoh penggunaan:
import org.slf4j.MDC;
public void handleRequest(String correlationId, String tenantId) {
MDC.put("correlation_id", correlationId);
MDC.put("tenant_id", tenantId);
try {
log.info("request processing started");
service.process();
} finally {
MDC.clear();
}
}
Jika log encoder include MDC, output bisa seperti:
{
"timestamp": "2026-07-11T14:31:10.112Z",
"level": "INFO",
"message": "request processing started",
"correlation_id": "corr-123",
"tenant_id": "tenant-9"
}
MDC penting untuk:
- request ID
- correlation ID
- trace ID
- span ID
- tenant ID
- actor ID
- business key
- process instance ID
Namun MDC punya failure mode:
- lupa clear, context bocor ke request berikutnya
- thread pool tidak membawa context otomatis
- async execution kehilangan context
- virtual thread behavior harus diverifikasi
- reactive framework butuh context mechanism berbeda
- encoder tidak include MDC
Detail MDC akan dibahas khusus di part berikutnya. Di part ini, hal pentingnya adalah: logging framework harus dikonfigurasi untuk membawa MDC ke output.
Marker
Marker adalah metadata tambahan pada log event untuk memberi kategori khusus.
Contoh konseptual:
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
private static final Marker SECURITY = MarkerFactory.getMarker("SECURITY");
log.warn(SECURITY, "authorization denied user_id={} action={}", userId, action);
Marker bisa berguna untuk:
- security event
- audit-related event
- compliance-sensitive event
- operational event khusus
- routing log ke appender tertentu
Tetapi Marker bukan pengganti structured field. Dalam JSON logging, sering lebih jelas memakai field eksplisit:
{
"event_category": "security",
"event": "authorization.denied"
}
Internal verification:
Apakah Marker dipakai?
Apakah Marker muncul di backend?
Apakah Marker mempengaruhi routing?
Apakah ada standard internal untuk security/audit marker?
Appender, Handler, Layout, dan Encoder
Terminologi berbeda antar framework, tetapi mental modelnya mirip.
| Konsep | Fungsi |
|---|---|
| Appender/Handler | Tujuan output log |
| Layout/Encoder | Format output log |
| Filter | Menentukan log event diterima/dibuang/diubah |
| Logger config | Level dan appender per logger/package |
Appender umum:
- console/stdout
- file
- rolling file
- socket/network
- async wrapper
- custom backend appender
Dalam container/Kubernetes, pattern yang umum:
Application logs to stdout
Container runtime captures stdout
Node-level agent ships logs
Central log backend stores/searches logs
File appender di container perlu alasan kuat, karena:
- file bisa hilang saat pod mati
- rotation harus dikontrol
- agent harus mount path
- disk pressure bisa terjadi
- debugging lebih kompleks
Async Appender
Async appender memindahkan penulisan log ke background thread agar request thread tidak terlalu terbebani.
Manfaat:
- mengurangi latency impact dari log IO
- membantu saat log volume tinggi
- menghindari request thread blocking karena output lambat
Namun async logging punya failure mode:
- queue penuh
- log drop
- shutdown sebelum flush
- ordering antar thread tidak selalu intuitif
- error penting bisa hilang jika discard policy salah
- memory pressure karena queue besar
Contoh Logback AsyncAppender
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>8192</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>true</neverBlock>
<appender-ref ref="JSON_STDOUT" />
</appender>
<root level="INFO">
<appender-ref ref="ASYNC" />
</root>
Konfigurasi di atas hanya contoh. Nilai queue, discard policy, dan blocking behavior harus disesuaikan dengan standar internal dan risiko production.
Trade-off Blocking vs Dropping
Blocking async appender:
+ log tidak mudah hilang
- request thread bisa tertahan saat output lambat
Dropping async appender:
+ request thread lebih terlindungi
- evidence incident bisa hilang saat load tinggi
Untuk mission-critical system, keputusan ini tidak boleh implicit. Harus ada alasan eksplisit.
Internal verification:
Apakah appender async?
Berapa queue size?
Apa yang terjadi saat queue penuh?
Apakah ERROR log pernah didrop?
Apakah shutdown flush dijamin?
Apakah ada metric untuk dropped logs?
JSON Encoder dan Structured Output
Structured logging membutuhkan encoder/layout yang menghasilkan JSON valid.
Target minimal:
{
"timestamp": "2026-07-11T14:42:00.125Z",
"level": "INFO",
"logger": "com.example.order.OrderService",
"thread": "http-nio-8080-exec-7",
"message": "Order submitted",
"service": "quote-order-service",
"environment": "prod",
"version": "2026.07.11-1842",
"trace_id": "7f1d...",
"span_id": "a93b...",
"correlation_id": "corr-991",
"tenant_id": "tenant-123",
"order_id": "O-91821"
}
Hal yang perlu dicek:
- valid JSON per line
- timestamp dengan timezone
- level konsisten
- logger name tersedia
- thread tersedia jika relevan
- message tetap manusiawi
- MDC masuk sebagai field
- exception masuk sebagai field atau stack trace yang aman diparse
- service/environment/version tersedia
- field name sesuai standard internal
Structured logging detail dibahas di Part 006.
Runtime Log Level Change
Dalam production incident, kadang engineer perlu menaikkan log level sementara untuk package tertentu.
Contoh kebutuhan:
Naikkan DEBUG untuk com.example.order.integration selama 15 menit.
Jangan naikkan DEBUG untuk root logger.
Jangan aktifkan SQL parameter logging yang membocorkan PII.
Runtime log level bisa dilakukan melalui:
- admin endpoint internal
- JMX
- framework actuator/admin feature
- config reload
- platform-specific mechanism
Risikonya:
- log volume melonjak
- cost meningkat
- sensitive data muncul
- CPU/IO overhead naik
- signal penting tertutup noise
- lupa dikembalikan ke INFO
Internal verification:
Apakah runtime log level change diizinkan?
Siapa yang boleh mengubah?
Apakah ada audit trail?
Apakah ada TTL otomatis?
Apakah ada runbook?
Apakah perubahan berlaku per pod atau semua replica?
Apakah root logger bisa diubah?
JAX-RS/Jakarta REST Logging Integration
Dalam aplikasi JAX-RS, logging perlu hadir pada boundary utama:
- request masuk
- response keluar
- exception mapping
- resource method
- service layer
- dependency adapter
Komponen yang sering terlibat:
ContainerRequestFilterContainerResponseFilterExceptionMapper- Servlet filter
- Jersey/RESTEasy filter/interceptor
- HTTP client filters
Contoh request filter konseptual:
@Provider
public class RequestContextLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
private static final Logger log = LoggerFactory.getLogger(RequestContextLoggingFilter.class);
@Override
public void filter(ContainerRequestContext requestContext) {
String correlationId = resolveCorrelationId(requestContext);
MDC.put("correlation_id", correlationId);
MDC.put("http_method", requestContext.getMethod());
MDC.put("http_path", requestContext.getUriInfo().getPath());
log.info("http request started");
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
try {
MDC.put("http_status", String.valueOf(responseContext.getStatus()));
log.info("http request completed");
} finally {
MDC.clear();
}
}
}
Contoh ini belum production-complete. Masih perlu:
- route template, bukan raw path jika tersedia
- latency measurement
- sensitive query/header masking
- request body policy
- exception path handling
- trace/span correlation
- MDC cleanup robust
- async request handling
Logging Framework dan Container/Kubernetes
Di containerized environment, aplikasi sebaiknya menulis ke stdout/stderr kecuali ada alasan internal lain.
Pipeline umum:
Java process
-> stdout/stderr
-> container runtime log file
-> node log agent
-> central log backend
Implication:
- aplikasi tidak perlu tahu backend log final
- log rotation biasanya ditangani container runtime/node
- file appender lokal perlu perhatian disk pressure
- multiline stack trace perlu parsing agent yang benar
- JSON per line lebih aman
Untuk Kubernetes:
kubectl logs <pod>
hanya melihat log yang masih ada di container runtime. Untuk incident historis, bergantung pada log backend dan retention.
Internal verification:
Apakah aplikasi log ke stdout?
Apakah stderr dipakai untuk ERROR saja atau semua lewat stdout?
Apakah log collector membaca container logs?
Apakah multiline stack trace aman?
Apakah pod label ikut menjadi log metadata?
Apakah service/version/env tersedia sebagai resource fields?
Library Logs: Noise vs Signal
Framework dan dependency library sering menghasilkan log sendiri.
Contoh sumber:
- HTTP client
- JDBC driver
- Hibernate/JPA
- MyBatis
- Kafka client
- RabbitMQ client
- Redis client
- Camunda engine/client
- Kubernetes client
- cloud SDK
Library logs bisa membantu, tetapi sering terlalu verbose.
Contoh bahaya:
org.hibernate.SQL=DEBUG
Bisa sangat berguna saat debugging query, tetapi berbahaya jika:
- volume tinggi
- SQL parameter berisi data sensitif
- production overhead tinggi
- log cost melonjak
Prinsip:
Default library log level harus konservatif.
Debug logging library sebaiknya temporary, scoped, dan punya approval/runbook.
Performance Concern
Logging bisa mempengaruhi performance.
Sumber overhead:
- string concatenation sebelum level check
- serialization JSON
- stack trace generation
- exception logging berulang
- synchronous IO
- async queue pressure
- large message/body logging
- huge MDC map
- excessive DEBUG logs
- high-frequency loop logging
Contoh buruk:
for (OrderLine line : lines) {
log.info("processing line {}", line);
}
Jika order punya ribuan line dan traffic tinggi, log menjadi bottleneck dan cost driver.
Pattern lebih baik:
log.info("processing order lines order_id={} line_count={}", orderId, lines.size());
Detail line bisa dimunculkan hanya saat debug scoped dan aman.
Security and Privacy Concern
Logging framework tidak otomatis melindungi data sensitif.
Data yang sering bocor:
- Authorization header
- Cookie
- session ID
- access token
- refresh token
- API key
- password
- request body
- query parameter sensitif
- SQL parameter
- customer identity
- commercial pricing detail
- personal data
Contoh buruk:
log.info("incoming headers={}", requestContext.getHeaders());
log.info("request body={}", body);
log.error("failed SQL={} params={}", sql, params, ex);
Gunakan explicit allowlist, bukan blacklist.
Log field yang diketahui aman.
Jangan log seluruh object/request/header/map secara bebas.
Cost Concern
Logging cost biasanya berasal dari:
- ingestion volume
- storage retention
- indexing field
- query cost
- dashboard cost
- high-volume DEBUG logs
- duplicate stack traces
- noisy library logs
- request/response body logs
Senior engineer harus memahami bahwa setiap log line punya biaya.
Pertanyaan review:
Berapa kali log ini muncul per request?
Berapa request per second?
Apakah log ini berada dalam loop?
Apakah log ini mengandung stack trace besar?
Apakah field ini diindex?
Apakah log ini dibutuhkan untuk incident atau hanya developer convenience?
Common Anti-Patterns
Anti-pattern 1: Multiple Logging Providers
Gejala:
SLF4J warning saat startup.
Log format tidak konsisten.
Sebagian log hilang.
Akar masalah:
Dependency membawa lebih dari satu provider/binding.
Anti-pattern 2: Logging Whole Object
log.info("order={}", order);
Risiko:
- PII leakage
- payload besar
- recursive object graph
- unstable format
- expensive serialization
Anti-pattern 3: Catch, Log, and Swallow
try {
process();
} catch (Exception ex) {
log.error("failed", ex);
}
Risiko:
- failure disembunyikan
- transaction tetap dianggap sukses
- incident sulit dideteksi
Anti-pattern 4: Log and Throw Everywhere
catch (Exception ex) {
log.error("failed in DAO", ex);
throw ex;
}
Jika setiap layer melakukan ini, satu failure menghasilkan banyak stack trace duplicate.
Anti-pattern 5: Root Logger DEBUG di Production
Risiko:
- volume ekstrem
- PII leakage
- performance degradation
- alert/log backend pressure
Anti-pattern 6: Log Format Berubah Tanpa Review
Mengubah JSON field name bisa merusak:
- dashboard
- saved query
- alert rule
- incident runbook
- downstream parser
Production Review Checklist
Gunakan checklist ini saat mereview aplikasi Java/JAX-RS.
API and Implementation
[ ] Logging API utama jelas.
[ ] Logging implementation utama jelas.
[ ] Tidak ada multiple SLF4J provider warning.
[ ] Dependency tree tidak mengandung bridge loop.
[ ] Library logs diarahkan ke pipeline utama.
Configuration
[ ] Config logging termuat dari classpath yang benar.
[ ] Root level production bukan DEBUG/TRACE.
[ ] Package-specific level masuk akal.
[ ] Runtime log level change punya control dan audit.
[ ] Config berbeda environment dikontrol dengan aman.
Output Format
[ ] Output production berupa JSON valid per line jika standard internal demikian.
[ ] Timestamp punya timezone dan konsisten.
[ ] service/environment/version tersedia.
[ ] logger/thread/level/message tersedia.
[ ] MDC fields masuk ke output.
[ ] Stack trace tidak merusak parsing.
Performance
[ ] Tidak ada expensive string concatenation di log disabled level.
[ ] Tidak ada high-frequency INFO log dalam loop besar.
[ ] Async appender behavior dipahami.
[ ] Queue full/drop policy jelas.
[ ] Shutdown flush behavior jelas.
Privacy and Security
[ ] Header sensitif tidak dilog.
[ ] Body logging dibatasi/disabled sesuai policy.
[ ] SQL parameter sensitif tidak dilog.
[ ] Object besar tidak dilog mentah.
[ ] Redaction/masking utility tersedia dan dipakai.
Operability
[ ] Log bisa dikorelasikan dengan trace/request/correlation ID.
[ ] Log bisa dicari berdasarkan tenant/business key jika aman.
[ ] Log level cukup untuk incident debugging.
[ ] Library log noise terkendali.
[ ] Log backend menampilkan fields sesuai schema.
Internal Verification Checklist
Gunakan checklist ini di codebase/team internal. Jangan mengasumsikan jawabannya sebelum dicek.
Dependency and Framework
[ ] Apakah aplikasi memakai SLF4J API?
[ ] Apakah backend logging Logback, Log4j2, JUL, atau kombinasi?
[ ] Apakah ada multiple provider warning saat startup?
[ ] Apakah dependency tree punya bridge library?
[ ] Apakah ada bridge loop risk?
[ ] Apakah framework JAX-RS/Jersey/RESTEasy/container punya logging sendiri?
Configuration Files
[ ] Di mana file konfigurasi logging berada?
[ ] Apakah config berbeda per environment?
[ ] Apakah production config berbeda dari local/dev?
[ ] Apakah root logger level production jelas?
[ ] Apakah package-level override terdokumentasi?
[ ] Apakah runtime log level change didukung?
Output and Collector
[ ] Apakah log keluar ke stdout, stderr, file, atau network appender?
[ ] Apakah container/Kubernetes menangkap output dengan benar?
[ ] Apakah log collector menjaga JSON fields?
[ ] Apakah multiline stack trace diparse benar?
[ ] Apakah service/environment/version otomatis ditambahkan?
[ ] Apakah pod/deployment metadata ikut masuk ke log backend?
MDC and Correlation
[ ] Apakah MDC dipakai?
[ ] Field apa saja yang dimasukkan ke MDC?
[ ] Apakah MDC dibersihkan di akhir request?
[ ] Apakah async/thread pool propagation didukung?
[ ] Apakah trace_id/span_id muncul di log?
[ ] Apakah correlation_id/request_id muncul di log?
Security and Cost
[ ] Apakah ada secure logging guideline?
[ ] Apakah ada masking/redaction library internal?
[ ] Apakah body/header/query logging dilarang atau dibatasi?
[ ] Apakah ada monitoring log volume per service?
[ ] Apakah DEBUG logging production punya approval/TTL?
[ ] Apakah audit log dipisah dari application log?
Senior Engineer Questions
Saat melihat logging setup service baru, tanyakan:
Apa API logging yang dipakai application code?
Apa implementation logging yang aktif di runtime?
Apakah logs dari framework/library masuk format yang sama?
Apakah output log valid JSON?
Apakah MDC masuk ke JSON fields?
Apakah correlation ID dan trace ID muncul di semua log penting?
Apa yang terjadi saat log backend/collector lambat?
Apakah async queue bisa drop ERROR logs?
Apakah runtime log level change tersedia dan diaudit?
Apa policy untuk request body, headers, SQL params, dan PII?
Berapa volume log per service per hari?
Apa log paling noisy?
Apa contoh incident terakhir yang sulit karena logging kurang?
Kesimpulan
Ekosistem logging Java memiliki banyak lapisan. Untuk senior backend engineer, yang penting bukan sekadar tahu SLF4J atau Logback, tetapi mampu menilai apakah logging pipeline production benar-benar mendukung operasi.
Prinsip utama:
Gunakan API logging yang konsisten.
Pastikan implementation tunggal dan jelas.
Bridge library harus disengaja, bukan kebetulan.
Output production harus machine-readable.
MDC harus masuk ke output.
Exception harus dilog dengan stack trace dan context yang benar.
Async logging harus punya failure policy.
Runtime log level harus dikontrol.
Library logs harus dikendalikan.
Privacy dan cost adalah bagian dari desain logging.
Di part berikutnya, kita masuk lebih dalam ke structured logging: bagaimana mendesain JSON log field yang stabil, queryable, privacy-aware, dan berguna untuk incident reconstruction di sistem Java/JAX-RS enterprise.
You just completed lesson 05 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.