Reading Files

package main
 
import (
 "bufio"
 "fmt"
 "io"
 "os"
 "path/filepath"
)
 
func check(e error) {
 if e != nil {
  panic(e)
 }
}
 
func main() {
 // Read all contents to the memory.
 path := filepath.Join(os.TempDir(), "dat")
 dat, err := os.ReadFile(path)
 check(err)
 fmt.Print(string(dat))
 
 // When we want more control over how and what parts of a file are read.
 // For these tasks, start by Opening a file to obtain an `os.File` value.
 f, err := os.Open(path)
 check(err)
 defer f.Close() // Don't forget to close the file.
 
 b1 := make([]byte, 5)
 n1, err := f.Read(b1)
 check(err)
 fmt.Printf("%d bytes: %s\n", n1, string(b1[:n1]))
 
 o2, err := f.Seek(6, io.SeekStart) // Count from start
 check(err)
 b2 := make([]byte, 2)
 n2, err := f.Read(b2)
 check(err)
 fmt.Printf("%d bytes @ %d: ", n2, o2)
 fmt.Printf("%s\n", string(b2[:n2]))
 
 _, err = f.Seek(2, io.SeekCurrent) // Count from current position
 check(err)
 
 _, err = f.Seek(-4, io.SeekEnd) // Or count backwards from end
 check(err)
 
 o3, err := f.Seek(6, io.SeekStart)
 check(err)
 b3 := make([]byte, 2)
 // A more robust choice, avoid short read.
 n3, err := io.ReadAtLeast(f, b3, 2)
 // Note if `len(b3) < 2`, panic will occurred.
 // We can use `n3, err := io.ReadFull(f, b3)` instead.
 check(err)
 fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3))
 
 _, err = f.Seek(0, io.SeekStart) // No rewind, use this.
 check(err)
 
 // The bufio package implements a buffered reader that may be useful
 // both for its efficiency with many small reads and
 // because of the additional reading methods it provides.
 r4 := bufio.NewReader(f)
 b4, err := r4.Peek(5) // Will not move the pointer.
 check(err)
 fmt.Printf("5 bytes: %s\n", string(b4))
}
/*
$ echo "hello" > /tmp/dat
$ echo "go" >> /tmp/dat
$ run cmd/read/read.go
hello
go
5 bytes: hello
2 bytes @ 6: go
2 bytes @ 6: go
5 bytes: hello
*/

Writing Files

It follow the similar pattern to Reading Files

package main
 
import (
 "bufio"
 "fmt"
 "os"
 "path/filepath"
)
 
func check(e error) {
 if e != nil {
  panic(e)
 }
}
 
func main() {
 // Dump bytes to the file directly.
 d1 := []byte("hello\ngo\n")
 path1 := filepath.Join(os.TempDir(), "dat1")
 err := os.WriteFile(path1, d1, 0o644)
 check(err)
 
 // Create the file and get its pointer.
 path2 := filepath.Join(os.TempDir(), "dat2")
 f, err := os.Create(path2)
 check(err)
 defer f.Close()
 
 // Write byte slices.
 d2 := []byte{115, 111, 109, 101, 10}
 n2, err := f.Write(d2)
 check(err)
 fmt.Printf("wrote %d bytes\n", n2)
 
 // Write strings.
 n3, err := f.WriteString("writes\n")
 check(err)
 fmt.Printf("wrote %d bytes\n", n3)
 
 // Flush to storage
 f.Sync()
 
 // Buffered writers.
 w := bufio.NewWriter(f)
 n4, err := w.WriteString("buffered\n")
 check(err)
 fmt.Printf("wrote %d bytes\n", n4)
 
 // Ensure buffers applied to writers
 w.Flush()
}
/*
$ go run cmd/write/write.go
wrote 5 bytes
wrote 7 bytes
wrote 9 bytes
$ cat /tmp/dat1
hello
go
$ cat /tmp/dat2
some
writes
buffered
*/

Logging

package main
 
import (
 "bytes"
 "fmt"
 "log"
 "log/slog"
 "os"
)
 
func main() {
 log.Println("Standard Log") // Output to `os.Stderr`
 
 // Loggers can be configured with flags to set their output format.
 // By default, the standard logger has the `log.Ldate` and `log.Ltime` flags set,
 // and these are collected in `log.LstdFlags`.
 // We can change its flags to emit time with microsecond accuracy, for example.
 log.SetFlags(log.LstdFlags | log.Lmicroseconds)
 log.Println("with micro")
 
 // Emitting the file name and line from which the log function is called.
 log.SetFlags(log.LstdFlags | log.Lshortfile)
 log.Println("with file/line")
 
 // Create a logger with prefix specified.
 mylog := log.New(os.Stdout, "my:", log.LstdFlags)
 mylog.Println("from mylog")
 
 // And change it.
 mylog.SetPrefix("ohmy:")
 mylog.Println("from mylog")
 
 // The output target could be different from stdout.
 var buf bytes.Buffer
 buflog := log.New(&buf, "buf:", log.LstdFlags)
 
 buflog.Println("hello")
 fmt.Print("from buflog:", buf.String())
 
 // The slog package provides structured log output.
 // For example, logging in JSON format is straightforward.
 jsonHandler := slog.NewJSONHandler(os.Stderr, nil)
 myslog := slog.New(jsonHandler)
 myslog.Info("hi there")
 
 // In addition to the message, slog output can contain an arbitrary number of key=value pairs.
 myslog.Info("hello again", "key", "val", "age", 25)
}
/*
2026/05/30 14:15:25 Standard Log
2026/05/30 14:15:25.508164 with micro
2026/05/30 14:15:25 logging.go:23: with file/line
my:2026/05/30 14:15:25 from mylog
ohmy:2026/05/30 14:15:25 from mylog
from buflog:buf:2026/05/30 14:15:25 hello
{"time":"2026-05-30T14:15:25.508210182+08:00","level":"INFO","msg":"hi there"}
{"time":"2026-05-30T14:15:25.508224497+08:00","level":"INFO","msg":"hello again","key":"val","age":25}
*/