Skip to main content

SWR баг — Logging стандарт

Формат: JSON structured · Жишээ: Java (Log4j 2) · Цуглуулга: Loki / ELK / Datadog


1. Үндсэн зарчим

  • Лог бол production дахь нүд. Алдаа гарахаас өмнө биш, гарсны дараа уншигдахаар бич.
  • Бүх лог structured JSONSystem.out.println / чөлөөт текст хориотой. Машин уншиж, indexlэх боломжтой байх ёстой.
  • Нэг үйл явдал = нэг лог мөр. Олон мөрт текст, stack trace-ийг нэг талбарт хий.
  • Лог нь нэмэлт мэдээлэл агуулна (хэн, юу, хаана), зүгээр "алдаа гарлаа" биш.
  • Нууц мэдээлэл хэзээ ч логонд орохгүй (доор §6).

Аюулгүй байдал: зөвхөн Log4j 2.x (≥ 2.17.1) ашигла. Log4j 1.x EOL бөгөөд эмзэг — хэзээ ч бүү ашигла. (Log4Shell / CVE-2021-44228-аас хойш засагдсан хувилбар сонго.)


2. Лог түвшин (levels)

ТүвшинХэзээЖишээ
DEBUGХөгжүүлэлтийн нарийн ширийн мэдээлэл. Production-д унтраалттай.хувьсагчийн утга, гүйцэтгэлийн алхам
INFOХэвийн, ач холбогдолтой үйл явдал."payment processed", "user registered"
WARNГэнэтийн боловч системийг зогсоохгүй нөхцөл.retry хийсэн, deprecated API дуудсан
ERRORХүсэлт/үйлдэл амжилтгүй болсон, анхаарал шаардсан.DB холболт тасарсан, payment failed

Дүрэм:

  • FATAL-ийг зөвхөн программ эхлэх үед (config уншиж чадаагүй гэх мэт) ашигла. Хүсэлт боловсруулах явцад хэзээ ч бүү унага.
  • ERROR бичсэн бол хэн нэгэн арга хэмжээ авах ёстой. Авах шаардлагагүй бол WARN болго.
  • Production-ийн default түвшин: INFO.

3. Заавал байх талбарууд

JSON лог бүр дараах талбаруудтай байна:

ТалбарУтга
@timestampISO-8601 / RFC3339 timestamp (UTC)
levelDEBUG / INFO / WARN / ERROR
messageТогтмол, богино тайлбар (хувьсагч бүү шигтгэ — талбар болго)
serviceСервисийн нэр, ж: payment-api
envdev / staging / production
versionBuild / git SHA
request_idХүсэлт бүрийн өвөрмөц ID (trace холбох)
logger / threadКласс/thread нэр (Log4j автоматаар нэмнэ)

Контекстээс хамаарч: user_id, trace_id, duration_ms, status_code, error.


4. message ба түлхүүр-утга

  • message нь тогтмол байх ёстой — динамик утгыг тусдаа талбар болго. Ингэснээр групплэх, тоолох боломжтой.
// ❌ Буруу — message дотор утга шигтгэсэн, indexlэх боломжгүй
log.info("user {} paid {}", userId, amount);

// ✅ Зөв — тогтмол message, утгууд структур талбарт (MapMessage)
log.info(new StringMapMessage()
.with("event", "user_paid")
.with("user_id", userId)
.with("amount", String.valueOf(amount)));
  • Түлхүүр нэр snake_case, бүх сервист нэгдсэн (user_id, request_id, duration_msuserId/uid биш).
  • Хугацааг _ms / _sec дагавартай тоогоор бич (duration_ms).

5. Java — Log4j 2 тохиргоо

Maven хамаарал (JSON layout-д log4j-layout-template-json хэрэгтэй):

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.24.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.24.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
<version>2.24.3</version>
</dependency>

log4j2.xml — structured JSON гаралт (JsonTemplateLayout, ECS загвар):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<!-- JSON structured layout; MDC (ThreadContext) талбаруудыг автоматаар оруулна -->
<JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/>
</Console>
</Appenders>
<Loggers>
<Root level="${env:LOG_LEVEL:-INFO}">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

Logger үүсгэх (класс бүрт нэг static logger):

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class PaymentService {
private static final Logger log = LogManager.getLogger(PaymentService.class);
}

Лог нь stdout руу JSON-оор гарч, container / log collector (Loki, ELK, Datadog) цуглуулна. Файл руу бичих, ротаци хийх шаардлагагүй.


6. Юу логлохгүй вэ (нууцлал)

Хэзээ ч логлохгүй:

  • Нууц үг, токен, API key, session ID, cookie
  • Картын дугаар, CVV, банкны данс
  • Хувийн мэдээлэл бүхэлд нь (овог нэр, утас, и-мэйл, регистр) — шаардлагатай бол masklэ (user_***, ****1234)
  • Authorization header, JWT-ийн бүтэн агуулга
// ❌ Буруу
log.info("login pw={} token={}", pw, token);

// ✅ Зөв — зөвхөн таних ID, нууцыг хасна
log.info(new StringMapMessage()
.with("event", "login_succeeded")
.with("user_id", userId));
  • Эргэлзвэл бүү логло. Нэг удаагийн дебагт хэрэгтэй нууцыг логонд бүү үлдээ.

7. Алдааны лог

  • Алдааг нэг удаа, хамгийн дээд түвшинд (controller / boundary) логло — давхар бүү логло.
  • Exception-ийг сүүлийн аргумент болгон дамжуул — Log4j stack trace-ийг JSON талбарт оруулна. e.getMessage()-ийг текст рүү бүү нааж бай.
  • Контекст талбарыг (order_id гэх мэт) MDC эсвэл MapMessage-ээр нэм.
try {
paymentService.charge(order);
} catch (PaymentException e) {
ThreadContext.put("order_id", order.getId());
log.error("charge failed", e); // exception → stack trace JSON-д
throw e;
}

8. Контекст ба trace холболт (MDC)

  • Хүсэлт бүрт request_id (байхгүй бол trace_id) үүсгэж, ThreadContext (MDC)-д хий. JsonTemplateLayout үүнийг автоматаар лог бүрт нэмнэ.
  • Хүсэлт дуусахад ThreadContext.clearAll() — thread pool дахин ашиглагдах тул цэвэрлэхгүй бол утга "гоожино".
import org.apache.logging.log4j.ThreadContext;

public class RequestIdFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {

HttpServletRequest http = (HttpServletRequest) req;
String reqId = http.getHeader("X-Request-ID");
if (reqId == null || reqId.isBlank()) {
reqId = UUID.randomUUID().toString();
}

ThreadContext.put("request_id", reqId);
long start = System.currentTimeMillis();
try {
chain.doFilter(req, res);
} finally {
log.info(new StringMapMessage()
.with("event", "request_handled")
.with("method", http.getMethod())
.with("path", http.getRequestURI())
.with("duration_ms", String.valueOf(System.currentTimeMillis() - start)));
ThreadContext.clearAll(); // ЗААВАЛ цэвэрлэ
}
}
}
  • Микросервис хооронд X-Request-ID / traceparent header-ийг дамжуулж, нэг хүсэлтийг хэд хэдэн сервисээр дагах боломжтой болго.

9. Шалгах жагсаалт

Хийх (Do)

  • Structured JSON (JsonTemplateLayout) · тогтмол message + талбар · snake_case нэгдсэн түлхүүр
  • Зөв түвшин сонгох · request_id-г MDC-д хийх · алдааг нэг удаа exception-тэй нь логлох
  • UTC timestamp · хүсэлт дуусахад ThreadContext.clearAll()

Бүү хий (Don't)

  • System.out.println / чөлөөт текст · message-д утга шигтгэх
  • Нууц/PII логлох · алдааг давхар логлох
  • Production-д DEBUG асаах · хүсэлтийн явцад unhandled exception-аар унах
  • Log4j 1.x ашиглах (EOL, эмзэг)