การทำงานกับ Logs ใน Ruby
Logging เป็นหนึ่งใน Primary Tasks ที่ Application ปกติจัดการ Logs ใช้เมื่อคุณต้องการ เช่น
- ดูว่าภายใน App เกิดอะไรขึ้นบ้าง
- Monitor พวกมัน
- Collect Metrics สำหรับ Data เฉพาะเจาะจง
ตอนเรียนภาษาใหม่ ตัวเลือกอvious สำหรับ Log ข้อมูลคือ Native Mechanism มันมัก Easy, Documented, และ Well-spread ใน Community
Log Data แตกต่างกันมากขึ้นอยู่กับ Company, Business, และ Application Type ที่คุณทำงาน ดังนั้นเข้าใจว่า Logging Solution ที่คุณและทีมเลือกจะ Impact Overall Use ยังไง จึงสำคัญมาก
ในบทความนี้ เราจะทัวร์ตัวเลือก Logging สำหรับ Ruby และ Rails ตั้งแต่ Built-in ถึง Third-party Community Frameworks ที่ได้รับความนิยม มาเริ่มกัน!
ตัวเลือก Built-in ของ Ruby
Ruby มาพร้อม 2 Built-in Options สำหรับ Handle Logging: Printing Commands (ออกแบบเฉพาะ Command-line Scenarios) และ Logger Class
มา Explore เล็กน้อย
Print ไป Console
Ruby มี 4 วิธีทั่วไป Print Text ไป Console: puts, print, p, และ pp
puts Print อะไรก็ตามที่ส่งเข้าไป ตามด้วย New Line:
2.7.0 :001 > puts "Hey, I'm a log!"
Hey, I'm a log!
=> nil
print คล้าย puts แต่ Convert Arguments เป็น Strings ด้วย to_s เสมอ
p และ pp ทั้งคู่ Print Raw Object ที่ส่งเข้าไป โดยไม่ Convert ความต่างคือ pp Format Output ให้ Indented สวยกว่า p ไม่ Format
Application Logs
สำหรับ Apps ที่รันบน Servers ไม่ควร Print ไป Console
แทนที่จะใช้ Logger Class ซึ่งยืดหยุ่นกว่า
นี่คือวิธีใช้ Print "Debug" Log:
require "logger"
logger = Logger.new(STDOUT)
logger.debug("I'm a debug log")
เราสามารถ Config ให้เก็บ Logs ไป File หรือส่งไป Log Aggregator แทน STDOUT ก็ได้
ถ้าสนใจ "debug" เป็น Log Level Log Levels ให้บอก System ว่า "Log Message นี้เกี่ยวกับ Event ประเภทไหน" มี 6 Built-in Levels: Fatal, Error, Warn, Info, Debug และ Unknown
logger.debug("I'm a debug log")
logger.info("I'm an info log")
logger.warn("I'm a warn log")
logger.error("I'm an error log: error message")
logger.fatal("I'm a fatal log")
ดู Log Output จะเห็น Log Level, Process ID และ Timestamp ถูก Append ทุกบรรทัด:

Feature ยอดเยี่ยมอีกอย่าง: ตั้ง Logger Object ให้อยู่ Level เฉพาะ:
logger.level = Logger::WARN
ตอนนี้ Logger จะ Handle Logs ที่เท่ากับหรือสูงกว่า warn ในความสำคัญเท่านั้น มีประโยชน์มากใน Production ที่อาจไม่อยากเก็บ "debug" Logs เพราะ Size หรือ Security Reasons
Output จะเป็นแบบนี้:

ปรับแต่ง Logs ของคุณ
อยาก Customize Logs มี Options เยอะ เปลี่ยน Date/Time แค่ Overwrite Default:
logger.datetime_format = "%Y-%m-%d %H:%M"
Output ตัวอย่าง:
W, [2020-07-28 10:50#87786] WARN -- : I'm a warn log
date_time Format ตาม Spec มาตรฐาน อ้างอิง Spec ตอนทำการเปลี่ยนแปลง
บางครั้งต้อง Modify Log Format ทั้งหมด Ruby ให้ Formatter Class มันทำงานโดยให้ Devs Overwrite Default Template ที่ Logger ใช้ Print Logs
สมมติ App ใช้ Centralized Logs Analysis Tool ต้องการ Logs ทุกตัวเป็น Format name=value โค้ด Formatter ใหม่:
logger.formatter = proc do |severity, datetime, progname, msg|
date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
"date=[#{date_format}] severity=#{severity.ljust(5)} pid=##{Process.pid} message='#{msg}'\n"
end
ใช้ proc Intercept Logs ทุกตัว เปลี่ยน Default Print ให้เป็น Format ที่ต้องการ ผลลัพธ์:

เพียงไม่กี่บรรทัด App Logs ทั้งหมด Index ได้โดย Tool แล้ว
Logging เป็น JSON
ด้วยพลัง Formatter ง่ายมากที่จะ Customize Logs ให้ Output Type ใดๆ เช่น ใช้ JSON Module Output Logs เป็น JSON:
require "json"
logger.formatter = proc do |severity, datetime, progname, msg|
date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
JSON.dump(date: "#{date_format}", severity:"#{severity.ljust(5)}", pid:"##{Process.pid}", message: msg) + "\n"
end
Logs ผลลัพธ์:

Logging ไป File
เห็นก่อนหน้า Logger Constructor รับ Argument แรกเป็นที่เก็บ Log
อยากเก็บ Logs ไป File บอกเลย:
require 'logger'
logger = Logger.new('my_logs.log')
จะสร้าง File my_logs.log ใน Folder เดียวกับ Ruby File ที่มีโค้ดนี้:

แต่ Class ยังรับ Custom Options เยอะ เช่น Retention Policy Config ตัวอย่าง:
# เก็บ Data สำหรับสัปดาห์ปัจจุบัน
Logger.new('my_weekly_logs.log', 'weekly')
# เก็บ Data วันนี้และ 2 เดือนย้อนหลัง
Logger.new('my_latest_2_months_logs.log', 2, 'monthly')
# Restart Log เมื่อเกิน 26GB
Logger.new('my_custom_logs.log', 0, 20 * 1024 * 1024 * 1024)
Param ที่ 2 Logger รับ Definition ว่าเก็บ Logs บ่อยแค่ไหน หรือ ลบบ่อยแค่ไหน Object แรกเก็บ 1 สัปดาห์ Object ที่สองเก็บ Range 2 เดือน Object สุดท้ายวิเคราะห์แค่ File Size (เช่น ถึง 20GB จะ Discard) ตาม Param ที่ 3
Logging ใน Ruby on Rails
Benefits ของ Logger ที่เห็นส่วนใหญ่ ใช้ร่วมกับ Rails Apps ได้ Rails Version ของ Logger คือ Simple Extension ของ Native Class ของ Ruby
นอกจากนี้ Rails เพิ่ม Feature ให้ Devs Broadcast Logs ไป Multiple Loggers ได้ ถ้าอยากทำงานกับ Logging Libraries มากกว่าหนึ่ง ทำได้ใน Rails logger:
custom_logger = Logger.new(STDOUT)
Rails.logger.extend(ActiveSupport::Logger.broadcast(custom_logger))
มีประโยชน์ตอนต้องการ Intelligence เกี่ยวกับที่ Logs ควรไป บางส่วนของ App อาจต้องการ Logs ใน Locations หรือ Formats ต่างกัน
เลือก Third-party Logging Framework
ถ้า Built-in Logger ของ Ruby ไม่ทำให้พอใจ มี Options เยอะ
ยอดนิยมคือ Lograge มาดู!
First Steps with Lograge
Lograge ทำสำหรับ Rails Apps ถ้า Vanilla Ruby อาจไม่เหมาะ
ติดตั้งเพิ่ม Gem:
bundle add lograge
สร้าง Config File ใหม่ที่ config/initializers/lograge.rb เนื้อหา:
Rails.application.configure do
config.lograge.enabled = true
config.lograge.custom_options = lambda do |event|
{ time: event.time }
end
end
Config เปิด Lograge และกำหนดเวลาที่ Log (Event ต่อ Framework) ต้อง Print
Start Rails App เข้าถึง Endpoint แรก Lograge จะ Print Request Summary เปรียบเทียบ Logs ก่อนและหลัง Setup Lograge:

Lograge ไม่ใช่ Silver Bullet เป็น Opinionated Framework จะต้องใช้ร่วม Built-in Logger (หรือ Frameworks อื่นที่ชอบ) ได้
สรุป
สุดท้ายชัดเจนว่า Logging ไม่ได้แค่ Crucial Part ของ Projects แต่ยัง Underestimated อีกด้วย การเข้าใจ Tools ที่ใช้ ไม่ว่าจะ Native จะช่วย Achieve Better Results ได้
Test มากๆ ก่อน Adopt Framework ใหม่ Platform บางอันมาพร้อม Built-in Options ที่ยอดเยี่ยม ถ้าไม่ใช่ ให้เช็คว่า Allow Frameworks อื่น Integrated กับที่มีอยู่ได้ไหม
Good luck!