SWR баг — Logging стандарт
Формат: JSON structured · Жишээ: Java (Log4j 2) · Цуглуулга: Loki / ELK / Datadog
1. Үндсэн зарчим
- Лог бол production дахь нүд. Алдаа гарахаас өмнө биш, гарсны дараа уншигдахаар бич.
- Бүх лог structured JSON —
System.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 лог бүр дараах талбаруудтай байна:
| Талбар | Утга |
|---|---|
@timestamp | ISO-8601 / RFC3339 timestamp (UTC) |
level | DEBUG / INFO / WARN / ERROR |
message | Тогтмол, богино тайлбар (хувьсагч бүү шигтгэ — талбар болго) |
service | Сервисийн нэр, ж: payment-api |
env | dev / staging / production |
version | Build / 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_ms—userId/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) Authorizationheader, 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/traceparentheader-ийг дамжуулж, нэг хүсэлтийг хэд хэдэн сервисээр дагах боломжтой болго.
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, эмзэг)