จัดการ Log Go ให้เป็นระบบ ด้วย Zap และ CloudWatch

สวัสดีครับ โปรแกรมเมอร์ทุกคน นะครับ เคยไหมครับที่ต้องมานั่งงมหา Log เวลาที่ระบบมันมีปัญหานะครับ แบบว่าหาเท่าไหร่ก็ไม่เจอ หรือเจอแล้วก็อ่านยากมากๆ บทความนี้นะครับ ผมจะมาแนะนำวิธีจัดการ Log ใน Go ให้มันดูเป็นระบบระเบียบขึ้นมา ด้วยไลบรารี่ตัวเจ๋งอย่าง uber-go/zap แล้วก็จากนั้น เราจะมาลองดูกันว่าถ้าเราจะส่ง Log พวกนี้ไปให้ CloudWatch ของ AWS เค้าจัดการดูมันจะเป็นยังไงบ้าง นะครับ มันจะช่วยให้เรา มอนิเตอร์ ระบบได้ง่ายขึ้นมากๆ เลยล่ะครับ

ขั้นตอนที่ 1: ติดตั้ง uber-go/zap

ง่ายๆ ครับ แค่รันคำสั่งนี้ นะครับ

go get go.uber.org/zap

ขั้นตอนที่ 2: ใช้ zap สร้าง Log ทั้ง Console และ ไฟล์

มาดูตัวอย่างโค้ดนะครับ ที่จะทำให้เราได้ Log แบบเป็นโครงสร้าง (Structured Log) ทั้งไปที่ Console แล้วก็ บันทึกลงไฟล์ด้วยนะครับ ผมจะตั้งค่าให้เวลาใน Log เป็นแบบ ISO8601 ด้วย เพื่อให้มันเข้ากับ CloudWatch ได้ง่ายๆ นะครับ

package main

import (
\t"fmt"
\t"time"

\t"go.uber.org/zap"
\t"go.uber.org/zap/zapcore"
)

func main() {
\t// ตั้งค่า Config สำหรับ Logger ครับ
\t// ให้ Output เป็น JSON และมีเวลาแบบ ISO8601
\tconfig := zap.NewProductionConfig()
\tconfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder

\t// กำหนดเส้นทาง Output ครับ ให้ไปทั้ง stdout (Console) และไฟล์ "app.log"
\t// ถ้าจะรันในเครื่องตัวเอง ให้สร้างไฟล์ app.log ในโฟลเดอร์เดียวกันได้เลยนะครับ
\tconfig.OutputPaths = []string{"stdout", "app.log"}
\tconfig.ErrorOutputPaths = []string{"stderr"} // อันนี้สำหรับ Log ระดับ Error ครับ

\t// สร้าง Logger จาก Config ที่เราตั้งไว้
\tlogger, err := config.Build()
\tif err != nil {
\t\tfmt.Printf("มีปัญหาตอนสร้าง Logger ครับ: %v\
", err)
\t\treturn
\t}
\tdefer logger.Sync() // สำคัญมากๆ ครับ เพื่อให้มั่นใจว่า Log ถูกเขียนหมดก่อนโปรแกรมจะปิด

\t// ตัวอย่างการเขียน Log แบบ Info
\tlogger.Info("แอพของเราเริ่มทำงานแล้วครับ",
\t\tzap.String("service", "billing-api"),
\t\tzap.String("version", "1.0.0"),
\t)

\t// ตัวอย่างการเขียน Log พร้อม Field ที่เป็นข้อมูลเฉพาะ
\tlogger.Warn("มีบางอย่างดูแปลกๆ นะครับ",
\t\tzap.String("module", "payment-gateway"),
\t\tzap.Int("transaction_id", 98765),
\t\tzap.Duration("response_time", time.Millisecond*500),
\t)

\t// ตัวอย่างการเขียน Log ระดับ Error
\tlogger.Error("เจอข้อผิดพลาดร้ายแรง ครับ",
\t\tzap.String("component", "database"),
\t\tzap.Error(fmt.Errorf("เชื่อมต่อฐานข้อมูลไม่ได้")),
\t\tzap.Stack("stacktrace"), // ลองเพิ่ม stacktrace เข้าไปดูครับ
\t)

\tfmt.Println("\
--- ลองตรวจสอบไฟล์ 'app.log' ในโฟลเดอร์นี้ดูนะครับ ---\
")
}

Output ที่เราจะได้ (ประมาณนี้นะครับ)

พอเรารันโค้ดข้างบน นะครับ เราก็จะได้ Log ออกมา 2 ที่ครับ คือที่ Console และในไฟล์ app.log นะครับ ซึ่งจะมีหน้าตาประมาณนี้เลยนะครับ

{\"level\":\"info\",\"ts\":\"2024-03-15T10:30:00.123+0700\",\"caller\":\"main/main.go:30\",\"msg\":\"แอพของเราเริ่มทำงานแล้วครับ\",\"service\":\"billing-api\",\"version\":\"1.0.0\"}
{\"level\":\"warn\",\"ts\":\"2024-03-15T10:30:00.456+0700\",\"caller\":\"main/main.go:37\",\"msg\":\"มีบางอย่างดูแปลกๆ นะครับ\",\"module\":\"payment-gateway\",\"transaction_id\":98765,\"response_time\":\"500ms\"}
{\"level\":\"error\",\"ts\":\"2024-03-15T10:30:00.789+0700\",\"caller\":\"main/main.go:45\",\"msg\":\"เจอข้อผิดพลาดร้ายแรง ครับ\",\"component\":\"database\",\"error\":\"เชื่อมต่อฐานข้อมูลไม่ได้\",\"stacktrace\":\"goroutine 1 [running]:\
main.main()\
\\t/path/to/main.go:46 +0x...\"}

เป็นไงครับ อ่านง่าย จัดเป็นระบบมากๆ เลยใช่ไหมครับ ทำให้เราหาสิ่งที่ต้องการได้ง่ายขึ้นเยอะเลย

ขั้นตอนที่ 3: เชื่อม Log กับ CloudWatch

ทีนี้มาถึงการเชื่อมกับ CloudWatch นะครับ Log ที่เป็น JSON แบบที่เราได้จาก zap เนี่ย มันเหมาะมากๆ เลยนะครับ ที่จะเอาไปใช้กับ CloudWatch Logs ของ AWS

สิ่งที่เราต้องทำนะครับ คือการติดตั้ง CloudWatch Agent บน Server หรือ Container ที่รันแอพ Go ของเราครับ จากนั้นเราก็แค่กำหนดค่าในไฟล์ Config ของ Agent (ปกติจะเป็น amazon-cloudwatch-agent.json) ให้มันไปอ่านไฟล์ Log ของเรา เช่น app.log ที่เราสร้างขึ้นมานะครับ แบบนี้นะครับ

ตัวอย่าง Config ของ CloudWatch Agent (บางส่วนนะครับ)

{
\t\"logs\": {
\t\t\"logs_collected\": {
\t\t\t\"files\": {
\t\t\t\t\"collect_list\": [
\t\t\t\t\t{
\t\t\t\t\t\t\"file_path\": \"/path/to/your/app.log\", // แก้ไขเป็น Path จริงของไฟล์ Log ของคุณนะครับ
\t\t\t\t\t\t\"log_group_name\": \"MyGoZapLogs\", // ชื่อ Log Group ใน CloudWatch
\t\t\t\t\t\t\"log_stream_name\": \"{instance_id}\", // ใช้ instance_id หรือชื่อที่เหมาะกับคุณนะครับ
\t\t\t\t\t\t\"timestamp_format\": \"%Y-%m-%dT%H:%M:%S%Z\", // ต้องตรงกับเวลาที่เรา encode ด้วย zap นะครับ
\t\t\t\t\t\t\"multi_line_start_pattern\": \"^{\", // บอก Agent ว่า Log JSON แต่ละบรรทัดเริ่มที่ '{'
\t\t\t\t\t\t\"encoding\": \"utf-8\"
\t\t\t\t\t}
\t\t\t\t]
\t\t\t}
\t\t}
\t}
}

พอ CloudWatch Agent มันอ่านไฟล์ app.log ของเราได้แล้วนะครับ Log ทุกบรรทัดก็จะถูกส่งไปที่ CloudWatch Logs ใน Log Group ที่ชื่อ MyGoZapLogs โดยอัตโนมัติครับ เราก็จะสามารถเข้าไปดู Log, ค้นหา Log, หรือแม้แต่สร้าง Dashboard และ Alert จาก Log ใน CloudWatch ได้แบบสบายๆ เลยล่ะครับ

แค่นี้เองครับ ผม เราก็ได้ระบบจัดการ Log ที่เป็นระเบียบ อ่านง่าย แล้วก็ส่งไป มอนิเตอร์ ที่ CloudWatch ได้แล้วครับ หวังว่าบทความนี้จะเป็นประโยชน์กับเพื่อนๆ โปรแกรมเมอร์ทุกคน นะครับ ลองเอาไปปรับใช้กับโปรเจกต์ของตัวเองดูนะครับผม

แหล่งที่มา:\ - Uber-Go/Zap GitHub Repository\ - AWS CloudWatch Logs Documentation

Read more

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

เจาะลึกเรื่องราวของ Eileen Gu นักสกีฟรีสไตล์ผู้สร้างประวัติศาสตร์ในโอลิมปิก 2026 สถิติที่ไม่เคยมีมาก่อน ประเด็นถกเถียง และความแข็งแกร่งส่วนตัวที่ทำให้เธอก้าวสู่ระดับโลก

By ทีมงาน devdog
วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

เจาะลึกวันพระและความสำคัญของวันมาฆบูชา 2569 ทั้งวันหยุดราชการ ธนาคาร กิจกรรมเวียนเทียนต้นไม้ และผลกระทบต่อบริการขนส่ง เตรียมตัววางแผนทำบุญและพักผ่อน

By ทีมงาน devdog
ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

เจาะลึกงานวิวาห์ "บังมัดคลองตัน" กับ "ต้นข้าว มิสแกรนด์" พร้อมเหตุผลจากใจเจ้าสาวที่เลือกความรักเหนือกาลเวลาและคำวิจารณ์ สู่การเริ่มต้นชีวิตคู่ที่สะท้อนการให้อภัย

By ทีมงาน devdog
ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

เจาะลึกไฮไลท์บอลไทยลีก 2 ของมหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรง ชัยชนะสำคัญจาก ชิตชนก และบทบาทโค้ชดุสิต สู่เส้นทางเพลย์ออฟที่น่าจับตา!

By ทีมงาน devdog