

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Difficulty: 3
click for more info
Not enough gems
Cost: 6 gems
1: Log Storage
incomplete
2: Logging to the Console
incomplete
3: Filesystem Logging
incomplete
4: Log Rotation
incomplete
5: Syslog
incomplete
This lesson's interactive features are locked, please to keep using them
Logging directly to the filesystem is less common these days, but it still comes up, and it's worth understanding the fundamentals. We already set up file logging, so let's review the design decisions.
Linko logs to a file when the LINKO_LOG_FILE environment variable is set. This involves three parts:
Another approach is a single logger writing to two outputs: console and file. But then both destinations must receive identical output, and you still have to flush the file correctly. We want flexibility: color in the console, no color in the file, and often JSON in the file but text in the console.
Our initializeLogger function optionally sends logs to a file when LINKO_LOG_FILE is set:
type closeFunc func() error
func initializeLogger(logFile string) (*slog.Logger, closeFunc, error) {
var (
handlers []slog.Handler
closers []closeFunc
)
replaceAttr := func(groups []string, a slog.Attr) slog.Attr { /* ... */ }
// First initialize the console logger
handlers = append(handlers, tint.NewTextHandler(os.Stderr, &tint.Options{
ReplaceAttr: replaceAttr,
NoColor: !(isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())),
}))
if logFile != "" {
file, err := os.OpenFile(logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0x666)
if err != nil {
return nil, nil, fmt.Errorf("failed to open log file: %w", err)
}
bufferedFile := bufio.NewWriter(file)
handlers = append(handlers, slog.NewJSONHandler(bufferedFile, &slog.HandlerOptions{
ReplaceAttr: replaceAttr,
}))
closers = append(closers, func() error {
if err := bufferedFile.Flush(); err != nil {
return fmt.Errorf("failed to flush log file: %w", err)
}
if err := file.Close(); err != nil {
return fmt.Errorf("failed to close log file: %w", err)
}
return nil
})
}
close := func() error {
var errs []error
for _, closer := range closers {
errs = append(errs, closer())
}
return errors.Join(errs...)
}
return slog.New(slog.NewMultiHandler(handlers...)), close, nil
}
This structure makes future logger targets easy to add: append handlers and closers.