ไลบรารี vs แอปพลิเคชัน: ความต้องการด้านการทำล็อกที่แตกต่างกันโดยพื้นฐาน
- การทำล็อกในแอปพลิเคชัน: ตั้งค่าและจัดการอย่างชัดเจนในสภาพแวดล้อมที่นักพัฒนาควบคุมได้โดยตรง
- การทำล็อกในไลบรารี: ถูกนำไปรวมในโปรเจกต์ของผู้อื่น จึงต้องเคารพสภาพแวดล้อมและสิทธิ์ในการเลือกของผู้ใช้
- ข้อจำกัดของแนวทางเดิม: เมื่อนำล็อกเกอร์ที่ยึดแอปพลิเคชันเป็นศูนย์กลางอย่าง winston, Pino มาใช้กับไลบรารี จะเกิดปัญหาเรื่องการบังคับใช้งาน
- ภาวะกลืนไม่เข้าคายไม่ออกของผู้สร้างไลบรารี: ให้ข้อมูลสำหรับดีบัก vs ไม่สร้างภาระให้ผู้ใช้
ปัญหาของการทำล็อกในไลบรารีปัจจุบัน
- ระบบนิเวศการทำล็อกที่กระจัดกระจาย: Express ใช้
DEBUG=express:*, ส่วน Mongoose ใช้ mongoose.set('debug', true) เป็นต้น แต่ละตัวใช้วิธีต่างกัน
- ภาวะกลืนไม่เข้าคายไม่ออกเรื่อง dependency: หากใช้ไลบรารีที่ยึดแอปพลิเคชันเป็นศูนย์กลางอย่าง winston, Pino ก็จะบังคับให้ผู้ใช้รับ dependency และการตั้งค่าที่ไม่ต้องการ
- ความเงียบ vs การบังคับ: ทางเลือกสุดโต่งมีเพียงเลิกทำล็อกไปเลย หรือบังคับผู้ใช้ให้ทำตามแนวทางการทำล็อกที่กำหนด
- ความซับซ้อนของ dependency injection: แม้เป็นแนวทางที่ประณีตกว่า แต่ทำให้ API ซับซ้อนขึ้นและเพิ่มภาระให้ผู้ใช้
ปรัชญาแบบ "library-first" ของ LogTape
- เปิดใช้งานแบบมีเงื่อนไข: หากไม่ได้ตั้งค่าการทำล็อกก็จะไม่ทำงานโดยสมบูรณ์ แต่เมื่อมีการตั้งค่าก็จะจัดการแบบรวมศูนย์ได้
- รับประกันสิทธิ์ในการเลือกของผู้ใช้: ไลบรารีไม่บังคับวิธีการทำล็อก และจะเปิดใช้ก็ต่อเมื่อผู้ใช้ต้องการเท่านั้น
- ไม่มี dependency: ขนาด 5.3KB ช่วยลดความเสี่ยงด้าน supply chain security และป้องกันปัญหาเวอร์ชันชนกัน
- รองรับ ESM/CJS อย่างสมบูรณ์: แก้ปัญหา compatibility chain และปรับ bundle ให้เหมาะสมผ่าน tree shaking
ข้อดีเชิงปฏิบัติ
- ปรับประสิทธิภาพให้เหมาะสม: แทบไม่มี overhead เมื่อปิดใช้งาน และเมื่อเปิดใช้งานก็มีประสิทธิภาพการแสดงผลบนคอนโซลที่ดีเยี่ยม
- แยก namespace: ใช้หมวดหมู่แบบลำดับชั้นในรูป
["my-lib", "feature"] เพื่อหลีกเลี่ยงการชนกัน
- ออกแบบแบบ TypeScript-first: มอบ type safety อย่างสมบูรณ์โดยไม่ต้องมีแพ็กเกจ type เพิ่มเติม
- เชื่อมต่อกับระบบเดิมได้: รองรับการนำมาใช้แบบค่อยเป็นค่อยไปผ่านอะแดปเตอร์สำหรับ winston และ Pino
ข้อพิจารณาในโลกความเป็นจริง
- ความหมายของอะแดปเตอร์: ยอมรับความจริงที่ว่ายังไม่ใช่มาตรฐานของระบบนิเวศ และเป็นการประนีประนอมเชิงปฏิบัติ
- แรงบันดาลใจจากระบบนิเวศ Python: อ้างอิงกรณีความสำเร็จของ Python ที่รวมศูนย์ผ่านไลบรารีมาตรฐาน
logging
- แนวทางที่มองไปข้างหน้า: เสนอเป็นหนึ่งในทางเลือกสำหรับการค่อย ๆ ปรับปรุงระบบนิเวศของไลบรารี
7 ความคิดเห็น
ผมยังไม่ค่อยเข้าใจว่าทำไมถึงบอกว่า "ไม่ทำงานเลยหากไม่ได้ตั้งค่า" นะครับ
ตอนเรียก
getLoggerก็สร้างล็อกเกอร์ขึ้นมาแล้ว และถ้าเรียกdebugมันก็ทำงานอยู่แล้วด้วยเท่าที่ผมดูโค้ด มันแค่ทำให้ดูเหมือนไม่ทำงานเท่านั้นเอง
และก็ไม่ได้ช่วยเลื่อนการประมวลผลของ string ออกไปด้วย
เลยยังไม่ค่อยเข้าใจว่ามันต่างจากไลบรารีอื่นที่แค่ไม่พิมพ์ออกมาเมื่อกำหนดระดับล็อกอย่างไร
เอ๊ะ ทั้งที่ไม่ได้เรียก
configure()/configureSync()ก็ยังมี log ออกมาเหรอ? แล้วมันถูกแสดงที่ไหน? แสดงบนหน้าจอคอนโซลเหรอ?อ๋อ ที่พูดว่ามันทำงานได้ในที่นี้ ไม่ได้หมายถึงการที่ล็อกถูกบันทึกลงใน console หรือไฟล์ แต่หมายถึงว่ามีการเรียกใช้ฟังก์ชันจริง ๆ และมี overhead เกิดขึ้นจริงหรือไม่ต่างหากครับ
น่าจะทำให้เข้าใจผิดกันได้อยู่เหมือนกัน
แน่นอนว่าเมื่อพิจารณาว่าภาระหลักของ logger คือ system call ก็อาจบอกไม่ได้ว่าไม่มี overhead
แต่จะบอกว่านั่นคือจุดที่แตกต่างจาก logger อื่นหรือ? ก็ไม่เชิง เพราะ logger อื่นก็ทำงานแบบเดียวกัน
อ๋อ หมายความว่าอย่างนั้นนี่เองครับ อย่างแรกเลย เมื่อลองรันเบนช์มาร์กโดยอิงจาก null output ก็พบว่าแทบไม่มีโอเวอร์เฮดเลย แต่สิ่งที่ผมมองว่าสำคัญยิ่งกว่าภาระด้านประสิทธิภาพ คือพฤติกรรมเริ่มต้นเป็น no-op หรือไม่ ในมุมของผู้เขียนไลบรารี ต่อให้มีการบันทึกล็อกไว้ภายในไลบรารี ก็จะลำบากหากตอนที่แอปพลิเคชันซึ่งใช้ไลบรารีนั้นรันอยู่ กลับมีล็อกถูกพิมพ์ออกไปที่คอนโซลหรือไฟล์เองตามอำเภอใจ
อ๋อ ที่แท้ก็เป็น SHOW GN นี่เอง
ช่วงนี้หลาย ecosystem เลือกรูปแบบที่รับ logger จากภายนอกเข้ามา inject กันเยอะ เลยดูเหมือนจะเป็นอีกเหตุผลที่ผมไม่ค่อยอินกับมันเท่าไร
เพราะถ้าไม่ได้ตั้งค่าไว้ มันก็ไม่ทำงานอยู่แล้วเป็นธรรมดา
แต่ถึงอย่างนั้น มันก็เป็นอินเทอร์เฟซ logger ที่ยังไม่เคยมีใน ecosystem นี้มาก่อน และเพราะมีความยืดหยุ่นสูง เลยรู้สึกว่าน่าจะดีกว่า
ในกรณีเกณฑ์ benchmark ที่ให้มานั้น เนื่องจากเป็นการส่งออกแบบ null output โดยตัด system call ออกไป
ผมเลยคิดว่าในจุดนี้ผลก็น่าจะต่างกันได้ชัดเจนตามรูปแบบของ logger ภายใน
ตรงนี้ต่างกับ Pino ได้มากถึงสามเท่าเลยนะครับ โอ้โห
FYI: เพิ่มเติมจากที่ผมเคยพูดไว้ รูปแบบ logger ที่รับ inject จากภายนอกนั้น ถ้าดูแค่ Openai Node sdk ก็จะเห็นได้ง่ายเลยว่าเป็นรูปแบบที่รับ logger จากภายนอกมาแล้วค่อยทำการ output ครับ
https://github.com/dahlia/logtape/…