Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
159: Configuring Log Output Destinations
Imagine you're setting up a security camera system for a warehouse. By default, the cameras are wired to a monitor in the security office. If something happens, the guard sees it in real-time. That's great for immediate awareness, but it's useless for evidence because the feed isn't being saved. To fix this, you don't replace the cameras; you just change where the cable goes. You plug the feed into a DVR (Digital Video Recorder) to save it to a hard drive, or maybe you split the signal so it goes to both the monitor and the DVR.
In Go, the log package works exactly the same way. By default, every log.Println call is "wired" to os.Stderr (the standard error stream). It's like that monitor in the security office—it's visible in your terminal, but it vanishes the moment you close the session. To keep a permanent record, we need to redirect that output stream to something that persists, like a file.
Redirecting the Global Logger
The simplest way to change where your logs go is using log.SetOutput. This function takes any type that implements the io.Writer interface. Since a file is an io.Writer, we can open a file and tell the logger to send everything there.
package main
import (
"log"
"os"
)
func main() {
// Open a file for writing.
// O_APPEND adds to the end, O_CREATE makes it if it doesn't exist.
file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// This is where we "switch the cable"
log.SetOutput(file)
log.Println("This message will go straight to app.log, not the console!")
}
I'll be honest: using the global logger like this is fine for tiny scripts, but in a professional codebase, it's a bit of a blunt instrument. If you have different parts of your app—say, a database handler and an HTTP server—you probably don't want them all shouting into the same file without a way to distinguish them.
Creating Dedicated Loggers
Instead of hijacking the global logger, I prefer creating specific logger instances using log.New. This allows you to define exactly where the output goes, what the prefix is (like "[DATABASE]" or "[API]"), and which flags (like timestamps) are included.
package main
import (
"log"
"os"
)
func main() {
file, _ := os.OpenFile("server.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// Create a custom logger: output destination, prefix, and flags
serverLog := log.New(file, "SERVER: ", log.Ldate|log.Ltime|log.Lshortfile)
serverLog.Println("Server started on port 8080")
}
By doing this, you can have one logger writing to a file for long-term auditing and another writing to os.Stdout for real-time debugging, all within the same program.
The Best of Both Worlds with MultiWriter
There are times when you want the "monitor" and the "DVR" simultaneously. You want to see the logs in your terminal while you're developing, but you also want them saved to a file so you can analyze them later if the app crashes at 3 AM.
Go provides a handy utility in the io package called MultiWriter. It essentially acts as a "Y-splitter" for your data stream. Anything written to the MultiWriter is duplicated to every destination you provide.
package main
import (
"io"
"log"
"os"
)
func main() {
file, _ := os.OpenFile("combined.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// Split the output between the file and the standard output (console)
multi := io.MultiWriter(os.Stdout, file)
log.SetOutput(multi)
log.Println("I am appearing in both the terminal and the file!")
}
One quick tip: always remember to handle your file closing. Using defer file.Close() is the standard way to ensure you don't leave file handles hanging open, which could lead to memory leaks or locked files in some operating systems.
📋 Practical Task
Build a Dual-Stream Audit Logger for User Registrations
You are building a user registration system. For security reasons, you need to maintain a permanent audit trail of every registration in a file called audit.log, but for operational monitoring, you also need to see these events in the console in real-time.
Requirements:
- Create a program that simulates a user registration process.
- Configure the
logpackage to use anio.MultiWriterthat sends output to bothos.Stdoutand a file namedaudit.log. - The log output should include a custom prefix
"[AUDIT] ". - Log a message whenever a fake user "registers" (e.g.,
log.Printf("User %s registered successfully", username)). - Ensure the file is opened with the correct flags to append data rather than overwriting it.
There are no comments for now.