summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValery Piashchynski <[email protected]>2021-06-09 13:32:34 +0300
committerValery Piashchynski <[email protected]>2021-06-09 13:32:34 +0300
commit3ec3306730455b6c5c423d130ebc322757661487 (patch)
tree1acfe544effb563a06e133a13be1faac6b3fddbb
parenta723cedba199a1c50dca05630b53139ee456ace8 (diff)
- Add support for the file logger with log rotation and compression
Signed-off-by: Valery Piashchynski <[email protected]>
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--plugins/logger/config.go92
-rw-r--r--plugins/logger/enums.go12
-rw-r--r--plugins/logger/zap_adapter.go2
-rw-r--r--tests/plugins/http/configs/.rr-init.yaml6
-rw-r--r--tests/plugins/logger/configs/.rr-file-logger.yaml23
-rw-r--r--tests/plugins/logger/logger_test.go97
8 files changed, 218 insertions, 17 deletions
diff --git a/go.mod b/go.mod
index 026a7d7d..4142d9b3 100644
--- a/go.mod
+++ b/go.mod
@@ -36,4 +36,5 @@ require (
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015
google.golang.org/protobuf v1.23.0
+ gopkg.in/natefinch/lumberjack.v2 v2.0.0
)
diff --git a/go.sum b/go.sum
index 3b4461a2..3edc27a7 100644
--- a/go.sum
+++ b/go.sum
@@ -671,6 +671,8 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
diff --git a/plugins/logger/config.go b/plugins/logger/config.go
index d2236cac..6ef56661 100644
--- a/plugins/logger/config.go
+++ b/plugins/logger/config.go
@@ -1,10 +1,12 @@
package logger
import (
+ "os"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
+ "gopkg.in/natefinch/lumberjack.v2"
)
// ChannelConfig configures loggers per channel.
@@ -13,9 +15,57 @@ type ChannelConfig struct {
Channels map[string]Config `mapstructure:"channels"`
}
+// FileLoggerConfig structure represents configuration for the file logger
+type FileLoggerConfig struct {
+ // Filename is the file to write logs to. Backup log files will be retained
+ // in the same directory. It uses <processname>-lumberjack.log in
+ // os.TempDir() if empty.
+ LogOutput string `mapstructure:"log_output"`
+
+ // MaxSize is the maximum size in megabytes of the log file before it gets
+ // rotated. It defaults to 100 megabytes.
+ MaxSize int `mapstructure:"max_size"`
+
+ // MaxAge is the maximum number of days to retain old log files based on the
+ // timestamp encoded in their filename. Note that a day is defined as 24
+ // hours and may not exactly correspond to calendar days due to daylight
+ // savings, leap seconds, etc. The default is not to remove old log files
+ // based on age.
+ MaxAge int `mapstructure:"max_age"`
+
+ // MaxBackups is the maximum number of old log files to retain. The default
+ // is to retain all old log files (though MaxAge may still cause them to get
+ // deleted.)
+ MaxBackups int `mapstructure:"max_backups"`
+
+ // Compress determines if the rotated log files should be compressed
+ // using gzip. The default is not to perform compression.
+ Compress bool `mapstructure:"compress"`
+}
+
+func (fl *FileLoggerConfig) InitDefaults() *FileLoggerConfig {
+ if fl.LogOutput == "" {
+ fl.LogOutput = os.TempDir()
+ }
+
+ if fl.MaxSize == 0 {
+ fl.MaxSize = 100
+ }
+
+ if fl.MaxAge == 0 {
+ fl.MaxAge = 24
+ }
+
+ if fl.MaxBackups == 0 {
+ fl.MaxBackups = 10
+ }
+
+ return fl
+}
+
type Config struct {
// Mode configures logger based on some default template (development, production, off).
- Mode string `mapstructure:"mode"`
+ Mode Mode `mapstructure:"mode"`
// Level is the minimum enabled logging level. Note that this is a dynamic
// level, so calling ChannelConfig.Level.SetLevel will atomically change the log
@@ -38,17 +88,20 @@ type Config struct {
// sends error-level logs to a different location from info- and debug-level
// logs, see the package-level AdvancedConfiguration example.
ErrorOutput []string `mapstructure:"errorOutput"`
+
+ // File logger options
+ FileLogger *FileLoggerConfig `mapstructure:"file_logger_options"`
}
// BuildLogger converts config into Zap configuration.
func (cfg *Config) BuildLogger() (*zap.Logger, error) {
var zCfg zap.Config
- switch strings.ToLower(cfg.Mode) {
- case "off", "none":
+ switch Mode(strings.ToLower(string(cfg.Mode))) {
+ case off, none:
return zap.NewNop(), nil
- case "production":
+ case production:
zCfg = zap.NewProductionConfig()
- case "development":
+ case development:
zCfg = zap.Config{
Level: zap.NewAtomicLevelAt(zap.DebugLevel),
Development: true,
@@ -72,7 +125,7 @@ func (cfg *Config) BuildLogger() (*zap.Logger, error) {
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
- case "raw":
+ case raw:
zCfg = zap.Config{
Level: zap.NewAtomicLevelAt(zap.InfoLevel),
Encoding: "console",
@@ -120,13 +173,38 @@ func (cfg *Config) BuildLogger() (*zap.Logger, error) {
zCfg.ErrorOutputPaths = cfg.ErrorOutput
}
+ // if we also have a file logger specified in the config
+ // init it
+ // otherwise - return standard config
+ if cfg.FileLogger != nil {
+ // init absent options
+ cfg.FileLogger.InitDefaults()
+
+ w := zapcore.AddSync(
+ &lumberjack.Logger{
+ Filename: cfg.FileLogger.LogOutput,
+ MaxSize: cfg.FileLogger.MaxSize,
+ MaxAge: cfg.FileLogger.MaxAge,
+ MaxBackups: cfg.FileLogger.MaxBackups,
+ Compress: cfg.FileLogger.Compress,
+ },
+ )
+
+ core := zapcore.NewCore(
+ zapcore.NewJSONEncoder(zCfg.EncoderConfig),
+ w,
+ zCfg.Level,
+ )
+ return zap.New(core), nil
+ }
+
return zCfg.Build()
}
// InitDefault Initialize default logger
func (cfg *Config) InitDefault() {
if cfg.Mode == "" {
- cfg.Mode = "development"
+ cfg.Mode = development
}
if cfg.Level == "" {
cfg.Level = "debug"
diff --git a/plugins/logger/enums.go b/plugins/logger/enums.go
new file mode 100644
index 00000000..803eace0
--- /dev/null
+++ b/plugins/logger/enums.go
@@ -0,0 +1,12 @@
+package logger
+
+// Mode represents available logger modes
+type Mode string
+
+const (
+ none Mode = "none"
+ off Mode = "off"
+ production Mode = "production"
+ development Mode = "development"
+ raw Mode = "raw"
+)
diff --git a/plugins/logger/zap_adapter.go b/plugins/logger/zap_adapter.go
index 6d865519..fab59844 100644
--- a/plugins/logger/zap_adapter.go
+++ b/plugins/logger/zap_adapter.go
@@ -10,7 +10,7 @@ type ZapAdapter struct {
zl *zap.Logger
}
-// Create NewZapAdapter which uses general log interface
+// NewZapAdapter ... which uses general log interface
func NewZapAdapter(zapLogger *zap.Logger) *ZapAdapter {
return &ZapAdapter{
zl: zapLogger.WithOptions(zap.AddCallerSkip(1)),
diff --git a/tests/plugins/http/configs/.rr-init.yaml b/tests/plugins/http/configs/.rr-init.yaml
index 1671c3c0..02cb1636 100644
--- a/tests/plugins/http/configs/.rr-init.yaml
+++ b/tests/plugins/http/configs/.rr-init.yaml
@@ -3,17 +3,13 @@ rpc:
server:
command: "php ../../http/client.php echo pipes"
- user: ""
- group: ""
- env:
- "RR_HTTP": "true"
relay: "pipes"
relay_timeout: "20s"
http:
address: 127.0.0.1:15395
max_request_size: 1024
- middleware: [ "" ]
+ middleware: [ ]
uploads:
forbid: [ ".php", ".exe", ".bat" ]
trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ]
diff --git a/tests/plugins/logger/configs/.rr-file-logger.yaml b/tests/plugins/logger/configs/.rr-file-logger.yaml
new file mode 100644
index 00000000..49c30d02
--- /dev/null
+++ b/tests/plugins/logger/configs/.rr-file-logger.yaml
@@ -0,0 +1,23 @@
+server:
+ command: "php ../../http/client.php echo pipes"
+ relay: "pipes"
+ relay_timeout: "20s"
+
+http:
+ address: 127.0.0.1:54224
+ max_request_size: 1024
+ middleware: [ ]
+ uploads:
+ forbid: [ ".php", ".exe", ".bat" ]
+ trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ]
+ pool:
+ num_workers: 2
+ max_jobs: 0
+ allocate_timeout: 60s
+ destroy_timeout: 60s
+
+logs:
+ mode: development
+ level: debug
+ file_logger_options:
+ log_output: "test.log"
diff --git a/tests/plugins/logger/logger_test.go b/tests/plugins/logger/logger_test.go
index d2877781..9e3fa4da 100644
--- a/tests/plugins/logger/logger_test.go
+++ b/tests/plugins/logger/logger_test.go
@@ -1,16 +1,19 @@
package logger
import (
+ "net/http"
"os"
"os/signal"
+ "strings"
"sync"
"syscall"
"testing"
+ "time"
"github.com/golang/mock/gomock"
endure "github.com/spiral/endure/pkg/container"
"github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/http"
+ httpPlugin "github.com/spiral/roadrunner/v2/plugins/http"
"github.com/spiral/roadrunner/v2/plugins/logger"
"github.com/spiral/roadrunner/v2/plugins/rpc"
"github.com/spiral/roadrunner/v2/plugins/server"
@@ -19,7 +22,7 @@ import (
)
func TestLogger(t *testing.T) {
- container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(false), endure.SetLogLevel(endure.ErrorLevel))
if err != nil {
t.Fatal(err)
}
@@ -97,7 +100,7 @@ func TestLoggerRawErr(t *testing.T) {
cfg,
mockLogger,
&server.Plugin{},
- &http.Plugin{},
+ &httpPlugin.Plugin{},
)
assert.NoError(t, err)
@@ -222,7 +225,7 @@ func TestLoggerNoConfig2(t *testing.T) {
vp,
&rpc.Plugin{},
&logger.ZapLogger{},
- &http.Plugin{},
+ &httpPlugin.Plugin{},
&server.Plugin{},
)
assert.NoError(t, err)
@@ -268,3 +271,89 @@ func TestLoggerNoConfig2(t *testing.T) {
stopCh <- struct{}{}
wg.Wait()
}
+
+func TestFileLogger(t *testing.T) {
+ container, err := endure.NewContainer(nil, endure.RetryOnFail(true), endure.SetLogLevel(endure.ErrorLevel))
+ if err != nil {
+ t.Fatal(err)
+ }
+ // config plugin
+ vp := &config.Viper{}
+ vp.Path = "configs/.rr-file-logger.yaml"
+ vp.Prefix = "rr"
+
+ err = container.RegisterAll(
+ vp,
+ &rpc.Plugin{},
+ &logger.ZapLogger{},
+ &httpPlugin.Plugin{},
+ &server.Plugin{},
+ )
+ assert.NoError(t, err)
+
+ err = container.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ errCh, err := container.Serve()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // stop by CTRL+C
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+
+ stopCh := make(chan struct{}, 1)
+
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ go func() {
+ defer wg.Done()
+ for {
+ select {
+ case e := <-errCh:
+ assert.NoError(t, e.Error)
+ assert.NoError(t, container.Stop())
+ return
+ case <-c:
+ err = container.Stop()
+ assert.NoError(t, err)
+ return
+ case <-stopCh:
+ assert.NoError(t, container.Stop())
+ return
+ }
+ }
+ }()
+
+ time.Sleep(time.Second * 2)
+ t.Run("HTTPEchoReq", httpEcho)
+
+ f, err := os.ReadFile("test.log")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ strings.Contains(string(f), "worker constructed")
+ strings.Contains(string(f), "201 GET")
+
+ _ = os.Remove("test.log")
+
+ stopCh <- struct{}{}
+ wg.Wait()
+}
+
+func httpEcho(t *testing.T) {
+ req, err := http.NewRequest(http.MethodGet, "http://localhost:54224?hello=world", nil)
+ assert.NoError(t, err)
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusCreated, r.StatusCode)
+
+ err = r.Body.Close()
+ assert.NoError(t, err)
+}