Time based log file rotation with zap

zap is one of the nicely maintained, well-written and nicely performing open source logging libraries in Go. Unfortunately, not all apps in our organization are 12-factor apps and thus we still log to physical files. So, I needed a way to rotate log files while using zap. I come from Java background, and log4j has a very vast array of log rotation options. But I found it lacking in zap, clearly because in the world of containers, logging to stdout (treating logs as event streams – 12-factor logs) is very common.

To be specific, I was looking to do time based rotation (one log file per hour, helps in debugging) and not size based. On zap's FAQ, they've shown integration with lumberjack, which only supports size based rotation.

I found another library file-rotatelogs which does support time based rotation. So I used it along with zap.

The good thing about zap is that it takes any io.Writer interface as a WriteSyncer and file-rotatelogs returns *RotateLogs which implements the io.Writer interface.

Here the rotatelogs.WithRotationTime(time.Hour)) indicates hourly rotation. rotatelogs.WithMaxAge(60*24*time.Hour) indicates the files are cleaned up after 60 days.

package main

import (
	"encoding/json"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	// initialize the rotator
	logFile := "/var/log/app-%Y-%m-%d-%H.log"
	rotator, err := rotatelogs.New(
		logFile,
		rotatelogs.WithMaxAge(60*24*time.Hour),
		rotatelogs.WithRotationTime(time.Hour))
	if err != nil {
		panic(err)
	}

	// initialize the JSON encoding config
	encoderConfig := map[string]string{
		"levelEncoder": "capital",
		"timeKey":      "date",
		"timeEncoder":  "iso8601",
	}
	data, _ := json.Marshal(encoderConfig)
	var encCfg zapcore.EncoderConfig
	if err := json.Unmarshal(data, &encCfg); err != nil {
		panic(err)
	}

	// add the encoder config and rotator to create a new zap logger
	w := zapcore.AddSync(rotator)
	core := zapcore.NewCore(
		zapcore.NewJSONEncoder(encCfg),
		w,
		zap.InfoLevel)
	logger := zap.New(core)

	logger.Info("Now logging in a rotated file")
}

This will create the file /var/log/app-2020-05-18-15.log, which is exactly how we wanted it. It will automatically start logging to the next file during the start of the next hour.

#go #golang #logging