Add to the end of a file without erasing what is already in it, using os.OpenFile and the O_APPEND flag.
os.WriteFile and os.Create both truncate. Appending needs os.OpenFile, which takes the flags directly.
Combine the flags with |. Each one answers a separate question about how the file opens.
f, err := os.OpenFile("events.log",
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
// handle error
}
defer f.Close()
if _, err := f.WriteString("deploy finished\n"); err != nil {
// handle error
}O_APPEND is the flag doing the work. It moves the write position to the end of the file before every write, so two processes appending to the same log interleave lines instead of overwriting each other.
The 0644 permission applies only when the file gets created.
os.O_APPEND // write at the end, every time
os.O_CREATE // make the file if it is missing
os.O_WRONLY // open for writing only
// Without O_APPEND, writes start at offset 0 and
// overwrite whatever is already there.
f, _ := os.OpenFile("events.log", os.O_CREATE|os.O_WRONLY, 0644)Appending many lines
Each WriteString on a raw file can cost a system call. bufio.Writer collects small writes and hands them over in larger chunks.
Anything still in the buffer when the program ends is lost. Declare defer w.Flush() after the file's own defer, so it runs first.
f, err := os.OpenFile("events.log",
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
// handle error
}
defer f.Close()
w := bufio.NewWriter(f)
defer w.Flush() // runs before f.Close, so the bytes land
for _, e := range events {
fmt.Fprintln(w, e)
}Working program
A complete program that appends three timestamped lines to an audit log, then prints the file.
package main
import (
"fmt"
"os"
"time"
)
// appendEvent adds one timestamped line to an audit log.
func appendEvent(path, msg string) error {
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
stamp := time.Now().UTC().Format(time.RFC3339)
_, err = fmt.Fprintf(f, "%s %s\n", stamp, msg)
return err
}
func main() {
os.Remove("audit.log")
for _, msg := range []string{"login", "upload", "logout"} {
if err := appendEvent("audit.log", msg); err != nil {
fmt.Println("append failed:", err)
return
}
}
data, _ := os.ReadFile("audit.log")
fmt.Print(string(data))
}Run it. Running it a second time adds three more lines rather than replacing the first three.
$ go run main.go
2026-08-29T14:02:11Z login
2026-08-29T14:02:11Z upload
2026-08-29T14:02:11Z logout