Skip to main content
Go provides powerful logging capabilities through two standard library packages: log for free-form output and log/slog for structured logging.

log package

Simple, free-form logging for basic use cases

log/slog package

Structured logging with JSON output and key-value pairs

Basic Logging with log

The simplest way to log is using the standard logger:
import "log"

log.Println("standard logger")
The standard logger is pre-configured to output to os.Stderr with reasonable defaults.
Output format: By default, the standard logger includes date and time prefixes.Example: 2023/08/22 10:45:16 standard logger

Fatal and Panic Logging

The log package provides special methods that log and then terminate:

Fatal*

Logs message then calls os.Exit(1)
log.Fatal("critical error")

Panic*

Logs message then calls panic()
log.Panic("unexpected state")

Configuring Log Output

Output Flags

Customize what information appears in your logs:
// Add microsecond precision to timestamps
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.Println("with micro")
// Output: 2023/08/22 10:45:16.904141 with micro

// Include file name and line number
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("with file/line")
// Output: 2023/08/22 10:45:16 logging.go:40: with file/line

Available Flags

FlagDescriptionExample Output
log.LdateDate (YYYY/MM/DD)2023/08/22
log.LtimeTime (HH:MM:SS)10:45:16
log.LmicrosecondsMicrosecond precision10:45:16.904141
log.LlongfileFull file path and line/path/to/logging.go:40:
log.LshortfileFile name and linelogging.go:40:
log.LUTCUse UTC instead of local time
log.LmsgprefixMove prefix to before message
log.LstdFlagsLdate | Ltime (default)2023/08/22 10:45:16
Combine flags using the bitwise OR operator (|).

Custom Loggers

Create loggers with custom prefixes and output destinations:
import (
    "log"
    "os"
)

// Create a logger with a prefix
mylog := log.New(os.Stdout, "my:", log.LstdFlags)
mylog.Println("from mylog")
// Output: my:2023/08/22 10:45:16 from mylog

// Change the prefix later
mylog.SetPrefix("ohmy:")
mylog.Println("from mylog")
// Output: ohmy:2023/08/22 10:45:16 from mylog

Logging to Different Destinations

You can send logs to any io.Writer:
import (
    "bytes"
    "fmt"
    "log"
)

// Log to a buffer
var buf bytes.Buffer
buflog := log.New(&buf, "buf:", log.LstdFlags)

buflog.Println("hello")

// Retrieve the logged content
fmt.Print("from buflog:", buf.String())
// Output: from buflog:buf:2023/08/22 10:45:16 hello
Common io.Writer destinations include:
  • os.Stdout - Standard output
  • os.Stderr - Standard error (default)
  • &bytes.Buffer - In-memory buffer
  • File handles from os.OpenFile
  • io.MultiWriter - Multiple destinations

Structured Logging with slog

For production applications, structured logging provides better queryability and parsing:
import (
    "log/slog"
    "os"
)

// Create a JSON logger
jsonHandler := slog.NewJSONHandler(os.Stderr, nil)
myslog := slog.New(jsonHandler)

// Simple log message
myslog.Info("hi there")
// Output: {"time":"2023-08-22T10:45:16.904166391-07:00","level":"INFO","msg":"hi there"}

Adding Structured Data

Include key-value pairs in your logs:
myslog.Info("hello again", "key", "val", "age", 25)
// Output: 
// {
//   "time":"2023-08-22T10:45:16.904178985-07:00",
//   "level":"INFO",
//   "msg":"hello again",
//   "key":"val",
//   "age":25
// }
1

Create a handler

Choose between JSON or text format:
// JSON format
jsonHandler := slog.NewJSONHandler(os.Stderr, nil)

// Text format
textHandler := slog.NewTextHandler(os.Stderr, nil)
2

Create a logger

Wrap the handler in a logger:
logger := slog.New(jsonHandler)
3

Log with context

Add key-value pairs for structured data:
logger.Info("message", "key1", "value1", "key2", "value2")

Log Levels with slog

slog supports different severity levels:

Debug

Detailed information for debugging
logger.Debug("variable state", "x", x)

Info

General informational messages
logger.Info("server started", "port", 8080)

Warn

Warning messages for potential issues
logger.Warn("high memory usage", "percent", 85)

Error

Error conditions that need attention
logger.Error("failed to connect", "err", err)

Example Output

Running the complete logging example:
$ go run logging.go
2023/08/22 10:45:16 standard logger
2023/08/22 10:45:16.904141 with micro
2023/08/22 10:45:16 logging.go:40: with file/line
my:2023/08/22 10:45:16 from mylog
ohmy:2023/08/22 10:45:16 from mylog
from buflog:buf:2023/08/22 10:45:16 hello
{"time":"2023-08-22T10:45:16.904166391-07:00","level":"INFO","msg":"hi there"}
{"time":"2023-08-22T10:45:16.904178985-07:00","level":"INFO","msg":"hello again","key":"val","age":25}
JSON output is shown wrapped for clarity. In reality, each JSON entry is emitted on a single line.

Best Practices

Structured logs (JSON) are easier to parse, query, and analyze:
// Production
logger := slog.New(slog.NewJSONHandler(os.Stderr, nil))
logger.Info("request processed", "duration_ms", 42, "status", 200)

// Development (more readable)
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
Add relevant information to help debugging:
logger.Error("database error",
    "operation", "insert",
    "table", "users",
    "error", err,
    "user_id", userID,
)
Different levels help filter and prioritize:
  • Debug: Verbose information for development
  • Info: Normal operational messages
  • Warn: Potentially harmful situations
  • Error: Error events that might still allow the app to continue
Avoid logging passwords, tokens, or personal information:
// Bad
logger.Info("login attempt", "password", password)

// Good
logger.Info("login attempt", "username", username)
Use separate loggers with prefixes for different parts of your app:
dbLogger := log.New(os.Stdout, "[DB] ", log.LstdFlags)
apiLogger := log.New(os.Stdout, "[API] ", log.LstdFlags)

dbLogger.Println("connection opened")
apiLogger.Println("request received")

Advanced: Handler Options

Customize slog handlers with options:
opts := &slog.HandlerOptions{
    Level: slog.LevelDebug,  // Set minimum level
    AddSource: true,          // Include source file info
}

handler := slog.NewJSONHandler(os.Stderr, opts)
logger := slog.New(handler)

logger.Debug("debug message")  // Will be logged

Logging to Files

Redirect logs to a file:
file, err := os.OpenFile("app.log", 
    os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
    log.Fatal(err)
}
defer file.Close()

log.SetOutput(file)
log.Println("This goes to the file")
For production applications, consider using log rotation libraries like lumberjack to manage log file sizes.

Exit

Exit programs with proper status codes

Signals

Log signal events and shutdowns

Build docs developers (and LLMs) love