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

Log Level Discipline

Log Level Discipline for Production Systems

Disiplin penggunaan TRACE, DEBUG, INFO, WARN, ERROR, expected error, unexpected error, retriable error, non-retriable error, log spam, missing log, dan review checklist untuk Java/JAX-RS production systems.

16 min read3084 words
PrevNext
Lesson 0762 lesson track01–12 Start Here
#logging#log-level#production-debugging#alert-noise+1 more

Cheatsheet Observability Part 007 — Log Level Discipline

Tujuan Part Ini

Part ini membahas log level discipline: kemampuan menentukan level log yang tepat agar log benar-benar berguna untuk operasi production, incident debugging, audit investigation, dan diagnosis distributed systems.

Fokusnya bukan sekadar hafalan bahwa INFO untuk informasi dan ERROR untuk error. Fokusnya adalah bagaimana log level mempengaruhi:

  • signal quality
  • alert noise
  • incident triage speed
  • debugging evidence
  • log ingestion cost
  • privacy exposure
  • support readiness
  • production confidence

Dalam sistem enterprise Java 17+ / JAX-RS / Jakarta RESTful Web Services, log level yang salah bisa menciptakan dua masalah ekstrem:

Terlalu banyak log  -> noise, cost tinggi, query lambat, alert fatigue
Terlalu sedikit log -> blind spot, incident sulit direkonstruksi, RCA lemah

Setelah membaca part ini, Anda harus mampu:

  • membedakan kapan memakai TRACE, DEBUG, INFO, WARN, dan ERROR
  • mengenali expected error vs unexpected error
  • mengenali retriable error vs non-retriable error
  • menentukan apakah sebuah log event harus operational, business, security, atau audit signal
  • menghindari log spam pada retry loop, polling, queue consumer, scheduler, dan hot path
  • menghindari missing log pada state transition, external call, failed dependency, dan data mutation
  • mereview PR yang menambah logging dari sudut severity, usefulness, cost, privacy, dan alertability

Ringkasan Mental Model

Log level adalah severity contract antara developer dan operator.

Saat engineer membaca log production, level log memberi sinyal:

TRACE -> detail sangat rendah untuk debugging lokal/diagnostik terbatas
DEBUG -> detail teknis untuk debugging, biasanya off di production
INFO  -> event operasional normal yang penting untuk memahami lifecycle
WARN  -> kondisi tidak ideal, tetapi sistem masih bisa lanjut
ERROR -> operasi gagal atau sistem kehilangan kemampuan yang seharusnya berhasil

Level log bukan gaya bahasa. Level log adalah keputusan operasional.

Contoh buruk:

log.error("User entered invalid quote id");

Jika invalid quote ID adalah validasi input normal, ini bukan ERROR. Ia bisa jadi INFO atau WARN tergantung konteks dan security relevance.

Contoh lebih baik:

log.info("quote lookup rejected due to invalid format quote_id_present={} actor_id={} correlation_id={}",
        quoteId != null,
        actorId,
        correlationId);

Contoh buruk lain:

log.info("Processing item {}", itemId);

Jika ini terjadi jutaan kali per jam dalam batch job, ia bisa menjadi log spam. Mungkin lebih baik menjadi metric counter atau debug log yang bisa diaktifkan sementara.

Log level discipline berarti setiap log menjawab pertanyaan:

Apakah event ini normal atau abnormal?
Apakah operator harus peduli?
Apakah event ini membantu rekonstruksi incident?
Apakah level ini akan menyebabkan alert/noise/cost yang tidak perlu?
Apakah field-nya aman dan cukup untuk debugging?

Log Level sebagai Operational Contract

Log level harus dipahami sebagai kontrak terhadap pembaca log.

LevelArti operasionalUmumnya aktif di production?Risiko jika salah pakai
TRACEDetail eksekusi sangat granularTidakVolume ekstrem, data sensitif, noise
DEBUGDetail teknis untuk debuggingBiasanya tidakCost tinggi, bocor internal state
INFOEvent normal yang pentingYaJika terlalu banyak menjadi noise
WARNKondisi abnormal yang recoverableYaJika terlalu banyak menjadi alert fatigue
ERRORFailure yang butuh perhatianYaJika expected error dijadikan ERROR, alert palsu

Log level harus konsisten antar service. Jika satu service memakai ERROR untuk validation failure dan service lain memakai INFO, incident triage akan bias.

Prinsip utama

Level log harus merepresentasikan dampak operasional, bukan emosi developer.

Developer sering menulis ERROR karena code masuk catch. Ini tidak selalu benar.

Pertanyaan yang lebih baik:

Apakah operasi yang diminta gagal?
Apakah sistem kehilangan kemampuan penting?
Apakah failure memerlukan intervensi?
Apakah failure akan terlihat oleh user/customer?
Apakah retry otomatis sudah cukup?
Apakah ini expected dalam domain?

TRACE

TRACE adalah level paling detail. Biasanya digunakan untuk memahami alur internal step-by-step.

Contoh:

log.trace("entered price rule evaluation rule_id={} quote_id={}", ruleId, quoteId);
log.trace("candidate discount rule evaluated rule_id={} matched={}", ruleId, matched);

TRACE cocok untuk:

  • debugging lokal
  • troubleshooting sangat spesifik
  • library/framework internals
  • short-lived diagnostic session
  • reproduksi bug yang sulit

TRACE tidak cocok untuk:

  • production default
  • high-throughput endpoint
  • logging request/response body
  • logging loop item-level
  • logging SQL parameter detail
  • logging token/header/session data

Failure mode TRACE

TRACE sering menjadi masalah ketika:

TRACE aktif di production terlalu lama
TRACE mengandung PII/secrets
TRACE dipakai pada loop besar
TRACE menghasilkan log lebih mahal daripada traffic utama
TRACE membuat log backend sulit dipakai saat incident

Java/JAX-RS concern

Di JAX-RS endpoint high traffic, TRACE pada filter, mapper, serializer, atau dependency client bisa menghasilkan volume besar.

Contoh berbahaya:

log.trace("request body={}", requestBody);

Risiko:

  • PII leakage
  • token leakage
  • payload besar
  • ingestion cost tinggi
  • data compliance issue

Internal verification checklist

[ ] Apakah TRACE disabled by default di production?
[ ] Apakah ada runtime log level change dengan approval/process?
[ ] Apakah TRACE pernah digunakan untuk request body, response body, SQL parameter, atau token?
[ ] Apakah ada TTL/process untuk menurunkan log level setelah debugging?
[ ] Apakah log backend bisa menahan volume jika TRACE sementara diaktifkan?
[ ] Apakah TRACE event punya privacy guardrail?

DEBUG

DEBUG dipakai untuk detail teknis yang membantu developer memahami state internal, tetapi biasanya tidak dibutuhkan untuk operasi normal.

Contoh:

log.debug("pricing rule candidate selected quote_id={} rule_count={} strategy={}",
        quoteId,
        rules.size(),
        strategyName);

DEBUG cocok untuk:

  • detail branch logic
  • computed internal state
  • feature flag evaluation
  • decision tree debugging
  • dependency request composition tanpa payload sensitif
  • local/staging diagnostics

DEBUG tidak cocok untuk:

  • event lifecycle penting yang harus selalu tersedia
  • audit evidence
  • business state transition penting
  • incident-critical dependency failure
  • security-relevant event

Kesalahan umum

Kesalahan besar adalah menaruh event penting di DEBUG.

Contoh buruk:

log.debug("order state changed from {} to {} order_id={}", oldState, newState, orderId);

Jika state transition penting untuk audit/debugging, jangan taruh di DEBUG. Minimal INFO, atau audit log terpisah jika merupakan business evidence.

Contoh lebih baik:

log.info("order state transitioned order_id={} from_state={} to_state={} transition={} actor_type={}",
        orderId,
        oldState,
        newState,
        transitionName,
        actorType);

Guarded debug logging

Untuk computation mahal, gunakan guard:

if (log.isDebugEnabled()) {
    log.debug("pricing candidates quote_id={} candidates={}", quoteId, buildDebugSummary(candidates));
}

Parameterized logging mencegah string interpolation awal, tetapi tidak selalu mencegah computation argument mahal.

Buruk:

log.debug("large payload summary={}", expensiveSerialize(payload));

Lebih aman:

if (log.isDebugEnabled()) {
    log.debug("large payload summary={}", safeSummary(payload));
}

Internal verification checklist

[ ] Apakah DEBUG disabled by default di production?
[ ] Apakah DEBUG tidak menyimpan event lifecycle penting?
[ ] Apakah DEBUG tidak mengandung PII, token, body lengkap, atau SQL parameter sensitif?
[ ] Apakah expensive debug argument dijaga dengan isDebugEnabled()?
[ ] Apakah runtime log level change dapat dibatasi per logger/package?
[ ] Apakah ada prosedur menyalakan DEBUG sementara saat incident?

INFO

INFO adalah level utama untuk event operasional normal yang penting.

INFO bukan tempat untuk semua hal. INFO adalah tempat untuk event yang membantu memahami lifecycle production.

Contoh event INFO yang baik:

http.request.completed
quote.pricing.started
quote.pricing.completed
order.submission.accepted
order.state.transitioned
kafka.message.published
rabbitmq.message.consumed
background.job.completed
workflow.task.completed
cache.warmup.completed

Contoh Java:

log.info("quote pricing completed quote_id={} tenant_id={} price_version={} latency_ms={} item_count={}",
        quoteId,
        tenantId,
        priceVersion,
        latencyMs,
        itemCount);

INFO harus menjawab:

Apa yang terjadi?
Untuk entity mana?
Dalam konteks request/trace/correlation mana?
Berhasil atau gagal?
Berapa durasinya?
Apa outcome pentingnya?

INFO bukan log semua step

Buruk:

log.info("Entering method A");
log.info("Calling method B");
log.info("Received result from method B");
log.info("Mapping result");
log.info("Returning response");

Ini noise. Lebih baik gunakan satu lifecycle event yang padat.

Lebih baik:

log.info("quote retrieval completed quote_id={} tenant_id={} source={} latency_ms={} status={}",
        quoteId,
        tenantId,
        source,
        latencyMs,
        status);

INFO untuk business lifecycle

Dalam CPQ/order management, INFO sering cocok untuk business lifecycle event:

quote.created
quote.priced
quote.submitted_for_approval
quote.approved
quote.rejected
quote.accepted
order.created
order.validated
order.decomposed
fulfillment.started
fallout.created
cancellation.requested
amendment.requested

Namun jika event tersebut harus menjadi compliance evidence, ia juga harus masuk audit log, bukan hanya application INFO log.

INFO untuk dependency lifecycle

Contoh:

log.info("kafka publish completed topic={} event_type={} event_id={} correlation_id={} latency_ms={}",
        topic,
        eventType,
        eventId,
        correlationId,
        latencyMs);

Tetapi hati-hati: publish per message pada high-throughput topic bisa mahal. Untuk volume tinggi, metric + sampled log mungkin lebih tepat.

Internal verification checklist

[ ] Apakah INFO berisi lifecycle event penting, bukan method tracing?
[ ] Apakah setiap INFO punya event name atau message yang stabil?
[ ] Apakah INFO membawa correlation_id/trace_id/request_id?
[ ] Apakah business entity key seperti quote_id/order_id dicatat sesuai policy?
[ ] Apakah INFO tidak terlalu banyak pada hot path?
[ ] Apakah event volume INFO dipantau?
[ ] Apakah event yang harus audit masuk audit log terpisah?

WARN

WARN berarti kondisi abnormal atau tidak ideal, tetapi sistem masih bisa melanjutkan atau melakukan recovery.

WARN cocok untuk:

  • fallback digunakan
  • retry akan dilakukan
  • dependency lambat tetapi belum gagal final
  • invalid external event yang bisa di-skip
  • cache unavailable tetapi DB fallback berhasil
  • idempotency duplicate detected
  • stale data detected tetapi request masih bisa diproses
  • queue message redelivered
  • near-capacity condition

Contoh:

log.warn("dependency call retry scheduled dependency={} operation={} attempt={} max_attempts={} reason={} correlation_id={}",
        "pricing-service",
        "calculate-price",
        attempt,
        maxAttempts,
        reason,
        correlationId);

WARN bukan ERROR ringan

WARN bukan untuk failure final yang membuat operasi gagal. Jika order submission gagal total, itu ERROR. Jika retry internal terjadi dan akhirnya berhasil, retry attempt bisa WARN atau DEBUG tergantung volume dan policy.

Retry logging problem

Retry loop bisa menghasilkan WARN spam.

Buruk:

for (int attempt = 1; attempt <= 5; attempt++) {
    try {
        callDependency();
        return;
    } catch (Exception ex) {
        log.warn("dependency failed", ex);
    }
}

Jika dependency down dan traffic tinggi, ini menghasilkan ribuan stack trace WARN.

Lebih baik:

log.warn("dependency retry scheduled dependency={} operation={} attempt={} max_attempts={} error_class={} error_code={}",
        dependency,
        operation,
        attempt,
        maxAttempts,
        ex.getClass().getName(),
        errorCode);

Dan hanya log stack trace pada final failure atau sampled diagnostic.

WARN untuk fallback

Fallback harus terlihat karena bisa menyembunyikan degradasi.

log.warn("cache lookup failed using database fallback cache={} operation={} key_hash={} correlation_id={}",
        "redis-quote-cache",
        "getQuoteSummary",
        safeKeyHash,
        correlationId);

Jika fallback sering terjadi, metric harus ada:

cache_fallback_total{cache="quote-cache", reason="redis_timeout"}

WARN untuk duplicate/idempotency

Duplicate event dalam messaging bisa expected.

log.warn("duplicate event ignored event_id={} event_type={} idempotency_key={} consumer={} offset={}",
        eventId,
        eventType,
        idempotencyKey,
        consumerName,
        offset);

Jika duplicate adalah normal akibat at-least-once delivery, jangan jadikan ERROR.

Internal verification checklist

[ ] Apakah WARN dipakai untuk recoverable abnormal condition?
[ ] Apakah WARN tidak menjadi spam pada retry loop?
[ ] Apakah WARN punya reason, attempt, dependency, dan correlation field?
[ ] Apakah WARN yang sering terjadi punya metric?
[ ] Apakah fallback path terlihat?
[ ] Apakah duplicate/idempotency behavior tidak salah diklasifikasikan sebagai ERROR?
[ ] Apakah WARN tertentu dipakai alert? Jika iya, apakah actionable?

ERROR

ERROR berarti operasi gagal atau sistem kehilangan kemampuan yang seharusnya tersedia.

ERROR cocok untuk:

  • request gagal karena unexpected exception
  • dependency call gagal final setelah retry habis
  • database transaction gagal
  • message processing gagal dan masuk DLQ
  • order lifecycle transition gagal
  • audit write gagal
  • security control gagal
  • workflow job gagal final
  • data consistency violation
  • invariant violation

Contoh:

log.error("order submission failed order_id={} quote_id={} tenant_id={} error_code={} retryable={} correlation_id={}",
        orderId,
        quoteId,
        tenantId,
        errorCode,
        retryable,
        correlationId,
        ex);

ERROR harus punya field yang cukup untuk triage:

service
operation/event
correlation_id
trace_id
request_id
tenant_id jika allowed
business key jika allowed
error_code
exception_class
retryable
dependency jika dependency failure
attempt jika retry exhausted
latency_ms jika relevant

ERROR bukan semua exception

Tidak semua exception harus ERROR.

Contoh expected validation exception:

throw new ValidationException("invalid quote status");

Jika invalid input dari user adalah expected, response 400 bukan ERROR. Biasanya cukup INFO atau WARN jika ada indikasi abuse/security.

Contoh authorization failure:

  • single normal forbidden request: mungkin INFO atau security event
  • repeated forbidden attempts: security signal, mungkin WARN
  • auth subsystem failure: ERROR

ERROR dan stack trace

Stack trace mahal tetapi penting untuk unexpected failure.

Gunakan stack trace untuk:

  • unexpected exception
  • final failure after retry exhausted
  • invariant violation
  • unrecoverable dependency failure
  • data corruption risk

Hindari stack trace penuh untuk:

  • validation error normal
  • expected duplicate event
  • known business rule rejection
  • high-frequency timeout attempt yang akan retry
  • client cancellation normal

ERROR harus tidak menggandakan stack trace

Buruk:

try {
    service.submit(order);
} catch (Exception ex) {
    log.error("submit failed", ex);
    throw ex;
}

Lalu di ExceptionMapper:

log.error("request failed", ex);

Hasilnya stack trace ganda.

Lebih baik:

  • log context di boundary yang tepat
  • jangan log dan rethrow tanpa menambah value
  • gunakan exception mapper sebagai central error logging untuk request failure
  • service layer log hanya jika ada domain/dependency context penting yang tidak tersedia di mapper

Internal verification checklist

[ ] Apakah ERROR benar-benar final failure atau unexpected failure?
[ ] Apakah expected 4xx tidak diperlakukan sebagai ERROR secara default?
[ ] Apakah stack trace tidak digandakan antar layer?
[ ] Apakah ERROR membawa error_code dan correlation_id?
[ ] Apakah dependency final failure memiliki dependency name dan operation?
[ ] Apakah retry exhausted dicatat sebagai final failure?
[ ] Apakah ERROR dipetakan ke metric/alert yang meaningful?
[ ] Apakah ERROR tidak mengandung PII/secrets dalam message atau stack trace custom?

FATAL Awareness

Beberapa framework memiliki FATAL, sebagian tidak. Dalam SLF4J standar, level umum adalah TRACE, DEBUG, INFO, WARN, ERROR.

Jika backend mendukung FATAL, gunakan sangat hati-hati.

FATAL berarti:

Process/service tidak dapat melanjutkan secara aman.

Contoh kondisi fatal:

  • konfigurasi critical hilang saat startup
  • schema/migration incompatible sehingga service tidak boleh running
  • secret provider unavailable saat boot dan service tidak bisa authenticate dependency
  • corruption detected yang membuat proses harus dihentikan
  • invariant platform yang membuat semua request tidak aman

Dalam banyak Java service, kondisi fatal biasanya direpresentasikan sebagai:

ERROR log + process exit + failed readiness/startup probe

Bukan log level khusus.

Internal verification checklist

[ ] Apakah logging framework memiliki FATAL atau tidak?
[ ] Apakah startup failure dicatat jelas sebelum process exit?
[ ] Apakah readiness/startup probe mencerminkan fatal misconfiguration?
[ ] Apakah fatal condition tidak disembunyikan sebagai WARN?
[ ] Apakah orchestrator/Kubernetes menangkap restart reason?

Operational Event vs Business Event vs Security Event vs Audit Event

Log level juga bergantung pada jenis event.

Event typeTujuanContohLevel umum
Operational eventMemahami behavior teknis servicerequest completed, dependency retryINFO/WARN/ERROR
Business eventMemahami lifecycle domainquote approved, order submittedINFO + possibly audit
Security eventMemahami akses/ancamanforbidden access, token failureINFO/WARN/ERROR
Audit eventCompliance evidencewho changed quote statusAudit channel, bukan sekadar level

Business event

Contoh:

log.info("quote approved quote_id={} tenant_id={} actor_id={} approval_level={} correlation_id={}",
        quoteId,
        tenantId,
        actorId,
        approvalLevel,
        correlationId);

Tetapi untuk compliance:

audit_event: quote.approved
actor_id
target_entity
before_state
after_state
time
reason
source_ip

Application INFO log bukan pengganti audit log jika policy membutuhkan audit evidence.

Security event

Contoh:

log.warn("authorization denied actor_id={} action={} resource_type={} resource_id_present={} reason={} correlation_id={}",
        actorId,
        action,
        resourceType,
        resourceId != null,
        reason,
        correlationId);

Hati-hati jangan log token atau credential.

Internal verification checklist

[ ] Apakah event business penting masuk application log dan/atau audit log sesuai policy?
[ ] Apakah security event tidak bocor token/header/cookie?
[ ] Apakah audit event tidak hanya mengandalkan INFO log biasa?
[ ] Apakah level security event disepakati dengan security team?
[ ] Apakah business event volume tidak membanjiri log backend?

Expected Error vs Unexpected Error

Ini salah satu pemisahan paling penting.

Expected error

Expected error adalah kondisi gagal yang merupakan bagian normal dari domain atau kontrak API.

Contoh:

  • invalid request body
  • missing required field
  • quote not found
  • quote already submitted
  • duplicate idempotency key
  • forbidden operation karena role tidak cukup
  • optimistic lock conflict yang bisa retry
  • business rule rejection

Expected error tidak otomatis ERROR.

Contoh:

log.info("quote submission rejected quote_id={} reason={} actor_id={} correlation_id={}",
        quoteId,
        "QUOTE_ALREADY_SUBMITTED",
        actorId,
        correlationId);

Atau jika abnormal/fraud/security risk:

log.warn("quote submission rejected due to suspicious actor mismatch quote_id={} actor_id={} owner_id={} correlation_id={}",
        quoteId,
        actorId,
        ownerId,
        correlationId);

Unexpected error

Unexpected error adalah failure yang tidak seharusnya terjadi dalam operasi normal.

Contoh:

  • NullPointerException
  • database unavailable
  • migration mismatch
  • serialization failure karena incompatible schema
  • downstream timeout setelah retry exhausted
  • invariant violation
  • audit persistence failed
  • corrupted state transition

Unexpected error biasanya ERROR.

log.error("unexpected quote submission failure quote_id={} tenant_id={} error_code={} correlation_id={}",
        quoteId,
        tenantId,
        "QUOTE_SUBMISSION_UNEXPECTED_FAILURE",
        correlationId,
        ex);

HTTP status mapping

Tidak semua 4xx adalah INFO, dan tidak semua 5xx punya penyebab yang sama.

HTTP classDefault interpretationLogging guidance
2xxsuccessINFO for lifecycle/access log, not every internal step
3xxredirect/cache behaviorUsually access log only
400validation/client issueINFO, sometimes WARN
401auth missing/invalidINFO/WARN security-aware
403forbiddenINFO/WARN security-aware
404not foundINFO unless suspicious/noisy
409conflict/idempotency/state issueINFO/WARN depending domain
429rate limitedINFO/WARN + metric
499client closed requestedge/access log; often not app ERROR
500server failureERROR
502/503/504gateway/upstream issueERROR/WARN depending boundary and final impact

Internal verification checklist

[ ] Apakah expected 4xx tidak mencemari ERROR rate?
[ ] Apakah unexpected 5xx selalu memiliki ERROR log dan metric?
[ ] Apakah business rejection dibedakan dari technical failure?
[ ] Apakah conflict/idempotency cases punya log level yang konsisten?
[ ] Apakah client cancellation 499 tidak salah dianggap server ERROR?

Retriable Error vs Non-Retriable Error

Retry behavior harus terlihat di log, tetapi tidak boleh menciptakan noise.

Retriable error

Retriable error adalah failure yang mungkin berhasil jika dicoba lagi.

Contoh:

  • transient timeout
  • temporary network issue
  • database connection temporarily unavailable
  • broker publish timeout
  • optimistic locking conflict
  • rate limit dari dependency dengan backoff

Log guidance:

Intermediate retry attempt -> DEBUG/WARN depending severity and volume
Final retry exhausted       -> ERROR
Retry success after failure -> INFO or metric, depending usefulness

Contoh:

log.warn("dependency retry scheduled dependency={} operation={} attempt={} max_attempts={} backoff_ms={} error_class={} correlation_id={}",
        dependency,
        operation,
        attempt,
        maxAttempts,
        backoffMs,
        ex.getClass().getSimpleName(),
        correlationId);

Final:

log.error("dependency call failed after retries dependency={} operation={} attempts={} latency_ms={} correlation_id={}",
        dependency,
        operation,
        attempts,
        totalLatencyMs,
        correlationId,
        ex);

Non-retriable error

Non-retriable error tidak akan membaik dengan retry.

Contoh:

  • validation error
  • forbidden access
  • schema incompatible
  • missing required business state
  • permanent downstream rejection
  • invalid event version jika tidak ada compatibility handler

Log guidance:

  • business non-retryable rejection: INFO/WARN
  • technical non-retryable invariant violation: ERROR

Messaging context

Kafka/RabbitMQ consumer harus sangat disiplin.

Buruk:

log.error("message processing failed", ex);
throw ex; // broker redelivers forever

Lebih baik:

log.warn("message processing retry scheduled topic={} event_id={} attempt={} reason={} correlation_id={}",
        topic,
        eventId,
        attempt,
        reason,
        correlationId);

Final DLQ:

log.error("message moved to dlq topic={} dlq_topic={} event_id={} attempts={} error_code={} correlation_id={}",
        topic,
        dlqTopic,
        eventId,
        attempts,
        errorCode,
        correlationId,
        ex);

Internal verification checklist

[ ] Apakah retry attempt dan final failure dibedakan levelnya?
[ ] Apakah retry loop tidak menulis stack trace penuh setiap attempt?
[ ] Apakah retry metrics tersedia?
[ ] Apakah DLQ movement dicatat sebagai final outcome?
[ ] Apakah non-retriable business rejection tidak di-retry tanpa henti?
[ ] Apakah retryable flag ada dalam error log atau structured field?

Log Spam

Log spam adalah log yang volumenya tinggi tetapi value diagnostiknya rendah.

Sumber umum:

  • log per loop item
  • log per poll cycle
  • log per retry attempt dengan stack trace
  • log request body besar
  • log every cache hit
  • log every DB row
  • log every message consumed di high-throughput topic
  • debug enabled di production
  • repeated health check logs
  • noisy framework logs

Contoh log spam

for (OrderLine line : lines) {
    log.info("Processing order line order_id={} line_id={}", orderId, line.id());
}

Jika order punya ribuan line dan traffic tinggi, ini buruk.

Lebih baik:

log.info("order lines processed order_id={} line_count={} failed_count={} latency_ms={}",
        orderId,
        lines.size(),
        failedCount,
        latencyMs);

Jika butuh detail gagal:

log.warn("order line processing failed order_id={} line_id={} reason={} correlation_id={}",
        orderId,
        failedLineId,
        reason,
        correlationId);

Health check spam

Buruk:

log.info("Health check called");

Health checks bisa dipanggil sangat sering oleh Kubernetes, load balancer, dan monitoring probe.

Lebih baik:

Tidak log per health check success.
Log only health check failure or state transition healthy <-> unhealthy.

Cache spam

Buruk:

log.info("Cache hit key={}", key);

Lebih baik:

cache hit/miss -> metric
cache failure/fallback -> WARN log
cache warmup completed -> INFO log

Internal verification checklist

[ ] Apakah ada INFO log di loop besar?
[ ] Apakah health check success dilog?
[ ] Apakah cache hit/miss dilog per request, bukan metric?
[ ] Apakah retry menghasilkan stack trace spam?
[ ] Apakah framework logger terlalu verbose?
[ ] Apakah top noisy logger diketahui?
[ ] Apakah log volume per service dipantau?

Missing Log

Missing log adalah kondisi ketika event penting tidak tercatat.

Missing log lebih berbahaya daripada log spam saat incident karena menciptakan blind spot.

Contoh missing log penting:

  • request gagal tanpa correlation ID
  • state transition tidak tercatat
  • order stuck tanpa aging event
  • Kafka publish berhasil/gagal tidak terlihat
  • DLQ movement tidak terlihat
  • dependency timeout tidak punya dependency name
  • fallback path tidak terlihat
  • config change tidak terlihat
  • feature flag decision tidak terlihat saat incident
  • audit-relevant change hanya terjadi di database tanpa audit event

Boundary yang wajib dipikirkan

HTTP inbound
HTTP outbound
DB transaction
Kafka/RabbitMQ publish
Kafka/RabbitMQ consume
Redis fallback/failure
Camunda job/task failure
background job start/end/failure
state transition
external side effect
security decision
audit-relevant mutation

Tidak semua boundary perlu INFO log detail, tetapi semua boundary penting harus observable melalui kombinasi log, metric, trace, audit, atau event.

Internal verification checklist

[ ] Apakah setiap request failure punya log dengan correlation_id?
[ ] Apakah setiap state transition penting tercatat?
[ ] Apakah setiap external side effect punya success/failure evidence?
[ ] Apakah Kafka/RabbitMQ DLQ movement terlihat?
[ ] Apakah retry exhausted terlihat?
[ ] Apakah fallback path terlihat?
[ ] Apakah audit-relevant mutation masuk audit log?
[ ] Apakah config/deployment/feature flag change bisa dikorelasikan dengan incident?

Wrong Log Level Anti-Patterns

Anti-pattern 1: Everything is ERROR

log.error("validation failed field=name");
log.error("quote not found quote_id={}", quoteId);
log.error("duplicate request ignored idempotency_key={}", key);

Dampak:

  • ERROR rate meaningless
  • alert fatigue
  • incident triage sulit
  • expected business behavior terlihat seperti outage

Anti-pattern 2: Everything is INFO

log.info("database unavailable");
log.info("message moved to dlq");
log.info("audit write failed");

Dampak:

  • failure critical tersembunyi
  • alert tidak terpicu
  • operator mengabaikan sinyal penting

Anti-pattern 3: Log and throw everywhere

catch (Exception ex) {
    log.error("failed", ex);
    throw ex;
}

Dampak:

  • duplicate stack trace
  • query noisy
  • root cause sulit ditemukan
  • cost tinggi

Anti-pattern 4: Log message tanpa structured field

log.error("Order failed");

Dampak:

  • tidak bisa dicari berdasarkan order_id, tenant_id, error_code, dependency, trace_id
  • tidak membantu support

Anti-pattern 5: Error message sebagai metric label

Ini bukan logging langsung, tetapi sering berhubungan.

Buruk:

http_errors_total{error_message="timeout after 5032ms calling /api/order/123"}

Gunakan error code stabil:

http_errors_total{error_code="DOWNSTREAM_TIMEOUT"}

Internal verification checklist

[ ] Apakah expected business rejection tidak menjadi ERROR?
[ ] Apakah critical failure tidak hanya INFO?
[ ] Apakah stack trace tidak diduplikasi di banyak layer?
[ ] Apakah setiap ERROR punya structured fields?
[ ] Apakah error_code stabil dipakai, bukan raw exception message sebagai dimension?

Log Level di JAX-RS Request Lifecycle

Contoh lifecycle request:

NGINX / ingress
  -> Servlet container
  -> JAX-RS filter
  -> resource method
  -> service layer
  -> database / Redis / messaging / downstream
  -> ExceptionMapper
  -> response

Request success

Biasanya cukup access log atau request completed log:

log.info("http request completed method={} route={} status={} latency_ms={} correlation_id={} trace_id={}",
        method,
        routeTemplate,
        status,
        latencyMs,
        correlationId,
        traceId);

Validation failure

log.info("http request rejected method={} route={} status=400 error_code={} correlation_id={}",
        method,
        routeTemplate,
        errorCode,
        correlationId);

Authorization denied

log.warn("http request forbidden method={} route={} actor_id={} action={} resource_type={} reason={} correlation_id={}",
        method,
        routeTemplate,
        actorId,
        action,
        resourceType,
        reason,
        correlationId);

Depending on policy, single forbidden could be INFO while suspicious pattern becomes WARN. Verify internally.

Unexpected exception

log.error("http request failed method={} route={} status=500 error_code={} correlation_id={} trace_id={}",
        method,
        routeTemplate,
        errorCode,
        correlationId,
        traceId,
        ex);

Client cancellation

499 is often observed at NGINX/ingress level. It may not be an application ERROR.

Internal question:

Did app continue expensive work after client disconnected?
Did gateway timeout before app finished?
Is cancellation normal or due to latency regression?

Log Level for PostgreSQL/MyBatis/JPA/JDBC

Database-related log level should reflect outcome and impact.

Slow query

Slow query is not always ERROR.

log.warn("slow database query operation={} latency_ms={} threshold_ms={} rows={} correlation_id={}",
        operation,
        latencyMs,
        thresholdMs,
        rowCount,
        correlationId);

If request still succeeds, WARN may be enough. If query causes request failure, final request failure is ERROR.

Pool exhaustion

Pool exhaustion usually operationally serious.

log.error("database connection pool exhausted pool={} active={} idle={} pending={} timeout_ms={} correlation_id={}",
        poolName,
        active,
        idle,
        pending,
        timeoutMs,
        correlationId,
        ex);

Optimistic lock conflict

Often expected in concurrent systems.

log.info("optimistic lock conflict detected entity_type={} entity_id={} operation={} retryable={} correlation_id={}",
        entityType,
        entityId,
        operation,
        true,
        correlationId);

If retry exhausted:

log.warn("optimistic lock retry exhausted entity_type={} entity_id={} attempts={} correlation_id={}",
        entityType,
        entityId,
        attempts,
        correlationId);

SQL logging warning

Do not enable raw SQL parameter logging in production without privacy review.

Internal verification:

[ ] Are SQL parameters logged?
[ ] Can SQL logs expose customer data, commercial pricing, identifiers, or secrets?
[ ] Is slow query correlation possible without raw parameter leakage?

Log Level for Kafka/RabbitMQ

Messaging requires extra discipline because failures can repeat.

Message consumed successfully

For low/medium volume:

log.info("message processed topic={} event_type={} event_id={} latency_ms={} correlation_id={}",
        topic,
        eventType,
        eventId,
        latencyMs,
        correlationId);

For high volume, prefer metrics and sampled logs.

Redelivery

log.warn("message redelivered queue={} event_id={} redelivery_count={} correlation_id={}",
        queue,
        eventId,
        redeliveryCount,
        correlationId);

DLQ

log.error("message moved to dlq source={} dlq={} event_type={} event_id={} attempts={} error_code={} correlation_id={}",
        source,
        dlq,
        eventType,
        eventId,
        attempts,
        errorCode,
        correlationId,
        ex);

Duplicate message

If duplicate is expected due to at-least-once delivery:

log.info("duplicate message ignored event_id={} idempotency_key={} consumer={} correlation_id={}",
        eventId,
        idempotencyKey,
        consumerName,
        correlationId);

If duplicate indicates producer bug or ordering issue, WARN may be justified.


Log Level for Redis

Redis is often used as cache, lock, idempotency store, rate limiter, or stream backend.

Cache miss

Do not log every cache miss at INFO in high-throughput path. Use metric.

cache_miss_total{cache="quote-summary"}

Redis timeout with DB fallback

log.warn("redis cache unavailable using fallback operation={} cache={} key_hash={} correlation_id={}",
        operation,
        cacheName,
        keyHash,
        correlationId);

Redis lock failure

If expected contention:

log.info("distributed lock not acquired lock_name={} owner={} operation={} correlation_id={}",
        lockName,
        owner,
        operation,
        correlationId);

If lock system broken:

log.error("distributed lock operation failed lock_name={} operation={} correlation_id={}",
        lockName,
        operation,
        correlationId,
        ex);

Key privacy

Never log raw keys if they include user/order/quote/customer data unless policy explicitly permits. Prefer hash or key pattern.


Log Level for Camunda/Workflow

Workflow systems create long-running state. Log level should distinguish expected business path from stuck/failure path.

Normal task completion

log.info("workflow task completed process_instance_id={} task_type={} business_key={} latency_ms={} correlation_id={}",
        processInstanceId,
        taskType,
        businessKey,
        latencyMs,
        correlationId);

Failed job with retry remaining

log.warn("workflow job failed retry scheduled process_instance_id={} job_type={} retries_left={} error_code={} correlation_id={}",
        processInstanceId,
        jobType,
        retriesLeft,
        errorCode,
        correlationId);

Incident created or retries exhausted

log.error("workflow incident created process_instance_id={} activity_id={} business_key={} error_code={} correlation_id={}",
        processInstanceId,
        activityId,
        businessKey,
        errorCode,
        correlationId,
        ex);

Human task aging

Human task aging is usually metric/dashboard/SLO first. Log state transition or breach event, not every polling scan.


Log Level in Docker/Kubernetes/AWS/Azure/on-prem

In containerized production systems, stdout/stderr log is often collected automatically.

Important implications

Every noisy INFO becomes platform ingestion cost.
Every multiline stack trace must be parsed correctly.
Every wrong ERROR may affect alerting or dashboards.
Every missing structured field makes log query harder.

Kubernetes probe logs

Do not log successful liveness/readiness checks on every request. It creates noise.

Pod restart diagnosis

Startup failures should log enough before exit:

log.error("application startup failed reason={} config_key={} environment={} version={}",
        reason,
        safeConfigKey,
        environment,
        version,
        ex);

Cloud log cost

Cloud/on-prem/hybrid setups may have different retention and ingestion cost. Log level policy must consider:

  • production vs lower environment
  • regulatory retention
  • audit retention
  • debug enablement process
  • customer tenant isolation
  • log access control

Internal verification checklist

[ ] Apakah stdout/stderr parsing mempertahankan level field?
[ ] Apakah stack trace multiline diparse benar?
[ ] Apakah Kubernetes probe success tidak noisy?
[ ] Apakah startup failure punya ERROR sebelum exit?
[ ] Apakah log level berbeda per environment?
[ ] Apakah cloud/on-prem retention dan cost diketahui?

Performance Concern

Logging bisa mempengaruhi performance.

Risiko:

  • string construction mahal
  • serialization mahal
  • huge object logging
  • stack trace generation
  • synchronous appender blocking
  • async queue saturation
  • stdout backpressure
  • log collector bottleneck
  • GC pressure dari log allocation

Praktik aman

log.info("order submitted order_id={} item_count={} latency_ms={}",
        orderId,
        itemCount,
        latencyMs);

Hindari:

log.info("order submitted payload={}", objectMapper.writeValueAsString(order));

Untuk exception, jangan membuat exception hanya untuk stack trace jika tidak perlu.

Buruk:

log.warn("slow path detected", new RuntimeException("stack trace"));

Internal verification checklist

[ ] Apakah hot path logging menggunakan parameterized logging?
[ ] Apakah expensive serialization dijaga?
[ ] Apakah object besar tidak dilog?
[ ] Apakah async appender queue dipantau?
[ ] Apakah logging pernah menjadi bottleneck?
[ ] Apakah GC pressure dari logging pernah terlihat?

Security and Privacy Concern

Log level tidak membenarkan logging data sensitif.

Bahkan DEBUG/TRACE di non-prod bisa bocor jika data non-prod mengandung data production-like.

Jangan log:

  • password
  • token
  • Authorization header
  • Cookie
  • session ID
  • API key
  • raw request body sensitif
  • card/payment data
  • customer personal data tanpa masking
  • commercial pricing detail jika policy melarang
  • SQL parameters sensitif

Contoh aman:

log.warn("authorization failed actor_id={} action={} resource_type={} reason={} correlation_id={}",
        actorId,
        action,
        resourceType,
        reasonCode,
        correlationId);

Bukan:

log.warn("authorization failed token={} headers={}", token, headers);

Internal verification checklist

[ ] Apakah log review mencakup PII/secrets?
[ ] Apakah lower environment juga punya secure logging policy?
[ ] Apakah debug/trace tidak bisa menyalakan payload logging sembarangan?
[ ] Apakah security team punya guideline sensitive fields?
[ ] Apakah masking/redaction utility dipakai konsisten?

Cost Concern

Log level adalah cost lever.

Volume terbesar biasanya datang dari:

  • INFO lifecycle terlalu granular
  • retry WARN spam
  • access log tanpa sampling untuk high-throughput endpoint
  • DEBUG/TRACE production
  • stack trace berulang
  • framework logs noisy
  • health check logs
  • body logging

Cost tidak hanya storage. Cost mencakup:

  • ingestion
  • indexing
  • retention
  • query latency
  • dashboard rendering
  • alert evaluation
  • engineer time saat triage

Cost-aware rule of thumb

High-frequency normal event -> metric first, sampled log if needed
Low-frequency important event -> INFO log
Recoverable abnormal event -> WARN, possibly aggregated/sampled
Final failure -> ERROR
Audit-relevant event -> audit log with retention policy

Internal verification checklist

[ ] Apakah top log volume per logger diketahui?
[ ] Apakah high-volume INFO bisa diganti metric?
[ ] Apakah retry logs dibatasi?
[ ] Apakah DEBUG/TRACE punya approval dan TTL?
[ ] Apakah log retention sesuai value signal?
[ ] Apakah audit retention dipisahkan dari application log retention?

Correctness Concern

Wrong log level bisa merusak interpretasi sistem.

Contoh:

Validation failure dilog ERROR -> terlihat seperti service outage
DLQ movement dilog INFO -> failure critical tersembunyi
Retry attempt dilog ERROR -> error rate palsu
Audit write failure dilog WARN -> compliance risk diremehkan
Cache miss dilog WARN -> alert noise

Correctness berarti log level harus konsisten dengan:

  • API contract
  • business semantics
  • retry semantics
  • user impact
  • data integrity impact
  • security impact
  • operational action required

Internal verification checklist

[ ] Apakah log level mencerminkan user impact?
[ ] Apakah log level mencerminkan final vs intermediate failure?
[ ] Apakah log level mencerminkan expected vs unexpected?
[ ] Apakah log level mencerminkan recoverable vs unrecoverable?
[ ] Apakah log level konsisten antar service?

PR Review Checklist

Gunakan checklist ini saat mereview perubahan logging.

[ ] Apakah log event benar-benar perlu ada?
[ ] Apakah level log sesuai dampak operasional?
[ ] Apakah expected error tidak menjadi ERROR?
[ ] Apakah unexpected/final failure tidak disembunyikan sebagai INFO/WARN?
[ ] Apakah retry attempt dan retry exhausted dibedakan?
[ ] Apakah stack trace hanya ditulis di boundary yang tepat?
[ ] Apakah log membawa correlation_id/trace_id/request_id?
[ ] Apakah business key seperti quote_id/order_id dipakai sesuai policy?
[ ] Apakah log tidak mengandung PII, secrets, token, cookie, raw body, atau SQL parameter sensitif?
[ ] Apakah log berada di hot path atau loop besar?
[ ] Apakah high-volume event lebih cocok menjadi metric?
[ ] Apakah event audit-relevant masuk audit log, bukan hanya application log?
[ ] Apakah log message stabil dan queryable?
[ ] Apakah structured fields konsisten dengan log schema?
[ ] Apakah log akan membantu incident reconstruction?
[ ] Apakah log akan meningkatkan alert noise?
[ ] Apakah ada cost impact yang perlu dipertimbangkan?

Internal Verification Checklist

Gunakan checklist ini untuk memahami standar log level di team/internal environment.

[ ] Logging framework apa yang digunakan?
[ ] Apakah ada dokumen internal logging standard?
[ ] Apakah ada definisi internal untuk INFO/WARN/ERROR?
[ ] Apakah ERROR log memicu alert atau dashboard tertentu?
[ ] Apakah expected 4xx dihitung dalam error dashboard?
[ ] Apakah runtime log level change tersedia?
[ ] Siapa yang boleh mengubah log level di production?
[ ] Apakah ada TTL atau approval untuk DEBUG/TRACE production?
[ ] Apakah top noisy loggers diketahui?
[ ] Apakah log cost dipantau per service/team?
[ ] Apakah security/privacy review diperlukan untuk log baru?
[ ] Apakah audit log dipisahkan dari application log?
[ ] Apakah incident notes pernah menyebut missing log atau noisy log?
[ ] Apakah retry/DLQ/fallback logging sudah konsisten?
[ ] Apakah service owner punya dashboard untuk error/warn trend?

Kesimpulan

Log level discipline adalah salah satu dasar observability yang paling sering diremehkan.

Logging yang baik bukan berarti banyak log. Logging yang baik berarti:

Event penting terlihat.
Event normal tidak menutupi failure.
Expected error tidak membuat noise.
Unexpected error tidak tersembunyi.
Retry dan final failure dibedakan.
Business, security, audit, dan operational event tidak dicampur sembarangan.
Log membantu incident reconstruction.
Log tidak membocorkan data sensitif.
Log tidak membuat cost meledak.

Untuk senior backend engineer, kemampuan menentukan log level yang benar adalah bagian dari production judgment.

Di part berikutnya, kita masuk ke Log Context and MDC: bagaimana correlation ID, trace ID, request ID, tenant ID, actor ID, dan business key dibawa ke setiap log event agar log bisa dikorelasikan lintas thread, service, async job, dan messaging boundary.

Lesson Recap

You just completed lesson 07 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.