Logrus เป็นตัวบันทึกที่มีโครงสร้างสำหรับ Go (golang) ซึ่งเป็น API ที่สมบูรณ์ซึ่งเข้ากันได้กับตัวบันทึกไลบรารีมาตรฐาน
Logrus อยู่ในโหมดบำรุงรักษา เราจะไม่แนะนำคุณสมบัติใหม่ มันยากเกินไปที่จะทำในแบบที่ไม่ทำลายโปรเจ็กต์ของหลายๆ คน ซึ่งเป็นสิ่งสุดท้ายที่คุณต้องการจากไลบรารี Logging ของคุณ (อีกครั้ง...)
นี่ไม่ได้หมายความว่า Logrus ตายแล้ว Logrus จะยังคงได้รับการบำรุงรักษาเพื่อความปลอดภัย (เข้ากันได้แบบย้อนหลัง) การแก้ไขข้อบกพร่อง และประสิทธิภาพ (ซึ่งเราถูกจำกัดโดยอินเทอร์เฟซ)
ฉันเชื่อว่าการสนับสนุนที่ยิ่งใหญ่ที่สุดของ Logrus คือการมีส่วนร่วมในการใช้การตัดไม้แบบมีโครงสร้างอย่างแพร่หลายในปัจจุบันใน Golang ดูเหมือนจะไม่มีเหตุผลที่จะต้องทำการทำซ้ำครั้งสำคัญใน Logrus V2 เนื่องจากชุมชน Go ที่ยอดเยี่ยมได้สร้างสิ่งเหล่านั้นขึ้นมาอย่างอิสระ ทางเลือกที่ยอดเยี่ยมมากมายได้ผุดขึ้นมา Logrus จะมีลักษณะเช่นนั้น หากได้รับการออกแบบใหม่ด้วยสิ่งที่เรารู้เกี่ยวกับการบันทึกแบบมีโครงสร้างใน Go ในปัจจุบัน ลองดู Zerolog, Zap และ Apex เป็นต้น
เห็นปัญหาที่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่แปลก ๆ ใช่ไหม? ในอดีตเราสามารถนำเข้า Logrus ได้ทั้งตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก เนื่องจากสภาพแวดล้อมแพ็คเกจ Go สิ่งนี้ทำให้เกิดปัญหาในชุมชนและเราจำเป็นต้องมีมาตรฐาน สภาพแวดล้อมบางอย่างประสบปัญหากับตัวแปรตัวพิมพ์ใหญ่ ดังนั้นจึงตัดสินใจใช้ตัวพิมพ์เล็ก ทุกอย่างที่ใช้ logrus
จะต้องใช้ตัวพิมพ์เล็ก: github.com/sirupsen/logrus
แพ็คเกจใดที่ไม่ใช่ควรเปลี่ยน
หากต้องการแก้ไข Glide โปรดดูความคิดเห็นเหล่านี้ สำหรับคำอธิบายเชิงลึกเกี่ยวกับปัญหาเคส โปรดดูความคิดเห็นนี้
รหัสสีอย่างสวยงามในการพัฒนา (เมื่อแนบ TTY มิฉะนั้นจะเป็นข้อความธรรมดา):
ด้วย log.SetFormatter(&log.JSONFormatter{})
เพื่อให้แยกวิเคราะห์ได้ง่ายด้วย logstash หรือ Splunk:
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
{"level":"warning","msg":"The group's number increased tremendously!",
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
ด้วยค่าดีฟอลต์ log.SetFormatter(&log.TextFormatter{})
เมื่อไม่ได้แนบ TTY เอาต์พุตจะเข้ากันได้กับรูปแบบ logfmt:
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
เพื่อให้แน่ใจถึงลักษณะการทำงานนี้แม้ว่าจะแนบ TTY ไว้แล้ว ให้ตั้งค่าฟอร์แมตเตอร์ของคุณดังนี้:
log . SetFormatter ( & log. TextFormatter {
DisableColors : true ,
FullTimestamp : true ,
})
หากคุณต้องการเพิ่มวิธีการโทรเป็นฟิลด์ ให้สั่งคนตัดไม้ผ่าน:
log . SetReportCaller ( true )
สิ่งนี้จะเพิ่มผู้โทรเป็น 'วิธีการ' ดังนี้:
{ "animal" : " penguin " , "level" : " fatal " , "method" : " github.com/sirupsen/arcticcreatures.migrate " , "msg" : " a penguin swims by " ,
"time" : " 2014-03-10 19:57:38.562543129 -0400 EDT " }
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
โปรดทราบว่านี่เป็นการเพิ่มค่าใช้จ่ายที่วัดได้ - ค่าใช้จ่ายจะขึ้นอยู่กับเวอร์ชันของ Go แต่อยู่ระหว่าง 20 ถึง 40% ในการทดสอบล่าสุดกับ 1.6 และ 1.7 คุณสามารถตรวจสอบสิ่งนี้ได้ในสภาพแวดล้อมของคุณผ่านการวัดประสิทธิภาพ:
go test -bench=.*CallerTracing
ชื่อองค์กรถูกเปลี่ยนเป็นตัวพิมพ์เล็ก--และจะไม่เปลี่ยนกลับ หากคุณได้รับข้อขัดแย้งในการนำเข้าเนื่องจากคำนึงถึงขนาดตัวพิมพ์ โปรดใช้การนำเข้าตัวพิมพ์เล็ก: github.com/sirupsen/logrus
วิธีที่ง่ายที่สุดในการใช้ Logrus คือเพียงตัวบันทึกการส่งออกระดับแพ็คเกจ:
package main
import (
log "github.com/sirupsen/logrus"
)
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
}). Info ( "A walrus appears" )
}
โปรดทราบว่าสามารถใช้งานร่วมกับ API ได้อย่างสมบูรณ์กับ stdlib logger ดังนั้นคุณจึงสามารถแทนที่การนำเข้า log
ของคุณได้ทุกที่ด้วย log "github.com/sirupsen/logrus"
และตอนนี้คุณจะมีความยืดหยุ่นของ Logrus แล้ว คุณสามารถปรับแต่งได้ตามที่คุณต้องการ:
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init () {
// Log as JSON instead of the default ASCII formatter.
log . SetFormatter ( & log. JSONFormatter {})
// Output to stdout instead of the default stderr
// Can be any io.Writer, see below for File example
log . SetOutput ( os . Stdout )
// Only log the warning severity or above.
log . SetLevel ( log . WarnLevel )
}
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
log . WithFields (log. Fields {
"omg" : true ,
"number" : 122 ,
}). Warn ( "The group's number increased tremendously!" )
log . WithFields (log. Fields {
"omg" : true ,
"number" : 100 ,
}). Fatal ( "The ice breaks!" )
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log . WithFields (log. Fields {
"common" : "this is a common field" ,
"other" : "I also should be logged always" ,
})
contextLogger . Info ( "I'll be logged with common and other field" )
contextLogger . Info ( "Me too" )
}
สำหรับการใช้งานขั้นสูง เช่น การบันทึกไปยังหลายตำแหน่งจากแอปพลิเคชันเดียวกัน คุณสามารถสร้างอินสแตนซ์ของ logrus
Logger ได้:
package main
import (
"os"
"github.com/sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
var log = logrus . New ()
func main () {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log . Out = os . Stdout
// You could set this to any `io.Writer` such as a file
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log . WithFields (logrus. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
}
Logrus สนับสนุนการบันทึกที่มีโครงสร้างอย่างระมัดระวังผ่านช่องการบันทึก แทนที่จะแสดงข้อความแสดงข้อผิดพลาดที่ยาวและแยกวิเคราะห์ไม่ได้ ตัวอย่างเช่น แทนที่จะเป็น: log.Fatalf("Failed to send event %s to topic %s with key %d")
คุณควรบันทึกสิ่งที่ค้นพบได้มากขึ้น:
log . WithFields (log. Fields {
"event" : event ,
"topic" : topic ,
"key" : key ,
}). Fatal ( "Failed to send event" )
เราพบว่า API นี้บังคับให้คุณคิดเกี่ยวกับการบันทึกในลักษณะที่สร้างข้อความการบันทึกที่มีประโยชน์มากกว่ามาก เราอยู่ในสถานการณ์นับไม่ถ้วนที่เพียงช่องเดียวที่เพิ่มลงในคำสั่งบันทึกที่มีอยู่แล้วสามารถช่วยเราประหยัดเวลาได้หลายชั่วโมง การเรียก WithFields
เป็นทางเลือก
โดยทั่วไป เมื่อ Logrus ใช้ฟังก์ชัน printf
-family ใดๆ ควรถือเป็นคำใบ้ คุณควรเพิ่มฟิลด์ อย่างไรก็ตาม คุณยังคงสามารถใช้ฟังก์ชัน printf
-family กับ Logrus ได้
บ่อยครั้ง การแนบฟิลด์เข้ากับคำสั่งบันทึกในแอปพลิเคชันหรือบางส่วนของแอปพลิเคชัน เสมอ จะเป็นประโยชน์ ตัวอย่างเช่น คุณอาจต้องการบันทึก request_id
และ user_ip
ในบริบทของคำขอเสมอ แทนที่จะเขียน log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
ในทุกบรรทัด คุณสามารถสร้าง logrus.Entry
เพื่อส่งต่อแทน:
requestLogger := log . WithFields (log. Fields { "request_id" : request_id , "user_ip" : user_ip })
requestLogger. Info ( "something happened on that request" ) # will log request_id and user_ip
requestLogger . Warn ( "something not great happened" )
คุณสามารถเพิ่ม hooks สำหรับระดับการบันทึกได้ ตัวอย่างเช่น การส่งข้อผิดพลาดไปยังบริการติดตามข้อยกเว้นใน Error
, Fatal
และ Panic
ข้อมูลไปยัง StatsD หรือบันทึกไปยังหลายๆ ที่พร้อมกัน เช่น syslog
Logrus มาพร้อมตะขอในตัว เพิ่มสิ่งเหล่านั้นหรือ hook ที่คุณกำหนดเองใน init
:
import (
log "github.com/sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
"log/syslog"
)
func init () {
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log . AddHook ( airbrake . NewHook ( 123 , "xyz" , "production" ))
hook , err := logrus_syslog . NewSyslogHook ( "udp" , "localhost:514" , syslog . LOG_INFO , "" )
if err != nil {
log . Error ( "Unable to connect to local syslog daemon" )
} else {
log . AddHook ( hook )
}
}
หมายเหตุ: Syslog hook ยังรองรับการเชื่อมต่อกับ syslog ภายในเครื่อง (เช่น "/dev/log" หรือ "/var/run/syslog" หรือ "/var/run/log") สำหรับรายละเอียด โปรดตรวจสอบ syslog hook README
รายการบริการ hooks ที่รู้จักในปัจจุบันสามารถพบได้ในหน้าวิกินี้
Logrus มีระดับการบันทึกเจ็ดระดับ: ติดตาม ดีบัก ข้อมูล คำเตือน ข้อผิดพลาด ร้ายแรง และตื่นตระหนก
log . Trace ( "Something very low level." )
log . Debug ( "Useful debugging information." )
log . Info ( "Something noteworthy happened!" )
log . Warn ( "You should probably take a look at this." )
log . Error ( "Something failed but I'm not quitting." )
// Calls os.Exit(1) after logging
log . Fatal ( "Bye." )
// Calls panic() after logging
log . Panic ( "I'm bailing." )
คุณสามารถตั้งค่าระดับการบันทึกบน Logger
ได้ จากนั้นมันจะบันทึกเฉพาะรายการที่มีความรุนแรงนั้นหรืออะไรก็ตามที่สูงกว่านั้น:
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log . SetLevel ( log . InfoLevel )
อาจมีประโยชน์ในการตั้งค่า log.Level = logrus.DebugLevel
ในสภาพแวดล้อมการดีบักหรือแบบละเอียดหากแอปพลิเคชันของคุณมีสิ่งนั้น
หมายเหตุ: หากคุณต้องการระดับการบันทึกที่แตกต่างกันสำหรับส่วนกลาง ( log.SetLevel(...)
) และการบันทึก syslog โปรดตรวจสอบ syslog hook README
นอกจากฟิลด์ที่เพิ่มด้วย WithField
หรือ WithFields
แล้ว บางฟิลด์จะถูกเพิ่มในเหตุการณ์การบันทึกทั้งหมดโดยอัตโนมัติ:
time
. การประทับเวลาเมื่อมีการสร้างรายการmsg
ข้อความบันทึกที่ส่งผ่านไปยัง {Info,Warn,Error,Fatal,Panic}
หลังจากการเรียก AddFields
เช่น Failed to send event.
level
. ระดับการบันทึก เช่น info
. Logrus ไม่มีแนวคิดเรื่องสิ่งแวดล้อม
หากคุณต้องการให้ hooks และฟอร์แมตเตอร์ใช้เฉพาะในสภาพแวดล้อมเฉพาะ คุณควรจัดการด้วยตนเอง ตัวอย่างเช่น หากแอปพลิเคชันของคุณมีตัวแปรโกลบอล Environment
ซึ่งเป็นการแสดงสตริงของสภาพแวดล้อม คุณสามารถทำได้:
import (
log "github.com/sirupsen/logrus"
)
func init () {
// do something here to set environment depending on an environment variable
// or command-line flag
if Environment == "production" {
log . SetFormatter ( & log. JSONFormatter {})
} else {
// The TextFormatter is default, you don't actually have to do this.
log . SetFormatter ( & log. TextFormatter {})
}
}
การกำหนดค่านี้เป็นวิธีที่ตั้งใจจะใช้ logrus
แต่ JSON ในการใช้งานจริงส่วนใหญ่จะมีประโยชน์ก็ต่อเมื่อคุณทำการรวมบันทึกด้วยเครื่องมือเช่น Splunk หรือ Logstash
ตัวจัดรูปแบบการบันทึกในตัวคือ:
logrus.TextFormatter
บันทึกเหตุการณ์เป็นสีหาก stdout เป็น tty หรือไม่มีสีForceColors
เป็น true
หากต้องการบังคับให้ไม่มีเอาต์พุตสีแม้ว่าจะมี TTY ให้ตั้งค่าฟิลด์ DisableColors
เป็น true
สำหรับ Windows โปรดดูที่ github.com/mattn/go-colorableDisableLevelTruncation
เป็น true
PadLevelText
เป็น true
จะเปิดใช้งานลักษณะการทำงานนี้ โดยการเพิ่มช่องว่างภายในข้อความระดับlogrus.JSONFormatter
บันทึกฟิลด์เป็น JSONตัวจัดรูปแบบการบันทึกของบุคคลที่สาม:
FluentdFormatter
. จัดรูปแบบรายการที่สามารถแยกวิเคราะห์โดย Kubernetes และ Google Container EngineGELF
. จัดรูปแบบรายการเพื่อให้สอดคล้องกับข้อกำหนด GELF 1.1 ของ Grayloglogstash
บันทึกฟิลด์เป็นเหตุการณ์ Logstashprefixed
แสดงแหล่งที่มาของรายการบันทึกพร้อมกับเค้าโครงทางเลือกzalgo
ปลุกพลังแห่งซัลโกnested-logrus-formatter
แปลงฟิลด์ logrus เป็นโครงสร้างแบบซ้อนpowerful-logrus-formatter
รับชื่อไฟล์ หมายเลขบรรทัดของบันทึก และชื่อฟังก์ชันล่าสุดเมื่อพิมพ์บันทึก บันทึกบันทึกลงในไฟล์caption-json-formatter
ตัวจัดรูปแบบข้อความ json ของ logrus พร้อมเพิ่มคำบรรยายที่มนุษย์สามารถอ่านได้ คุณสามารถกำหนดฟอร์แมตเตอร์ของคุณได้โดยใช้อินเทอร์เฟซ Formatter
ซึ่งต้องใช้วิธี Format
Format
ต้องใช้ *Entry
entry.Data
เป็นประเภท Fields
( map[string]interface{}
) พร้อมด้วยฟิลด์ทั้งหมดของคุณรวมถึงฟิลด์เริ่มต้น (ดูหัวข้อรายการด้านบน):
type MyJSONFormatter struct {
}
log . SetFormatter ( new ( MyJSONFormatter ))
func ( f * MyJSONFormatter ) Format ( entry * Entry ) ([] byte , error ) {
// Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers.
serialized , err := json . Marshal ( entry . Data )
if err != nil {
return nil , fmt . Errorf ( "Failed to marshal fields to JSON, %w" , err )
}
return append ( serialized , 'n' ), nil
}
io.Writer
Logrus สามารถแปลงเป็น io.Writer
ได้ ผู้เขียนคนนั้นคือจุดสิ้นสุดของ io.Pipe
และเป็นความรับผิดชอบของคุณที่จะต้องปิดมัน
w := logger . Writer ()
defer w . Close ()
srv := http. Server {
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog : log . New ( w , "" , 0 ),
}
แต่ละบรรทัดที่เขียนถึงผู้เขียนนั้นจะถูกพิมพ์ด้วยวิธีปกติ โดยใช้ฟอร์แมตเตอร์และ hooks ระดับสำหรับรายการเหล่านั้นคือ info
ซึ่งหมายความว่าเราสามารถแทนที่ไลบรารี่ logger มาตรฐานได้อย่างง่ายดาย:
logger := logrus . New ()
logger . Formatter = & logrus. JSONFormatter {}
// Use logrus for standard log output
// Note that `log` here references stdlib's log
// Not logrus imported under the name `log`.
log . SetOutput ( logger . Writer ())
การหมุนเวียนบันทึกไม่ได้มาพร้อมกับ Logrus การหมุนเวียนบันทึกควรทำโดยโปรแกรมภายนอก (เช่น logrotate(8)
) ที่สามารถบีบอัดและลบรายการบันทึกเก่าได้ ไม่ควรเป็นคุณลักษณะของตัวบันทึกระดับแอปพลิเคชัน
เครื่องมือ | คำอธิบาย |
---|---|
โลกรัส เมท | Logrus mate เป็นเครื่องมือสำหรับ Logrus ในการจัดการตัวบันทึก คุณสามารถเริ่มต้นระดับของตัวบันทึก เชื่อมต่อและฟอร์แมตเตอร์ด้วยไฟล์กำหนดค่า ตัวบันทึกจะถูกสร้างขึ้นด้วยการกำหนดค่าที่แตกต่างกันในสภาพแวดล้อมที่แตกต่างกัน |
Logrus Viper ผู้ช่วย | ตัวช่วยรอบ ๆ Logrus ที่ห่อด้วย spf13/Viper เพื่อโหลดการกำหนดค่าด้วยเขี้ยว! และเพื่อให้การกำหนดค่า Logrus ง่ายขึ้น ให้ใช้พฤติกรรมบางอย่างของ Logrus Mate ตัวอย่าง |
Logrus มีสิ่งอำนวยความสะดวกในตัวสำหรับยืนยันการมีอยู่ของข้อความบันทึก สิ่งนี้ถูกนำไปใช้ผ่าน hook test
และจัดเตรียม:
test.NewLocal
และ test.NewGlobal
) ซึ่งโดยทั่วไปเพียงเพิ่มตะขอ test
test.NewNullLogger
) ที่เพิ่งบันทึกข้อความบันทึก (และไม่ส่งออกใด ๆ ): import (
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSomething ( t * testing. T ){
logger , hook := test . NewNullLogger ()
logger . Error ( "Helloerror" )
assert . Equal ( t , 1 , len ( hook . Entries ))
assert . Equal ( t , logrus . ErrorLevel , hook . LastEntry (). Level )
assert . Equal ( t , "Helloerror" , hook . LastEntry (). Message )
hook . Reset ()
assert . Nil ( t , hook . LastEntry ())
}
Logrus สามารถลงทะเบียนฟังก์ชันตั้งแต่หนึ่งฟังก์ชันขึ้นไปที่จะถูกเรียกใช้เมื่อมีการบันทึกข้อความระดับ fatal
ใดๆ ตัวจัดการที่ลงทะเบียนจะถูกดำเนินการก่อนที่ logrus จะดำเนินการ os.Exit(1)
ลักษณะการทำงานนี้อาจเป็นประโยชน์หากผู้โทรจำเป็นต้องปิดระบบอย่างเรียบร้อย ไม่เหมือนกับ panic("Something went wrong...")
การโทรที่สามารถดักฟังได้ด้วยการเลื่อนการเรียก recover
ไปยัง os.Exit(1)
ไม่สามารถดักฟังได้
...
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
...
ตามค่าเริ่มต้น Logger ได้รับการปกป้องโดย mutex สำหรับการเขียนพร้อมกัน mutex จะถูกระงับเมื่อเรียก hooks และเขียนบันทึก หากคุณแน่ใจว่าไม่จำเป็นต้องล็อคดังกล่าว คุณสามารถเรียก logger.SetNoLock() เพื่อปิดการใช้งานการล็อคได้
สถานการณ์ที่ไม่จำเป็นต้องล็อค ได้แก่:
คุณไม่ได้ลงทะเบียน hooks หรือการเรียก hooks นั้นปลอดภัยต่อเธรดแล้ว
การเขียนถึง logger.Out นั้นปลอดภัยสำหรับเธรดอยู่แล้ว ตัวอย่างเช่น:
logger.Out ได้รับการปกป้องด้วยการล็อค
logger.Out เป็นตัวจัดการ os.File ที่เปิดด้วยแฟล็ก O_APPEND
และทุกการเขียนมีขนาดเล็กกว่า 4k (ซึ่งอนุญาตให้เขียนแบบหลายเธรด/หลายกระบวนการ)
(อ้างถึงhttp://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/ )