Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server: add support for log rotation #12774

Merged
merged 2 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG-3.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
- Setting this flag enables `SO_REUSEADDR` which allows binding to an address in `TIME_WAIT` state, improving etcd restart time.
- Reduce [around 30% memory allocation by logging range response size without marshal](https://github.com/etcd-io/etcd/pull/12871).
- `ETCD_VERIFY="all"` enviroment triggers [additional verification of consistency](https://github.com/etcd-io/etcd/pull/) of etcd data-dir files.
- Add [`etcd --enable-log-rotation`](https://github.com/etcd-io/etcd/pull/12774) boolean flag which enables log rotation if true.
- Add [`etcd --log-rotation-config-json`](https://github.com/etcd-io/etcd/pull/12774) flag which allows passthrough of JSON config to configure log rotation for a file output target.
### Package `runtime`

- Optimize [`runtime.FDUsage` by removing unnecessary sorting](https://github.com/etcd-io/etcd/pull/12214).
Expand All @@ -171,6 +173,8 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
- Use `embed.Config.LogLevel` instead.
- Add [`embed.Config.ZapLoggerBuilder`](https://github.com/etcd-io/etcd/pull/11147) to allow creating a custom zap logger.
- Replace [global `*zap.Logger` with etcd server logger object](https://github.com/etcd-io/etcd/pull/12212).
- Add [`embed.Config.EnableLogRotation`](https://github.com/etcd-io/etcd/pull/12774) which enables log rotation if true.
- Add [`embed.Config.LogRotationConfigJSON`](https://github.com/etcd-io/etcd/pull/12774) to allow passthrough of JSON config to configure log rotation for a file output target.

### Package `clientv3`

Expand Down
9 changes: 9 additions & 0 deletions bill-of-materials.json
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,15 @@
}
]
},
{
"project": "gopkg.in/natefinch/lumberjack.v2",
"licenses": [
{
"type": "MIT License",
"confidence": 1
}
]
},
{
"project": "gopkg.in/yaml.v2",
"licenses": [
Expand Down
1 change: 1 addition & 0 deletions etcdctl/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
Expand Down Expand Up @@ -477,6 +478,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
29 changes: 22 additions & 7 deletions server/embed/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ const (
StdErrLogOutput = "stderr"
StdOutLogOutput = "stdout"

// DefaultLogRotationConfig is the default configuration used for log rotation.
// Log rotation is disabled by default.
// MaxSize = 100 // MB
// MaxAge = 0 // days (no limit)
// MaxBackups = 0 // no limit
// LocalTime = false // use computers local time, UTC by default
// Compress = false // compress the rotated log in gzip format
DefaultLogRotationConfig = `{"maxsize": 100, "maxage": 0, "maxbackups": 0, "localtime": false, "compress": false}`

// DefaultStrictReconfigCheck is the default value for "--strict-reconfig-check" flag.
// It's enabled by default.
DefaultStrictReconfigCheck = true
Expand All @@ -86,6 +95,7 @@ var (
ErrConflictBootstrapFlags = fmt.Errorf("multiple discovery or bootstrap flags are set. " +
"Choose one of \"initial-cluster\", \"discovery\" or \"discovery-srv\"")
ErrUnsetAdvertiseClientURLsFlag = fmt.Errorf("--advertise-client-urls is required when --listen-client-urls is set explicitly")
ErrLogRotationInvalidLogOutput = fmt.Errorf("--log-outputs requires a single file path when --log-rotate-config-json is defined")

DefaultInitialAdvertisePeerURLs = "http://localhost:2380"
DefaultAdvertiseClientURLs = "http://localhost:2379"
Expand Down Expand Up @@ -320,7 +330,10 @@ type Config struct {
// - file path to append server logs to.
// It can be multiple when "Logger" is zap.
LogOutputs []string `json:"log-outputs"`

// EnableLogRotation enables log rotation of a single LogOutputs file target.
EnableLogRotation bool `json:"enable-log-rotation"`
// LogRotationConfigJSON is a passthrough allowing a log rotation JSON config to be passed directly.
LogRotationConfigJSON string `json:"log-rotation-config-json"`
// ZapLoggerBuilder is used to build the zap logger.
ZapLoggerBuilder func(*Config) error

Expand Down Expand Up @@ -437,12 +450,14 @@ func NewConfig() *Config {

PreVote: true,

loggerMu: new(sync.RWMutex),
logger: nil,
Logger: "zap",
LogOutputs: []string{DefaultLogOutput},
LogLevel: logutil.DefaultLogLevel,
EnableGRPCGateway: true,
loggerMu: new(sync.RWMutex),
logger: nil,
Logger: "zap",
LogOutputs: []string{DefaultLogOutput},
LogLevel: logutil.DefaultLogLevel,
EnableLogRotation: false,
LogRotationConfigJSON: DefaultLogRotationConfig,
EnableGRPCGateway: true,

ExperimentalDowngradeCheckTime: DefaultDowngradeCheckTime,
ExperimentalMemoryMlock: false,
Expand Down
65 changes: 63 additions & 2 deletions server/embed/config_logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ package embed

import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"

"go.etcd.io/etcd/client/pkg/v3/logutil"
Expand All @@ -26,6 +29,7 @@ import (
"go.uber.org/zap/zapgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"gopkg.in/natefinch/lumberjack.v2"
)

// GetLogger returns the logger.
Expand Down Expand Up @@ -54,6 +58,11 @@ func (cfg *Config) setupLogging() error {
}
}
}
if cfg.EnableLogRotation {
if err := setupLogRotation(cfg.LogOutputs, cfg.LogRotationConfigJSON); err != nil {
return err
}
}

outputPaths, errOutputPaths := make([]string, 0), make([]string, 0)
isJournal := false
Expand All @@ -75,8 +84,15 @@ func (cfg *Config) setupLogging() error {
errOutputPaths = append(errOutputPaths, StdOutLogOutput)

default:
outputPaths = append(outputPaths, v)
errOutputPaths = append(errOutputPaths, v)
var path string
if cfg.EnableLogRotation {
// append rotate scheme to logs managed by lumberjack log rotation
path = fmt.Sprintf("rotate:%s", v)
} else {
path = v
}
outputPaths = append(outputPaths, path)
errOutputPaths = append(errOutputPaths, path)
}
}

Expand Down Expand Up @@ -211,3 +227,48 @@ func (cfg *Config) SetupGlobalLoggers() {
zap.ReplaceGlobals(lg)
}
}

type logRotationConfig struct {
*lumberjack.Logger
}

// Sync implements zap.Sink
func (logRotationConfig) Sync() error { return nil }

// setupLogRotation initializes log rotation for a single file path target.
func setupLogRotation(logOutputs []string, logRotateConfigJSON string) error {
var logRotationConfig logRotationConfig
outputFilePaths := 0
for _, v := range logOutputs {
switch v {
case DefaultLogOutput, StdErrLogOutput, StdOutLogOutput:
continue
default:
outputFilePaths++
}
}
// log rotation requires file target
if len(logOutputs) == 1 && outputFilePaths == 0 {
return ErrLogRotationInvalidLogOutput
}
// support max 1 file target for log rotation
if outputFilePaths > 1 {
return ErrLogRotationInvalidLogOutput
}

if err := json.Unmarshal([]byte(logRotateConfigJSON), &logRotationConfig); err != nil {
var unmarshalTypeError *json.UnmarshalTypeError
var syntaxError *json.SyntaxError
switch {
case errors.As(err, &syntaxError):
return fmt.Errorf("improperly formatted log rotation config: %w", err)
case errors.As(err, &unmarshalTypeError):
return fmt.Errorf("invalid log rotation config: %w", err)
}
}
zap.RegisterSink("rotate", func(u *url.URL) (zap.Sink, error) {
logRotationConfig.Filename = u.Path
return &logRotationConfig, nil
})
return nil
}
75 changes: 75 additions & 0 deletions server/embed/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package embed

import (
"errors"
"fmt"
"io/ioutil"
"net"
Expand Down Expand Up @@ -289,3 +290,77 @@ func TestPeerURLsMapAndTokenFromSRV(t *testing.T) {
}
}
}

func TestLogRotation(t *testing.T) {
tests := []struct {
name string
logOutputs []string
logRotationConfig string
wantErr bool
wantErrMsg error
}{
{
name: "mixed log output targets",
logOutputs: []string{"stderr", "/tmp/path"},
logRotationConfig: `{"maxsize": 1}`,
hexfusion marked this conversation as resolved.
Show resolved Hide resolved
},
{
name: "no file targets",
logOutputs: []string{"stderr"},
logRotationConfig: `{"maxsize": 1}`,
wantErr: true,
wantErrMsg: ErrLogRotationInvalidLogOutput,
},
{
name: "multiple file targets",
logOutputs: []string{"/tmp/path1", "/tmp/path2"},
logRotationConfig: DefaultLogRotationConfig,
wantErr: true,
wantErrMsg: ErrLogRotationInvalidLogOutput,
},
{
name: "default output",
logRotationConfig: `{"maxsize": 1}`,
wantErr: true,
wantErrMsg: ErrLogRotationInvalidLogOutput,
},
{
name: "default log rotation config",
logOutputs: []string{"/tmp/path"},
logRotationConfig: DefaultLogRotationConfig,
},
{
name: "invalid logger config",
logOutputs: []string{"/tmp/path"},
logRotationConfig: `{"maxsize": true}`,
wantErr: true,
wantErrMsg: errors.New("invalid log rotation config: json: cannot unmarshal bool into Go struct field logRotationConfig.maxsize of type int"),
},
{
name: "improperly formatted logger config",
logOutputs: []string{"/tmp/path"},
logRotationConfig: `{"maxsize": true`,
wantErr: true,
wantErrMsg: errors.New("improperly formatted log rotation config: unexpected end of JSON input"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := NewConfig()
cfg.Logger = "zap"
cfg.LogOutputs = tt.logOutputs
cfg.EnableLogRotation = true
cfg.LogRotationConfigJSON = tt.logRotationConfig
err := cfg.Validate()
if err != nil && !tt.wantErr {
t.Errorf("test %q, unexpected error %v", tt.name, err)
}
if err != nil && tt.wantErr && tt.wantErrMsg.Error() != err.Error() {
t.Errorf("test %q, expected error: %+v, got: %+v", tt.name, tt.wantErrMsg, err)
}
if err == nil && tt.wantErr {
t.Errorf("test %q, expected error, got nil", tt.name)
}
})
}
}
2 changes: 2 additions & 0 deletions server/etcdmain/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ func newConfig() *config {
fs.StringVar(&cfg.ec.Logger, "logger", "zap", "Currently only supports 'zap' for structured logging.")
fs.Var(flags.NewUniqueStringsValue(embed.DefaultLogOutput), "log-outputs", "Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.")
fs.StringVar(&cfg.ec.LogLevel, "log-level", logutil.DefaultLogLevel, "Configures log level. Only supports debug, info, warn, error, panic, or fatal. Default 'info'.")
fs.BoolVar(&cfg.ec.EnableLogRotation, "enable-log-rotation", false, "Enable log rotation of a single log-outputs file target.")
fs.StringVar(&cfg.ec.LogRotationConfigJSON, "log-rotation-config-json", embed.DefaultLogRotationConfig, "Configures log rotation if enabled with a JSON logger config. Default: MaxSize=100(MB), MaxAge=0(days,no limit), MaxBackups=0(no limit), LocalTime=false(UTC), Compress=false(gzip)")

// version
fs.BoolVar(&cfg.printVersion, "version", false, "Print the version and exit.")
Expand Down
4 changes: 4 additions & 0 deletions server/etcdmain/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ Logging:
Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd, or list of comma separated output targets.
--log-level 'info'
Configures log level. Only supports debug, info, warn, error, panic, or fatal.
--enable-log-rotation 'false'
Enable log rotation of a single log-outputs file target.
--log-rotation-config-json '{"maxsize": 100, "maxage": 0, "maxbackups": 0, "localtime": false, "compress": false}'
hexfusion marked this conversation as resolved.
Show resolved Hide resolved
Configures log rotation if enabled with a JSON logger config. MaxSize(MB), MaxAge(days,0=no limit), MaxBackups(0=no limit), LocalTime(use computers local time), Compress(gzip)".

v2 Proxy (to be deprecated in v4):
--proxy 'off'
Expand Down
1 change: 1 addition & 0 deletions server/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
google.golang.org/grpc v1.36.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0
sigs.k8s.io/yaml v1.2.0
)

Expand Down
2 changes: 2 additions & 0 deletions server/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
2 changes: 2 additions & 0 deletions tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down