feat: add log rotate (#1200)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00d3b8f..9e9fd5e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -363,6 +363,7 @@
 * chen zhuo
 
 ### Changes
+
 <details><summary>24 commits</summary>
 <p>
 
@@ -390,6 +391,7 @@
 * [`4a2ebaf`](https://github.com/apache/apisix-ingress-controller/commit/4a2ebaf43ee4cf42212659b412e7ed8a8ab8ee21) chore: fix typo in ApidixRoute CRD (#830)
 * [`46fcf3f`](https://github.com/apache/apisix-ingress-controller/commit/46fcf3f153a00cef868454b06452065172500dd1) fix: consumer name contain "-" (#828)
 * [`b7dd90a`](https://github.com/apache/apisix-ingress-controller/commit/b7dd90ad9d6a5102fe67c91c29ab76fc4bad4b1a) chore: v1.4 release
+
 </p>
 </details>
 
diff --git a/cmd/ingress/ingress.go b/cmd/ingress/ingress.go
index cf73256..6450449 100644
--- a/cmd/ingress/ingress.go
+++ b/cmd/ingress/ingress.go
@@ -25,6 +25,8 @@
 	"time"
 
 	"github.com/spf13/cobra"
+	"go.uber.org/zap/zapcore"
+	"gopkg.in/natefinch/lumberjack.v2"
 
 	"github.com/apache/apisix-ingress-controller/pkg/config"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
@@ -102,10 +104,23 @@
 				dief("bad configuration: %s", err)
 			}
 
-			logger, err := log.NewLogger(
-				log.WithLogLevel(cfg.LogLevel),
-				log.WithOutputFile(cfg.LogOutput),
-			)
+			var ws zapcore.WriteSyncer
+
+			options := []log.Option{log.WithLogLevel(cfg.LogLevel), log.WithOutputFile(cfg.LogOutput)}
+
+			if cfg.LogRotateOutputPath != "" {
+				ws = zapcore.AddSync(&lumberjack.Logger{
+					Filename:   cfg.LogRotateOutputPath,
+					MaxSize:    cfg.LogRotationMaxSize,
+					MaxBackups: cfg.LogRotationMaxBackups,
+					MaxAge:     cfg.LogRotationMaxAge,
+				})
+
+				options = append(options, log.WithWriteSyncer(ws))
+			}
+
+			logger, err := log.NewLogger(options...)
+
 			if err != nil {
 				dief("failed to initialize logging: %s", err)
 			}
@@ -143,6 +158,10 @@
 	cmd.PersistentFlags().StringVar(&configPath, "config-path", "", "configuration file path for apisix-ingress-controller")
 	cmd.PersistentFlags().StringVar(&cfg.LogLevel, "log-level", "info", "error log level")
 	cmd.PersistentFlags().StringVar(&cfg.LogOutput, "log-output", "stderr", "error log output file")
+	cmd.PersistentFlags().StringVar(&cfg.LogRotateOutputPath, "log-rotate-output-path", "", "rotate log output path")
+	cmd.PersistentFlags().IntVar(&cfg.LogRotationMaxSize, "log-rotate-max-size", 100, "rotate log max size")
+	cmd.PersistentFlags().IntVar(&cfg.LogRotationMaxAge, "log-rotate-max-age", 0, "old rotate log max age to retain")
+	cmd.PersistentFlags().IntVar(&cfg.LogRotationMaxBackups, "log-rotate-max-backups", 0, "old rotate log max numbers to retain")
 	cmd.PersistentFlags().StringVar(&cfg.HTTPListen, "http-listen", ":8080", "the HTTP Server listen address")
 	cmd.PersistentFlags().StringVar(&cfg.HTTPSListen, "https-listen", ":8443", "the HTTPS Server listen address")
 	cmd.PersistentFlags().StringVar(&cfg.IngressPublishService, "ingress-publish-service", "",
diff --git a/cmd/ingress/ingress_test.go b/cmd/ingress/ingress_test.go
index 35168ef..a32a00b 100644
--- a/cmd/ingress/ingress_test.go
+++ b/cmd/ingress/ingress_test.go
@@ -19,6 +19,7 @@
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"io/ioutil"
 	"math/rand"
 	"os"
 	"strings"
@@ -162,3 +163,48 @@
 	assert.Nil(t, err)
 	return &f
 }
+
+func TestRotateLog(t *testing.T) {
+	listen := getRandomListen()
+	cmd := NewIngressCommand()
+	cmd.SetArgs([]string{
+		"--log-rotate-output-path", "./testlog/test.log",
+		"--log-rotate-max-size", "1",
+		"--http-listen", listen,
+		"--enable-profiling",
+		"--kubeconfig", "/foo/bar/baz",
+		"--resync-interval", "24h",
+		"--default-apisix-cluster-base-url", "http://apisixgw.default.cluster.local/apisix",
+		"--default-apisix-cluster-admin-key", "0x123",
+	})
+	defer os.RemoveAll("./testlog/")
+
+	stopCh := make(chan struct{})
+	go func() {
+		assert.Nil(t, cmd.Execute())
+		close(stopCh)
+	}()
+
+	// fill logs with data until the size > 1m
+	line := ""
+	for i := 0; i < 256; i++ {
+		line += "0"
+	}
+
+	for i := 0; i < 4096; i++ {
+		log.Info(line)
+	}
+
+	time.Sleep(5 * time.Second)
+	assert.Nil(t, syscall.Kill(os.Getpid(), syscall.SIGINT))
+	<-stopCh
+
+	files, err := ioutil.ReadDir("./testlog")
+
+	if err != nil {
+		t.Fatalf("Unable to read log dir: %v", err)
+	}
+
+	assert.Equal(t, true, len(files) >= 2)
+
+}
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index a91aa3e..3fe9cf1 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -27,6 +27,11 @@
                      # are marshalled in JSON format, which can be parsed by
                      # programs easily.
 
+log_rotate_output_path: ""  # rotate output path, the logs will be written in this file
+log_rotation_max_size: 100  # rotate max size, max size in megabytes of log file before it get rotated. It defaults to 100
+log_rotation_max_age: 0     # rotate max age, max age of old log files to retain
+log_rotation_max_backups: 0 # rotate max backups, max numbers of old log files to retain
+
 cert_file: "/etc/webhook/certs/cert.pem" # the TLS certificate file path.
 key_file: "/etc/webhook/certs/key.pem"   # the TLS key file path.
 
diff --git a/go.mod b/go.mod
index 1a486d1..83720cb 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@
 	go.uber.org/multierr v1.8.0
 	go.uber.org/zap v1.21.0
 	golang.org/x/net v0.0.0-20220725212005-46097bf591d3
+	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v2 v2.4.0
 	k8s.io/api v0.24.3
 	k8s.io/apimachinery v0.24.3
diff --git a/go.sum b/go.sum
index 5d6e370..a29610c 100644
--- a/go.sum
+++ b/go.sum
@@ -48,6 +48,7 @@
 github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
@@ -999,6 +1000,7 @@
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 4e061b6..a3997e0 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -79,6 +79,10 @@
 	KeyFilePath                string             `json:"key_file" yaml:"key_file"`
 	LogLevel                   string             `json:"log_level" yaml:"log_level"`
 	LogOutput                  string             `json:"log_output" yaml:"log_output"`
+	LogRotateOutputPath        string             `json:"log_rotate_output_path" yaml:"log_rotate_output_path"`
+	LogRotationMaxSize         int                `json:"log_rotation_max_size" yaml:"log_rotation_max_size"`
+	LogRotationMaxAge          int                `json:"log_rotation_max_age" yaml:"log_rotation_max_age"`
+	LogRotationMaxBackups      int                `json:"log_rotation_max_backups" yaml:"log_rotation_max_backups"`
 	HTTPListen                 string             `json:"http_listen" yaml:"http_listen"`
 	HTTPSListen                string             `json:"https_listen" yaml:"https_listen"`
 	IngressPublishService      string             `json:"ingress_publish_service" yaml:"ingress_publish_service"`
@@ -120,6 +124,10 @@
 	return &Config{
 		LogLevel:                   "warn",
 		LogOutput:                  "stderr",
+		LogRotateOutputPath:        "",
+		LogRotationMaxSize:         100,
+		LogRotationMaxAge:          0,
+		LogRotationMaxBackups:      0,
 		HTTPListen:                 ":8080",
 		HTTPSListen:                ":8443",
 		IngressPublishService:      "",
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index c448d44..5586114 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -30,6 +30,10 @@
 	cfg := &Config{
 		LogLevel:                   "warn",
 		LogOutput:                  "stdout",
+		LogRotateOutputPath:        "",
+		LogRotationMaxSize:         100,
+		LogRotationMaxAge:          0,
+		LogRotationMaxBackups:      0,
 		HTTPListen:                 ":9090",
 		HTTPSListen:                ":9443",
 		IngressPublishService:      "",
@@ -114,6 +118,10 @@
 	cfg := &Config{
 		LogLevel:                   "warn",
 		LogOutput:                  "stdout",
+		LogRotateOutputPath:        "",
+		LogRotationMaxSize:         100,
+		LogRotationMaxAge:          0,
+		LogRotationMaxBackups:      0,
 		HTTPListen:                 ":9090",
 		HTTPSListen:                ":9443",
 		IngressPublishService:      "",