fmt · log · slog Packages — Everything About Output and Logging
Three of the most frequently used packages in Go's standard library are fmt, log, and slog. fmt handles formatted I/O, log provides simple logging, and slog (Go 1.21+) offers structured, high-performance logging.
fmt Package — Formatted I/O
Output Functions
The fmt package supports three output destinations.
package main
import (
"fmt"
"os"
)
func main() {
// Print family — writes to standard output (stdout)
fmt.Print("no newline") // no newline
fmt.Println("with newline") // automatic newline
fmt.Printf("name: %s, age: %d\n", "Alice", 30) // formatted
// Sprint family — returns a string
s1 := fmt.Sprint("Hello", " ", "World") // "Hello World"
s2 := fmt.Sprintln("Hello", "World") // "Hello World\n"
s3 := fmt.Sprintf("Pi = %.4f", 3.14159265) // "Pi = 3.1416"
fmt.Println(s1, s2, s3)
// Fprint family — writes to io.Writer
fmt.Fprint(os.Stderr, "error message\n")
fmt.Fprintln(os.Stdout, "standard output")
fmt.Fprintf(os.Stderr, "error code: %d\n", 404)
}
Format Verbs — Complete Reference
Go's format verbs specify output format using a character after %.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
p := Person{"Alice", 30}
num := 255
pi := 3.14159265358979
// General verbs
fmt.Printf("%v\n", p) // {Alice 30} — default format
fmt.Printf("%+v\n", p) // {Name:Alice Age:30} — with field names
fmt.Printf("%#v\n", p) // main.Person{Name:"Alice", Age:30} — Go syntax
fmt.Printf("%T\n", p) // main.Person — type name
// Integer verbs
fmt.Printf("%d\n", num) // 255 — decimal
fmt.Printf("%b\n", num) // 11111111 — binary
fmt.Printf("%o\n", num) // 377 — octal
fmt.Printf("%x\n", num) // ff — hex lowercase
fmt.Printf("%X\n", num) // FF — hex uppercase
fmt.Printf("%08d\n", num) // 00000255 — width 8, zero-padded
fmt.Printf("%-8d|\n", num) // 255 | — left-aligned
// Float verbs
fmt.Printf("%f\n", pi) // 3.141593 — default decimal
fmt.Printf("%.2f\n", pi) // 3.14 — 2 decimal places
fmt.Printf("%e\n", pi) // 3.141593e+00 — scientific notation
fmt.Printf("%g\n", pi) // 3.14159265358979 — shorter representation
// String verbs
fmt.Printf("%s\n", "Hello") // Hello — plain string
fmt.Printf("%q\n", "Hello") // "Hello" — quoted string
fmt.Printf("%10s\n", "Go") // Go — width 10, right-aligned
fmt.Printf("%-10s|\n", "Go") // Go | — left-aligned
// Pointer verb
x := 42
fmt.Printf("%p\n", &x) // 0xc0000b4008 — pointer address
// Boolean verb
fmt.Printf("%t\n", true) // true
}
Reading Standard Input — Scan Functions
package main
import "fmt"
func main() {
var name string
var age int
// Scan — reads whitespace-separated input
fmt.Print("Enter name and age: ")
n, err := fmt.Scan(&name, &age)
if err != nil {
fmt.Println("input error:", err)
return
}
fmt.Printf("items read: %d, name: %s, age: %d\n", n, name, age)
// Scanf — reads formatted input
var x, y float64
fmt.Print("Enter coordinates (x,y): ")
fmt.Scanf("%f,%f", &x, &y)
fmt.Printf("X: %.2f, Y: %.2f\n", x, y)
// Scanln — reads a single line
var line string
fmt.Print("Enter a line: ")
fmt.Scanln(&line)
fmt.Println("line entered:", line)
}
fmt.Errorf for Error Wrapping
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("item not found")
func findUser(id int) error {
if id <= 0 {
// wrap error with %w verb (Go 1.13+)
return fmt.Errorf("findUser: invalid ID %d: %w", id, ErrNotFound)
}
return nil
}
func main() {
err := findUser(-1)
if err != nil {
fmt.Println(err) // findUser: invalid ID -1: item not found
// check wrapped error with errors.Is
if errors.Is(err, ErrNotFound) {
fmt.Println("this is a NotFound error")
}
}
}
log Package — Basic Logging
The log package prints messages with timestamps. It has built-in support for program exit and panic, making it useful for simple error handling.
package main
import (
"log"
"os"
)
func main() {
// Basic log functions — timestamp is automatically included
log.Print("basic log message")
log.Printf("user %s logged in (ID: %d)", "Alice", 42)
log.Println("log with newline")
// Fatal family — logs then calls os.Exit(1)
// log.Fatal("fatal error — program exits")
// log.Fatalf("DB connection failed: %v", err)
// Panic family — logs then calls panic()
// log.Panic("panic triggered")
// Set flags — control log output format
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// output example: 2024/01/15 10:30:00 main.go:25: message
log.Println("log after flag change")
// Flag constants:
// log.Ldate — date: 2009/01/23
// log.Ltime — time: 01:23:23
// log.Lmicroseconds — microseconds
// log.Llongfile — full file path
// log.Lshortfile — file name only
// log.LUTC — use UTC time
// log.Lmsgprefix — place prefix before message
// Set prefix
log.SetPrefix("[APP] ")
log.Println("log with prefix") // [APP] 2024/01/15 ...
// Change output destination
log.SetOutput(os.Stderr) // default is also stderr
}
log.New — Custom Logger
package main
import (
"log"
"os"
)
func main() {
// Writing logs to a file
logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal("failed to open log file:", err)
}
defer logFile.Close()
// Custom loggers — specify writer, prefix, and flags
infoLogger := log.New(os.Stdout, "[INFO] ", log.Ldate|log.Ltime)
errorLogger := log.New(logFile, "[ERROR] ", log.Ldate|log.Ltime|log.Lshortfile)
warnLogger := log.New(os.Stderr, "[WARN] ", log.Ldate|log.Ltime)
infoLogger.Println("server started")
warnLogger.Printf("memory usage: %d%%", 85)
errorLogger.Println("database connection error")
}
slog Package — Structured Logging (Go 1.21+)
slog was added to the standard library in Go 1.21. It outputs key-value pairs in JSON or text format, making it easy to integrate with log analysis tools.
Basic Usage
package main
import (
"log/slog"
"os"
)
func main() {
// Default slog — text format written to stderr
slog.Info("server started", "port", 8080, "env", "production")
slog.Debug("debug message", "detail", "verbose info") // default level is Info, not printed
slog.Warn("warning message", "threshold", 90)
slog.Error("error occurred", "code", 500, "msg", "Internal Server Error")
// JSON handler — outputs logs as JSON
jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // output all levels from Debug up
})
jsonLogger := slog.New(jsonHandler)
jsonLogger.Info("JSON log", "user", "Alice", "action", "login")
// {"time":"2024-01-15T10:30:00Z","level":"INFO","msg":"JSON log","user":"Alice","action":"login"}
// Text handler — human-readable format
textHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
// AddSource: true, // include source file location
})
textLogger := slog.New(textHandler)
textLogger.Info("text log", "user", "Bob", "score", 95.5)
// time=2024-01-15T10:30:00.000Z level=INFO msg=text log user=Bob score=95.5
// Replace default logger
slog.SetDefault(jsonLogger)
slog.Info("now outputs as JSON")
}
slog.With — Adding Common Attributes
package main
import (
"log/slog"
"os"
)
func main() {
baseLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// Create child logger with common attributes using With
requestLogger := baseLogger.With(
"request_id", "req-12345",
"user_id", 42,
"path", "/api/users",
)
// All logs automatically include request_id, user_id, path
requestLogger.Info("request processing started")
requestLogger.Info("executing DB query", "query", "SELECT * FROM users")
requestLogger.Info("request processing completed", "duration_ms", 123)
// slog.Attr — type-safe attributes
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
logger.Info("type-safe attributes",
slog.String("name", "Alice"),
slog.Int("age", 30),
slog.Bool("active", true),
slog.Float64("score", 98.5),
slog.Group("address",
slog.String("city", "Seoul"),
slog.String("country", "Korea"),
),
)
}
Custom Handler Implementation
package main
import (
"context"
"fmt"
"log/slog"
"os"
)
// ColorHandler — custom handler for color-coded output by level
type ColorHandler struct {
opts slog.HandlerOptions
inner slog.Handler
}
func NewColorHandler(w *os.File, opts *slog.HandlerOptions) *ColorHandler {
return &ColorHandler{
inner: slog.NewTextHandler(w, opts),
}
}
// Enabled — whether this handler processes the given level
func (h *ColorHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.inner.Enabled(ctx, level)
}
// Handle — process the actual log record
func (h *ColorHandler) Handle(ctx context.Context, r slog.Record) error {
// ANSI color codes
colors := map[slog.Level]string{
slog.LevelDebug: "\033[36m", // cyan
slog.LevelInfo: "\033[32m", // green
slog.LevelWarn: "\033[33m", // yellow
slog.LevelError: "\033[31m", // red
}
reset := "\033[0m"
color := colors[r.Level]
fmt.Printf("%s[%s]%s %s\n", color, r.Level, reset, r.Message)
return nil
}
// WithAttrs — return handler with added attributes
func (h *ColorHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &ColorHandler{inner: h.inner.WithAttrs(attrs)}
}
// WithGroup — return handler with added group
func (h *ColorHandler) WithGroup(name string) slog.Handler {
return &ColorHandler{inner: h.inner.WithGroup(name)}
}
func main() {
handler := NewColorHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
logger := slog.New(handler)
logger.Debug("debug message")
logger.Info("info message")
logger.Warn("warning message")
logger.Error("error message")
}
Real-World Example — Multi-Handler Logger with Request ID Structured Logging
package main
import (
"context"
"io"
"log/slog"
"os"
)
// MultiHandler — writes logs to multiple handlers simultaneously
type MultiHandler struct {
handlers []slog.Handler
}
func (m *MultiHandler) Enabled(ctx context.Context, level slog.Level) bool {
for _, h := range m.handlers {
if h.Enabled(ctx, level) {
return true
}
}
return false
}
func (m *MultiHandler) Handle(ctx context.Context, r slog.Record) error {
for _, h := range m.handlers {
if h.Enabled(ctx, r.Level) {
if err := h.Handle(ctx, r.Clone()); err != nil {
return err
}
}
}
return nil
}
func (m *MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
handlers := make([]slog.Handler, len(m.handlers))
for i, h := range m.handlers {
handlers[i] = h.WithAttrs(attrs)
}
return &MultiHandler{handlers: handlers}
}
func (m *MultiHandler) WithGroup(name string) slog.Handler {
handlers := make([]slog.Handler, len(m.handlers))
for i, h := range m.handlers {
handlers[i] = h.WithGroup(name)
}
return &MultiHandler{handlers: handlers}
}
// context key type
type contextKey string
const requestIDKey contextKey = "request_id"
// WithRequestID — stores request ID in context
func WithRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
// LoggerFromContext — extracts request ID from context and adds to logger
func LoggerFromContext(ctx context.Context, base *slog.Logger) *slog.Logger {
if id, ok := ctx.Value(requestIDKey).(string); ok {
return base.With("request_id", id)
}
return base
}
func main() {
// create log file
logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
defer logFile.Close()
// multi-handler setup — text to stdout and JSON to file simultaneously
multiHandler := &MultiHandler{
handlers: []slog.Handler{
// for developers: text format on terminal
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}),
// for production: JSON format to file
slog.NewJSONHandler(io.MultiWriter(logFile, os.Stdout), &slog.HandlerOptions{
Level: slog.LevelWarn, // only warnings and above to file
}),
},
}
logger := slog.New(multiHandler)
slog.SetDefault(logger)
// simulate request processing
ctx := WithRequestID(context.Background(), "req-abc-123")
reqLogger := LoggerFromContext(ctx, logger)
reqLogger.Info("HTTP request received", "method", "POST", "path", "/api/orders")
reqLogger.Debug("parsing request body", "size_bytes", 1024)
reqLogger.Info("starting DB transaction")
reqLogger.Warn("slow query detected", "duration_ms", 2500, "threshold_ms", 1000)
reqLogger.Info("HTTP response sent", "status", 201, "duration_ms", 2650)
}
Package Selection Guide
| Situation | Recommended Package |
|---|---|
| Simple debugging, scripts | fmt.Println |
| Small apps, quick prototypes | log |
| Production servers, microservices | slog (JSON handler) |
| Log analysis tool integration | slog (JSON handler) |
| Test output | fmt + testing.T.Log |
slog requires Go 1.21 or later. If you need to support older versions, use third-party libraries like zerolog or zap, or use the log package.