32 - Logging
เป้าหมายของบท
Section titled “เป้าหมายของบท”บทนี้จะเปลี่ยนจากการ debug ด้วย System.out.println ไปใช้ logging ที่ควบคุม level และอ่านปัญหา production ได้จริง
หลังจบบทนี้ผู้อ่านควรเข้าใจ:
- ทำไมไม่ควรใช้
System.out.printlnในงานจริง - ใช้ SLF4J logger อย่างไร
- เลือก
debug,info,warn,errorอย่างไร - ข้อมูลอะไรห้าม log
- ตั้งค่า log level ใน
application.propertiesอย่างไร
ทำไมต้องใช้ logger
Section titled “ทำไมต้องใช้ logger”System.out.println ใช้ทดลองตอนเริ่มเรียนได้ แต่ไม่เหมาะกับ backend จริง เพราะ:
- ปิด/เปิดตาม level ไม่สะดวก
- ไม่มี format มาตรฐาน
- filter ตาม package ยาก
- ส่งต่อไป log platform ยาก
- เสี่ยง print ข้อมูลลับโดยไม่ตั้งใจ
Spring Boot ใช้ logging system มาให้แล้ว เราจึงควรใช้ logger ตั้งแต่ service/controller
สร้าง logger
Section titled “สร้าง logger”ตัวอย่างใน AuthService:
package com.example.backendapi.service;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;
@Servicepublic class AuthService {
private static final Logger log = LoggerFactory.getLogger(AuthService.class);}ถ้าใช้ Lombok สามารถใช้:
@Slf4j@Servicepublic class AuthService {}ในหนังสือจะใช้ LoggerFactory เพราะมือใหม่เห็นที่มาชัดเจนกว่า
Log level ที่ควรรู้
Section titled “Log level ที่ควรรู้”| Level | ใช้เมื่อไร |
|---|---|
debug | รายละเอียดสำหรับ debug เช่น branch ที่ระบบเลือก |
info | เหตุการณ์ปกติที่สำคัญ เช่น login สำเร็จ |
warn | สิ่งผิดปกติแต่ระบบยังทำงานต่อได้ เช่น login failed |
error | error ที่ควรตรวจ เช่น database operation ล้มเหลว |
ตัวอย่างใน AuthService
Section titled “ตัวอย่างใน AuthService”public LoginResponse login(LoginRequest request) { log.info("Login attempt email={}", request.email());
User user = userRepository.findByEmail(request.email()) .orElseThrow(() -> { log.warn("Login failed email={} reason=user_not_found", request.email()); return new BadCredentialsException("Invalid email or password"); });
if (!passwordEncoder.matches(request.password(), user.getPassword())) { log.warn("Login failed email={} reason=bad_password", request.email()); throw new BadCredentialsException("Invalid email or password"); }
log.info("Login successful userId={}", user.getId()); return new LoginResponse(jwtService.generateToken(user), toResponse(user));}ใช้ {} แทนการต่อ string:
log.info("Login successful userId={}", user.getId());ดีกว่า:
log.info("Login successful userId=" + user.getId());เพราะ logger สามารถจัดการ parameter และ performance ได้ดีกว่า
ห้าม log ข้อมูลลับ
Section titled “ห้าม log ข้อมูลลับ”ห้าม log:
- password
- password hash
- JWT token เต็มก้อน
- secret key
- database password
- personal data ที่ไม่จำเป็น
ตัวอย่างที่ไม่ควรทำ:
log.info("Login request password={}", request.password());log.info("JWT token={}", token);ถ้าจำเป็นต้อง debug token ให้ log เฉพาะ metadata เช่น user id หรือ token id ไม่ใช่ token เต็ม
ตั้งค่า log level
Section titled “ตั้งค่า log level”ใน application-dev.properties:
logging.level.com.example.backendapi=DEBUGlogging.level.org.springframework.security=INFOใน application-prod.properties:
logging.level.com.example.backendapi=INFOlogging.level.org.springframework.security=WARNdev เปิด debug ได้ แต่ production ควรระวัง log volume และข้อมูล sensitive
Log format ที่อ่านง่าย
Section titled “Log format ที่อ่านง่าย”ในช่วงเริ่มต้นใช้ default ของ Spring Boot ได้ก่อน สิ่งสำคัญคือ message ต้องค้นหาได้:
Login successful userId=1Admin changed user status actorId=10 targetUserId=12 old=ACTIVE new=INACTIVEข้อความ log ควรมี:
- action
- id สำคัญ
- result หรือ reason
- ไม่มีข้อมูลลับ
แบบฝึกหัดท้ายบท
Section titled “แบบฝึกหัดท้ายบท”- เพิ่ม logger ใน
AuthService - log ตอน register สำเร็จ
- log ตอน login สำเร็จ
- log ตอน login failed ด้วย
warn - ตรวจว่าไม่มี password หรือ token เต็มใน log