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

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.

14 min read2601 words
PrevNext
Lesson 0562 lesson track01–12 Start Here
#java#slf4j#logback#log4j2+3 more

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:

LayerFungsiContoh
Logging APIInterface yang dipakai application codeSLF4J API, JUL API
Logging implementationEngine yang benar-benar menulis logLogback, 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:

  • trace
  • debug
  • info
  • warn
  • error
  • 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:

BridgeFungsi
jul-to-slf4jJUL logs diarahkan ke SLF4J
jcl-over-slf4jApache Commons Logging diarahkan ke SLF4J
log4j-over-slf4jLog4j 1.x API diarahkan ke SLF4J
log4j-to-slf4jLog4j2 API diarahkan ke SLF4J
log4j-slf4j2-implSLF4J 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.

KonsepFungsi
Appender/HandlerTujuan output log
Layout/EncoderFormat output log
FilterMenentukan log event diterima/dibuang/diubah
Logger configLevel 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:

  • ContainerRequestFilter
  • ContainerResponseFilter
  • ExceptionMapper
  • 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.

Lesson Recap

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.

Continue The Track

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