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
Flag Description Example Output log.LdateDate (YYYY/MM/DD) 2023/08/22log.LtimeTime (HH:MM:SS) 10:45:16log.LmicrosecondsMicrosecond precision 10:45:16.904141log.LlongfileFull file path and line /path/to/logging.go:40:log.LshortfileFile name and line logging.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
// }
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 )
Create a logger
Wrap the handler in a logger: logger := slog . New ( jsonHandler )
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
Use structured logging for production
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 ))
Include context in log messages
Add relevant information to help debugging: logger . Error ( "database error" ,
"operation" , "insert" ,
"table" , "users" ,
"error" , err ,
"user_id" , userID ,
)
Use appropriate log levels
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 )
Create logger instances for components
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