ประโยชน์ของการบันทึกอย่างมีโครงสร้างเทียบกับการบันทึกขั้นพื้นฐาน


110

เรากำลังสร้างแอปใหม่และฉันต้องการรวมการบันทึกที่มีโครงสร้าง การตั้งค่าในอุดมคติของฉันจะเป็นสิ่งที่ต้องการSerilogสำหรับรหัส C # Bunyanของเราและสำหรับ JS ของเรา เหล่านี้จะป้อนเข้าสู่fluentdแล้วจะออกไปจำนวนของสิ่งใด ๆ elasticsearch + kibanaที่ผมคิดในตอนแรก เรามีฐานข้อมูล MySQL อยู่แล้วดังนั้นในระยะสั้นฉันสนใจที่จะติดตั้ง Serilog + Bunyan มากขึ้นและผู้ใช้สามารถใช้งานได้และเราสามารถเข้าสู่ระบบ MySQL ได้ในขณะที่เราใช้เวลาเพิ่มขึ้นเล็กน้อยและส่วนที่เหลือ

อย่างไรก็ตามหนึ่งในนักเขียนโค้ดที่มีประสบการณ์มากกว่าของเราต้องการทำสิ่งที่ชอบ: การlog.debug("Disk quota {0} exceeded by user {1}", quota, user);ใช้log4netและจากนั้นก็เรียกใช้คำสั่งที่เลือกกับ MySQL เช่น:SELECT text FROM logs WHERE text LIKE "Disk quota";

ที่ถูกกล่าวว่าวิธีใดดีกว่าและ / หรือสิ่งใดที่เราต้องพิจารณาเมื่อเลือกประเภทของระบบบันทึก


ฉันเห็นด้วยกับการแก้ไขที่ทำ ฉันไม่ได้พยายามพิสูจน์อะไรบางอย่างกับใครบางคนมากนักในขณะที่ฉันพยายามเข้าใจถึงประโยชน์และความแตกต่างในการบันทึกแบบมีโครงสร้างและแบบพื้นฐาน ในใจของฉันมีโครงสร้างทำให้เรามีความยืดหยุ่นมากขึ้นโดยเฉพาะอย่างยิ่งในแหล่งที่มาของบันทึกและวิธีที่เราสามารถแสดงข้อมูลของพวกเขา ณ จุดที่ฉันเข้าใจแล้วฉันไม่สามารถอธิบายได้ว่าทำไมการบันทึกและค้นหา MySQL แบบพื้นฐานจึงดีกว่าหรือแย่กว่าการบันทึกแบบมีโครงสร้าง
DTI-Matt

2
@ DTI- การบันทึกที่มีโครงสร้างของ serilog ของ Matt เป็นเพียงการบันทึกขั้นพื้นฐานเท่านั้นที่จะจัดรูปแบบวัตถุที่คุณพิมพ์ไป - สิ่งที่คุณสามารถทำได้ด้วยตัวเองด้วยการขี่ ToString อย่างง่ายดาย สิ่งสำคัญยิ่งกว่าคือการกำหนดค่าและการจัดการไฟล์บันทึกไม่ใช่วิธีการจัดรูปแบบสตริงผ่านอีกวิธีหนึ่งคือประสิทธิภาพ หาก dev ต้องการใช้ log4net (ซึ่งเป็น lib การบันทึกที่ดี) ดังนั้นคุณสามารถเลือก serilog (ซึ่งดูดี) เป็นหนึ่งใน "วิธีแก้ปัญหาในการค้นหาปัญหา"
gbjbaanb

@ DTI-Matt จากการดู serilog มันดูคล้ายกับ log4net มาก log4net จัดการการสร้างบันทึกที่มีโครงสร้างในการกำหนดค่า คุณไม่จำเป็นต้องค้นหาข้อความบันทึกเนื่องจากคุณสามารถกำหนดค่าและเขียนข้อมูลเพิ่มเติมลงในตารางได้ นอกจากนี้ยังกำหนดค่า log4net สำหรับ fluentd tipstuff.org/2014/05/
RubberChickenLeader

ระวังมีคนโง่บางคนที่ไม่เข้าใจแนวคิดของคำถามเชิงแนวคิดที่นี่ ถามเกี่ยวกับทิศทางของแอปพลิเคชันฐานข้อมูลเพื่อพยายามจัดการกับความสามารถของ ETL v. code คุณจะได้รับ downvotes ที่ร้ายแรง ฉันคิดว่าคำถามของคุณจะอยู่ในเขียงด้วย
user3916597

2
@gbjbaanb Serilog ทำงานในลักษณะเดียวกับ log4net เมื่อเรนเดอร์เหตุการณ์เป็นข้อความ แต่ถ้าคุณใช้รูปแบบที่มีโครงสร้างเพื่อเก็บบันทึกมันจะเชื่อมโยงคุณสมบัติที่มีชื่อกับอาร์กิวเมนต์ที่ส่งผ่าน (เช่นเพื่อสนับสนุนการค้นหา / กรองโดยไม่ต้อง regex เป็นต้น) ) HTH!
Nicholas Blumhardt

คำตอบ:


140

มีความก้าวหน้าพื้นฐานสองประการด้วยวิธีการที่มีโครงสร้างซึ่งไม่สามารถเลียนแบบได้โดยใช้ความพยายามเพิ่มเติม (บางครั้งระดับมาก)

ประเภทเหตุการณ์

เมื่อคุณเขียนสองเหตุการณ์ด้วยlog4netเช่น:

log.Debug("Disk quota {0} exceeded by user {1}", 100, "DTI-Matt");
log.Debug("Disk quota {0} exceeded by user {1}", 150, "nblumhardt");

สิ่งเหล่านี้จะสร้างข้อความที่คล้ายกัน:

Disk quota 100 exceeded by user DTI-Matt
Disk quota 150 exceeded by user nblumhardt

แต่เท่าที่เกี่ยวข้องกับการประมวลผลของเครื่องพวกเขามีเพียงข้อความสองบรรทัดที่แตกต่างกัน

คุณอาจต้องการค้นหาเหตุการณ์ "เกินโควต้าดิสก์" แต่กรณีง่าย ๆ ของการค้นหาเหตุการณ์like 'Disk quota%'จะล้มลงทันทีที่มีเหตุการณ์อื่นเกิดขึ้นเช่น:

Disk quota 100 set for user DTI-Matt

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

ในทางตรงกันข้ามเมื่อคุณเขียนเหตุการณ์Serilogสองเหตุการณ์ต่อไปนี้:

log.Debug("Disk quota {Quota} exceeded by user {Username}", 100, "DTI-Matt");
log.Debug("Disk quota {Quota} exceeded by user {Username}", 150, "nblumhardt");

สิ่งเหล่านี้สร้างเอาต์พุตข้อความที่คล้ายกับเวอร์ชัน log4net แต่เบื้องหลังแล้ว"Disk quota {Quota} exceeded by user {Username}" เทมเพลตข้อความจะถูกดำเนินการโดยทั้งสองเหตุการณ์

ด้วยอ่างล้างจานที่เหมาะสมในภายหลังคุณสามารถเขียนคำสั่งwhere MessageTemplate = 'Disk quota {Quota} exceeded by user {Username}'และได้รับว่าเหตุการณ์ที่เกิดขึ้นที่โควต้าดิสก์ที่ถูกเกิน

มันไม่ได้เสมอสะดวกในการเก็บแม่แบบข้อความทั้งหมดที่มีการบันทึกเหตุการณ์ทุกครั้งเพื่ออ่างล้างมือบางสับแม่แบบข้อความเป็นตัวเลขEventTypeค่า (เช่น0x1234abcd) หรือคุณสามารถเพิ่ม Enricher ที่จะเข้าสู่ระบบท่อที่จะทำเช่นนี้ด้วยตัวคุณเอง

มันละเอียดกว่าความแตกต่างด้านล่างถัดไป แต่เป็นสิ่งที่ทรงพลังอย่างมากเมื่อต้องจัดการกับบันทึกปริมาณมาก

ข้อมูลที่มีโครงสร้าง

like 'Disk quota' and like 'DTI-Matt'อีกครั้งเมื่อพิจารณาจากสองเหตุการณ์เกี่ยวกับการใช้พื้นที่ดิสก์ก็อาจจะง่ายพอที่ใช้บันทึกข้อความเพื่อสอบถามสำหรับผู้ใช้โดยเฉพาะอย่างยิ่งกับ

แต่การวิเคราะห์การผลิตไม่ได้ตรงไปตรงมาเสมอ ลองนึกภาพว่าจำเป็นต้องหากิจกรรมที่มีโควต้าดิสก์เกินกว่า 125 MB หรือไม่

ด้วย Serilog สิ่งนี้เป็นไปได้ในการจมส่วนใหญ่โดยใช้ตัวแปร:

Quota < 125

การสร้างแบบสอบถามชนิดนี้จากการแสดงออกปกติเป็นไปได้ แต่มันก็เหนื่อยเร็วและมักจะกลายเป็นตัวชี้วัดสุดท้าย

ตอนนี้เพิ่มประเภทเหตุการณ์นี้:

Quota < 125 and EventType = 0x1234abcd

คุณเริ่มเห็นว่าความสามารถเหล่านี้รวมกันในวิธีที่ตรงไปตรงมาเพื่อให้การดีบักการผลิตกับบันทึกรู้สึกเหมือนเป็นกิจกรรมการพัฒนาชั้นหนึ่ง

ข้อดีอีกประการหนึ่งอาจไม่ใช่เรื่องง่ายที่จะป้องกันล่วงหน้า แต่เมื่อการดีบักการผลิตได้ถูกยกออกจากดินแดนแห่งแฮกเกอร์ regex นักพัฒนาเริ่มบันทึกค่ามากขึ้นและใช้ความระมัดระวังมากขึ้นและพิจารณาเมื่อเขียน บันทึกที่ดีขึ้น -> แอปพลิเคชันที่มีคุณภาพดีขึ้น -> มีความสุขมากขึ้นทุกรอบ


4
ฉันรักคำตอบนี้ เขียนได้ดีมากและด้วยเหตุผลบางอย่างที่ฉันไม่สามารถอธิบายได้ทำให้ฉันอยู่ที่ขอบที่นั่งของฉัน
jokab

16

เมื่อคุณกำลังรวบรวมบันทึกสำหรับการประมวลผลไม่ว่าจะเป็นการแยกวิเคราะห์ลงในฐานข้อมูลและ / หรือค้นหาผ่านบันทึกการประมวลผลในภายหลังการใช้การบันทึกที่มีโครงสร้างจะทำให้การประมวลผลบางอย่างง่ายขึ้น / มีประสิทธิภาพมากขึ้น parser สามารถใช้ประโยชน์จากโครงสร้างที่รู้จัก ( เช่น JSON, XML, ASN.1, อะไรก็ตาม) และใช้เครื่องของรัฐสำหรับการแยกวิเคราะห์เมื่อเทียบกับการแสดงออกปกติ (ซึ่งอาจมีราคาแพงในการคำนวณ แยกของข้อความแบบฟรีฟอร์มเช่นที่แนะนำโดยเพื่อนร่วมงานของคุณมีแนวโน้มที่จะพึ่งพาการแสดงออกปกติและต้องพึ่งพาข้อความที่ไม่เปลี่ยนแปลง สิ่งนี้สามารถทำให้การแยกวิเคราะห์ข้อความในรูปแบบอิสระค่อนข้างบอบบาง ( เช่นการแยกวิเคราะห์เป็นคู่กับข้อความที่แน่นอนในรหัส)

พิจารณากรณีการค้นหา / ค้นหาเช่น :

SELECT text FROM logs WHERE text LIKE "Disk quota";

LIKEเงื่อนไขจำเป็นต้องมีการเปรียบเทียบกับtextค่าของแถวทุกแถว อีกครั้งการคำนวณนี้ค่อนข้างแพงโดยเฉพาะเมื่อใช้สัญลักษณ์แทน:

SELECT text FROM logs WHERE text LIKE "Disk %";

ด้วยการบันทึกที่มีโครงสร้างข้อความบันทึกข้อผิดพลาดเกี่ยวกับดิสก์ของคุณอาจมีลักษณะเช่นนี้ใน JSON:

{ "level": "DEBUG", "user": "username", "error_type": "disk", "text": "Disk quota ... exceeded by user ..." }

สาขาของโครงสร้างประเภทนี้สามารถแมปสวยได้อย่างง่ายดายเช่นชื่อคอลัมน์ตาราง SQL ซึ่งหมายความว่าการค้นหาสามารถเจาะจง / ละเอียดมากขึ้น:

SELECT user, text FROM logs WHERE error_type = "disk";

คุณสามารถวางดัชนีในคอลัมน์ที่มีค่าที่คุณคาดหวังในการค้นหา / การค้นหาบ่อยตราบใดที่คุณไม่ได้ใช้LIKEคำสั่งสำหรับค่าในคอลัมน์เหล่านั้น ยิ่งคุณสามารถแบ่งข้อความบันทึกของคุณออกเป็นหมวดหมู่เฉพาะได้มากเท่าไหร่คุณก็ยิ่งกำหนดเป้าหมายได้มากขึ้นเท่านั้น ตัวอย่างเช่นนอกเหนือจากerror_typeฟิลด์ / คอลัมน์ในตัวอย่างข้างต้นคุณสามารถสร้างได้"error_category": "disk", "error_type": "quota"หรือแม้แต่บางอย่าง

โครงสร้างอื่น ๆ ที่คุณมีในข้อความเข้าสู่ระบบของคุณระบบมากขึ้นการแยกของคุณ / ค้นหา (เช่นfluentd, elasticsearch, kibana) สามารถใช้ประโยชน์จากโครงสร้างและดำเนินการงานของพวกเขามีความเร็วมากขึ้นและ CPU น้อย / หน่วยความจำ

หวังว่านี่จะช่วยได้!


1
+1 ต้องการเพิ่มว่าไม่ใช่เรื่องความเร็วและประสิทธิภาพเท่านั้น ความเกี่ยวข้องของผลลัพธ์การค้นหาจะสูงขึ้นมากเมื่อใช้การบันทึกที่มีโครงสร้างและทำให้ "คิวรีที่มีโครงสร้าง" หากไม่มีการค้นหาคำใด ๆ ที่เกิดขึ้นในบริบทที่แตกต่างกันจะทำให้คุณมีเพลงฮิตที่ไม่เกี่ยวข้องมากมาย
Marjan Venema

1
+1 จากฉันเช่นกันฉันคิดว่านี่เป็นอย่างนั้น เพิ่มสูตรที่แตกต่างกันเล็กน้อยด้านล่างเพื่อขยายในกรณีของประเภทเหตุการณ์เช่นกัน
Nicholas Blumhardt

8

คุณจะไม่ได้รับประโยชน์มากนักจากการบันทึกที่มีโครงสร้างเมื่อแอปของคุณสร้างข้อความบันทึกสองสามร้อยข้อความต่อวัน คุณจะทำอย่างแน่นอนเมื่อคุณมีข้อความบันทึกสองสามร้อยข้อความต่อวินาทีมาจากแอพที่ปรับใช้มากมาย

ที่เกี่ยวข้องการตั้งค่าที่บันทึกข้อความสิ้นสุดในELK Stackยังเหมาะสำหรับสเกลที่การบันทึกไปยัง SQL กลายเป็นปัญหาคอขวด

ฉันได้เห็นการตั้งค่าของ "การบันทึกและการค้นหาพื้นฐาน" ด้วย SQL select .. likeและ regexps ถูกผลักดันให้ถึงขีด จำกัด ที่มันแยกออกจากกัน - มีผลบวกปลอมละเว้นละเว้นรหัสตัวกรองที่น่ากลัวพร้อมข้อบกพร่อง knwon ที่ยากต่อการบำรุงรักษา ข้อความบันทึกใหม่ที่ไม่เป็นไปตามข้อสันนิษฐานของตัวกรองลังเลที่จะสัมผัสคำสั่งการบันทึกในรหัสเพื่อมิให้ทำลายรายงาน ฯลฯ

แพคเกจซอฟต์แวร์จำนวนมากเกิดขึ้นเพื่อจัดการกับปัญหานี้ในทางที่ดีขึ้น มี Serilog ฉันได้ยินมาว่าทีมงาน NLog กำลังดูอยู่และเราเขียนStructuredLogging.Jsonหา Nlogฉันยังเห็นว่าการบันทึกการใช้งานหลักหลัก ASP.Netใหม่"ทำให้ผู้ให้บริการการบันทึกสามารถใช้ ... การบันทึกที่มีโครงสร้าง"

ตัวอย่างด้วย StructuredLogging คุณเข้าสู่ระบบ NLog logger ดังนี้

logger.ExtendedError("Order send failed", new { OrderId = 1234, RestaurantId = 4567 } );

ข้อมูลที่มีโครงสร้างนี้ไปที่ kibana ค่า1234จะถูกเก็บไว้ในOrderIdฟิลด์ของรายการบันทึก จากนั้นคุณสามารถค้นหาโดยใช้ไวยากรณ์แบบสอบถาม kibana @LogType:nlog AND Level:Error AND OrderId:1234สำหรับเช่นรายการบันทึกทุกที่

MessageและOrderIdตอนนี้เป็นเพียงฟิลด์ที่สามารถค้นหาการจับคู่แบบตรงทั้งหมดหรือไม่แน่นอนตามที่คุณต้องการหรือรวมไว้สำหรับการนับ นี่คือพลังและความยืดหยุ่น

จากแนวทางปฏิบัติที่ดีที่สุดของ StructuredLogging :

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

ข้อความที่บันทึกควรมีความแตกต่างนั่นคือไม่เหมือนกับข้อความที่สร้างโดยคำสั่งบันทึกที่ไม่เกี่ยวข้อง จากนั้นค้นหามันไม่ตรงกับสิ่งที่ไม่เกี่ยวข้องเช่นกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.