เวลาแอปที่เรา build ขึ้น Docker มันพังหรือทำงานแปลกๆ สิ่งแรกที่เรานึกถึงคืออะไรครับ? ... "ขอดู Log หน่อย" ใช่ไหมครับ แต่ปัญหาคือ หลายคน โดยเฉพาะมือใหม่ มักจะงงว่า Log มันไปอยู่ที่ไหน? ทำไมรัน docker logs แล้วไม่เห็นมีอะไรเลย? หรือบางที Log ก็เยอะจนลายตาไปหมด

ผมเลยอยากมาแชร์วิธีที่ใช้กันจริงๆ ว่าเราจะดู Log ของ Docker กันยังไง ตั้งแต่วิธีง่ายๆ ไปจนถึงท่าที่ใช้กันจริงจังเวลาเจอปัญหาครับ

Docker มันเก็บ Log ยังไง?

เราต้องเข้าใจธรรมชาติของมันก่อนครับ ปกติแล้ว แอปที่ดีควรจะยิง Log ทุกอย่างออกมาที่ stdout (Standard Output) กับ stderr (Standard Error)

Docker ก็จะไปดักจับทุกอย่างที่พ่นออกมาจากสองท่อนี้แหละครับ แล้วเอาไปจัดการต่อผ่านสิ่งที่เรียกว่า 'Logging Driver'

ค่าเริ่มต้นที่มันใช้คือ json-file ซึ่งก็ตามชื่อเลย มันจะเก็บ Log เป็นไฟล์ JSON ไว้ในเครื่องเรานี่แหละ (ถ้าใครอยากส่องดู มันจะอยู่ที่ /var/lib/docker/containers/<id-container>/...-json.log)

1. คำสั่งพื้นฐาน (ที่ใช้บ่อย 90%)

วิธีที่ง่ายที่สุดที่ทุกคนรู้กัน คือ docker logs สมมติเรามี container ชื่อ my-api รันอยู่

  • docker logs my-api อันนี้คือดู Log ทั้งหมดที่เคยมีมาตั้งแต่ container เกิด
  • docker logs --follow my-api (หรือ docker logs -f my-api) อันนี้คือท่าที่ผมใช้บ่อยที่สุด คือดูแบบ real-time ครับ Log ใหม่มาปุ๊บ มันจะไหลขึ้นจอเราปั๊บ เหมาะมากเวลาเรากำลังยิงเทสแล้วอยากดูผลสดๆ
  • docker logs --tail 100 my-api ถ้า Log มันเยอะจนล้นจอ ขอเเค่ 100 บรรทัดล่าสุดก็พอ

2. กรอง Log แบบเร็วๆ (เมื่อ Log มันเยอะ)

บางทีเราไม่ได้อยากดูทั้งหมด เราอยากหาแค่บางอย่าง

  • กรองตามเวลา: docker logs --since "1h" my-api ขอดู Log เฉพาะชั่วโมงที่แล้ว (ใช้ 10m 10 นาทีที่แล้ว, 24h 24 ชั่วโมงที่แล้ว ก็ได้)
  • หาคำที่ต้องการ: อันนี้ Docker ทำเองตรงๆ ไม่ได้ แต่เราใช้คู่กับคำสั่ง Linux เทพๆ อย่าง grep ได้ครับ docker logs my-api | grep "ERROR" แบบนี้คือให้มันไปดึง Log มาทั้งหมดก่อน แล้วค่อยกรองเฉพาะบรรทัดที่มีคำว่า "ERROR"
  • เก็บ Log ลงไฟล์: docker logs my-api > my-api-logs.txt เซฟ Log ทั้งหมดเก็บไว้ในไฟล์ my-api-logs.txt เพื่อเอาไปเปิดอ่านทีหลัง
  • ถ้าคุณใช้ Docker Compose: ชีวิตจะง่ายขึ้นไปอีก พิมพ์แค่: docker-compose logs my-api มันจะไปดึง Log ของ service ที่ชื่อ my-api (ตามที่เราตั้งในไฟล์ .yml) มาให้เลย

3. ปัญหาที่เจอบ่อย (และวิธีแก้)

"ทำไม docker logs แล้วมันว่างเปล่า?"

เจอบ่อยมาก! คำตอบส่วนใหญ่ (99%) เป็นเพราะ แอปของคุณมันไม่ได้พ่น Log ออกมาที่ stdout/stderr ครับ แต่มันดันไปเขียนลงไฟล์ ข้างใน container (เช่น เขียนลง /app/logs/my-log.log)

วิธีแก้

  1. แก้ที่แอป (ดีที่สุด): ไปแก้โค้ดแอป ให้มันเปลี่ยนจากเขียนลงไฟล์ มาเป็นพ่นออก console (stdout) แทน
  2. แก้ที่ Docker (ถ้าแก้แอปไม่ได้): ต้องใช้ท่าหน่อย คือทำ symlink (ทางลัด) ใน Dockerfile ให้มันชี้ไฟล์ Log นั้น ไปที่ /dev/stdout ซะ

"Log มันดีเลย์ ไม่ยอมโชว์"

บางทีแอปบางภาษา (เช่น Python) มันจะเก็บ Log ไว้ใน buffer ก่อน (คือเก็บไว้ในเมมโมรี่) แล้วค่อยพ่นออกมาทีเดียวเป็นก้อนๆ ทำให้เราไม่เห็น Log แบบทันที

วิธีแก้: ต้องไปตั้งค่าในแอปให้มัน "unbuffered" ซะ (อย่างของ Python ก็แค่ตั้ง Environment Variable PYTHONUNBUFFERED=1 ตอนรัน container)

"Disk เต็มเพราะ Log!"

นี่คือหายนะของจริงครับ และเป็นเหตุผลที่ผมย้ำเรื่อง local driver เราต้องไปตั้งค่า Log Rotation ให้มันครับ

วิธีแก้: ไปแก้ไฟล์ daemon.json ของ Docker (ปกติอยู่ที่ /etc/docker/daemon.json) แล้วเพิ่มการตั้งค่านี้เข้าไป

{
  "log-driver": "local",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}

ความหมายคือ ให้ใช้ local driver เป็นค่าเริ่มต้น, แต่ละไฟล์ Log ห้ามใหญ่เกิน 50MB (max-size), และให้เก็บไฟล์เก่าไว้แค่ 5 ไฟล์ (max-file) พอมันเต็มไฟล์ที่ 5 มันจะลบไฟล์ที่เก่าที่สุดทิ้งเอง แค่นี้ disk ก็ไม่เต็มแล้วครับ

4. ใช้จริงบน Production ต้องคิดอะไรเพิ่ม?

เวลาทำงานจริงจัง เรามีเรื่องต้องคิดมากกว่าแค่ดู Log ได้ครับ

  1. เรื่อง Storage: ต้องคอยดูว่า Log มันกินที่ไปเท่าไหร่ ตั้งค่า rotation ให้ดี (แบบที่บอกไปตะกี้)
  2. เรื่องความปลอดภัย: สำคัญมาก! ห้ามมีพวก API Key, Password, เลขบัตรเครดิต, หรือข้อมูลส่วนตัวลูกค้า หลุดออกมาใน Log เด็ดขาด
  3. เรื่อง Format: ถ้าเป็นไปได้ พยายามทำให้ Log เป็น JSON Format ซะ มันจะช่วยให้โปรแกรมอื่น (เช่นพวกที่เอาไว้เก็บ Log กลาง) อ่านง่ายขึ้นเยอะ

สรุป

สรุปง่ายๆ นะครับ การดู Log ใน Docker ไม่ได้มีแค่ docker logs

หัวใจของมันคือ

  1. ต้องแน่ใจว่า แอปเราพ่น Log ออกมาที่ stdout/stderr (ไม่ใช่เขียนลงไฟล์)
  2. ต้อง ตั้งค่า Logging Driver ให้ถูก (ใช้ local driver เถอะ) และตั้งค่า rotation ให้ดีๆ ไม่งั้น disk เต็ม

พอเราคุมเรื่องพื้นฐานนี้ได้ การไล่ปัญหาบน Docker ก็จะไม่ใช่เรื่องปวดหัวอีกต่อไปครับ หวังว่าจะเป็นประโยชน์นะครับ