mirror of
https://github.com/zrepl/zrepl.git
synced 2025-06-19 17:27:46 +02:00
config: logging: defaults + definition as list
* Stdout logger as default logger * Clearer keyword / value separation * Allows multiple outlet definitions BREAK CONFIG fixes #20 fixes #19
This commit is contained in:
parent
2764c95952
commit
b95260f4b5
@ -19,149 +19,52 @@ type SetNoMetadataFormatter interface {
|
|||||||
SetNoMetadata(noMetadata bool)
|
SetNoMetadata(noMetadata bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutletCommon struct {
|
||||||
|
MinLevel logger.Level
|
||||||
|
Formatter EntryFormatter
|
||||||
|
}
|
||||||
|
|
||||||
func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
||||||
|
|
||||||
c = &LoggingConfig{}
|
c = &LoggingConfig{}
|
||||||
if i == nil {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var asMap struct {
|
|
||||||
Stdout struct {
|
|
||||||
Level string
|
|
||||||
Format string
|
|
||||||
}
|
|
||||||
TCP struct {
|
|
||||||
Level string
|
|
||||||
Format string
|
|
||||||
Net string
|
|
||||||
Address string
|
|
||||||
RetryInterval string `mapstructure:"retry_interval"`
|
|
||||||
TLS *struct {
|
|
||||||
CA string
|
|
||||||
Cert string
|
|
||||||
Key string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Syslog struct {
|
|
||||||
Enable bool
|
|
||||||
Format string
|
|
||||||
RetryInterval string `mapstructure:"retry_interval"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = mapstructure.Decode(i, &asMap); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "mapstructure error")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Outlets = logger.NewOutlets()
|
c.Outlets = logger.NewOutlets()
|
||||||
|
|
||||||
if asMap.Stdout.Level != "" {
|
var asList []interface{}
|
||||||
|
if err = mapstructure.Decode(i, &asList); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "mapstructure error")
|
||||||
|
}
|
||||||
|
if len(asList) == 0 {
|
||||||
|
// Default config
|
||||||
|
out := WriterOutlet{&HumanFormatter{}, os.Stdout}
|
||||||
|
c.Outlets.Add(out, logger.Warn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
out := WriterOutlet{
|
var syslogOutlets, stdoutOutlets int
|
||||||
&HumanFormatter{},
|
for lei, le := range asList {
|
||||||
os.Stdout,
|
|
||||||
}
|
|
||||||
|
|
||||||
level, err := logger.ParseLevel(asMap.Stdout.Level)
|
outlet, minLevel, err := parseOutlet(le)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "cannot parse 'level'")
|
return nil, errors.Wrapf(err, "cannot parse outlet #%d", lei)
|
||||||
|
}
|
||||||
|
var _ logger.Outlet = WriterOutlet{}
|
||||||
|
var _ logger.Outlet = &SyslogOutlet{}
|
||||||
|
switch outlet.(type) {
|
||||||
|
case *SyslogOutlet:
|
||||||
|
syslogOutlets++
|
||||||
|
case WriterOutlet:
|
||||||
|
stdoutOutlets++
|
||||||
}
|
}
|
||||||
|
|
||||||
if asMap.Stdout.Format != "" {
|
c.Outlets.Add(outlet, minLevel)
|
||||||
out.Formatter, err = parseLogFormat(asMap.Stdout.Format)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot parse 'format'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Outlets.Add(out, level)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if asMap.TCP.Address != "" {
|
if syslogOutlets > 1 {
|
||||||
|
return nil, errors.Errorf("can only define one 'syslog' outlet")
|
||||||
out := &TCPOutlet{}
|
|
||||||
|
|
||||||
out.Formatter, err = parseLogFormat(asMap.TCP.Format)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot parse 'format'")
|
|
||||||
}
|
|
||||||
|
|
||||||
lvl, err := logger.ParseLevel(asMap.TCP.Level)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot parse 'level'")
|
|
||||||
}
|
|
||||||
|
|
||||||
out.RetryInterval, err = time.ParseDuration(asMap.TCP.RetryInterval)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot parse 'retry_interval'")
|
|
||||||
}
|
|
||||||
|
|
||||||
out.Net, out.Address = asMap.TCP.Net, asMap.TCP.Address
|
|
||||||
|
|
||||||
if asMap.TCP.TLS != nil {
|
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(asMap.TCP.TLS.Cert, asMap.TCP.TLS.Key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot load client cert")
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootCAs *x509.CertPool
|
|
||||||
if asMap.TCP.TLS.CA == "" {
|
|
||||||
if rootCAs, err = x509.SystemCertPool(); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot open system cert pool")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rootCAs = x509.NewCertPool()
|
|
||||||
rootCAPEM, err := ioutil.ReadFile(asMap.TCP.TLS.CA)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot load CA cert")
|
|
||||||
}
|
|
||||||
if !rootCAs.AppendCertsFromPEM(rootCAPEM) {
|
|
||||||
return nil, errors.New("cannot parse CA cert")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil && asMap.TCP.TLS.CA == "" {
|
|
||||||
return nil, errors.Wrap(err, "cannot load root ca pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
out.TLS = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
}
|
|
||||||
|
|
||||||
out.TLS.BuildNameToCertificate()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Outlets.Add(out, lvl)
|
|
||||||
}
|
}
|
||||||
|
if stdoutOutlets > 1 {
|
||||||
if asMap.Syslog.Enable {
|
return nil, errors.Errorf("can only define one 'stdout' outlet")
|
||||||
|
|
||||||
out := &SyslogOutlet{}
|
|
||||||
|
|
||||||
out.Formatter = &HumanFormatter{}
|
|
||||||
if asMap.Syslog.Format != "" {
|
|
||||||
out.Formatter, err = parseLogFormat(asMap.Syslog.Format)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot parse 'format'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f, ok := out.Formatter.(SetNoMetadataFormatter); ok {
|
|
||||||
f.SetNoMetadata(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
out.RetryInterval = 0 // default to 0 as we assume local syslog will just work
|
|
||||||
if asMap.Syslog.RetryInterval != "" {
|
|
||||||
out.RetryInterval, err = time.ParseDuration(asMap.Syslog.RetryInterval)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot parse 'retry_interval'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Outlets.Add(out, logger.Debug)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
@ -189,3 +92,142 @@ func parseLogFormat(i interface{}) (f EntryFormatter, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseOutlet(i interface{}) (o logger.Outlet, level logger.Level, err error) {
|
||||||
|
|
||||||
|
var in struct {
|
||||||
|
Outlet string
|
||||||
|
Level string
|
||||||
|
Format string
|
||||||
|
}
|
||||||
|
if err = mapstructure.Decode(i, &in); err != nil {
|
||||||
|
err = errors.Wrap(err, "mapstructure error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if in.Outlet == "" || in.Level == "" || in.Format == "" {
|
||||||
|
err = errors.Errorf("must specify 'outlet', 'level' and 'format' field")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common := &OutletCommon{}
|
||||||
|
common.MinLevel, err = logger.ParseLevel(in.Level)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "cannot parse 'level' field")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.Formatter, err = parseLogFormat(in.Format)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "cannot parse")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch in.Outlet {
|
||||||
|
case "stdout":
|
||||||
|
o, err = parseStdoutOutlet(i, common)
|
||||||
|
case "tcp":
|
||||||
|
o, err = parseTCPOutlet(i, common)
|
||||||
|
case "syslog":
|
||||||
|
o, err = parseSyslogOutlet(i, common)
|
||||||
|
default:
|
||||||
|
err = errors.Errorf("unknown outlet type '%s'", in.Outlet)
|
||||||
|
}
|
||||||
|
return o, common.MinLevel, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStdoutOutlet(i interface{}, common *OutletCommon) (WriterOutlet, error) {
|
||||||
|
return WriterOutlet{
|
||||||
|
common.Formatter,
|
||||||
|
os.Stdout,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTCPOutlet(i interface{}, common *OutletCommon) (out *TCPOutlet, err error) {
|
||||||
|
|
||||||
|
out = &TCPOutlet{}
|
||||||
|
out.Formatter = common.Formatter
|
||||||
|
|
||||||
|
var in struct {
|
||||||
|
Net string
|
||||||
|
Address string
|
||||||
|
RetryInterval string `mapstructure:"retry_interval"`
|
||||||
|
TLS *struct {
|
||||||
|
CA string
|
||||||
|
Cert string
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = mapstructure.Decode(i, &in); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "mapstructure error")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.RetryInterval, err = time.ParseDuration(in.RetryInterval)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot parse 'retry_interval'")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Net, out.Address = in.Net, in.Address
|
||||||
|
|
||||||
|
if in.TLS != nil {
|
||||||
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(in.TLS.Cert, in.TLS.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot load client cert")
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootCAs *x509.CertPool
|
||||||
|
if in.TLS.CA == "" {
|
||||||
|
if rootCAs, err = x509.SystemCertPool(); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot open system cert pool")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rootCAs = x509.NewCertPool()
|
||||||
|
rootCAPEM, err := ioutil.ReadFile(in.TLS.CA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot load CA cert")
|
||||||
|
}
|
||||||
|
if !rootCAs.AppendCertsFromPEM(rootCAPEM) {
|
||||||
|
return nil, errors.New("cannot parse CA cert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && in.TLS.CA == "" {
|
||||||
|
return nil, errors.Wrap(err, "cannot load root ca pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.TLS = &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
RootCAs: rootCAs,
|
||||||
|
}
|
||||||
|
|
||||||
|
out.TLS.BuildNameToCertificate()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSyslogOutlet(i interface{}, common *OutletCommon) (out *SyslogOutlet, err error) {
|
||||||
|
|
||||||
|
var in struct {
|
||||||
|
RetryInterval string `mapstructure:"retry_interval"`
|
||||||
|
}
|
||||||
|
if err = mapstructure.Decode(i, &in); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "mapstructure error")
|
||||||
|
}
|
||||||
|
|
||||||
|
out = &SyslogOutlet{}
|
||||||
|
out.Formatter = common.Formatter
|
||||||
|
if f, ok := out.Formatter.(SetNoMetadataFormatter); ok {
|
||||||
|
f.SetNoMetadata(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.RetryInterval = 0 // default to 0 as we assume local syslog will just work
|
||||||
|
if in.RetryInterval != "" {
|
||||||
|
out.RetryInterval, err = time.ParseDuration(in.RetryInterval)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot parse 'retry_interval'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
global:
|
global:
|
||||||
logging:
|
logging:
|
||||||
stdout:
|
|
||||||
|
- outlet: stdout
|
||||||
level: warn
|
level: warn
|
||||||
format: human
|
format: human
|
||||||
tcp:
|
|
||||||
|
- outlet: tcp
|
||||||
level: debug
|
level: debug
|
||||||
format: json
|
format: json
|
||||||
net: tcp
|
net: tcp
|
||||||
@ -13,8 +15,10 @@ global:
|
|||||||
ca: sampleconf/random/logging/logserver.crt
|
ca: sampleconf/random/logging/logserver.crt
|
||||||
cert: sampleconf/random/logging/client.crt
|
cert: sampleconf/random/logging/client.crt
|
||||||
key: sampleconf/random/logging/client.key
|
key: sampleconf/random/logging/client.key
|
||||||
syslog:
|
|
||||||
enable: true
|
- outlet: syslog
|
||||||
|
level: debug
|
||||||
format: logfmt
|
format: logfmt
|
||||||
|
|
||||||
|
|
||||||
jobs: []
|
jobs: []
|
||||||
|
@ -7,20 +7,39 @@ zrepl uses structured logging to provide users with easily processable log messa
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Logging is configured in the `global` section of the [configuration file]({{< relref "install/_index.md#configuration-files" >}}).<br/>
|
Logging outlets are configured in the `global` section of the [configuration file]({{< relref "install/_index.md#configuration-files" >}}).<br />
|
||||||
Check out {{< sampleconflink "random/logging.yml" >}} for an example on how to configure multiple outlets:
|
Check out {{< sampleconflink "random/logging.yml" >}} for an example on how to configure multiple outlets:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
global:
|
global:
|
||||||
logging:
|
logging:
|
||||||
OUTLET_TYPE:
|
|
||||||
PARAM: VAUE
|
- outlet: OUTLET_TYPE
|
||||||
...
|
level: MINIMUM_LEVEL
|
||||||
OUTLET_TYPE:
|
format: FORMAT
|
||||||
...
|
|
||||||
jobs:
|
- outlet: OUTLET_TYPE
|
||||||
...
|
level: MINIMUM_LEVEL
|
||||||
|
format: FORMAT
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
jobs: ...
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Default Configuration
|
||||||
|
|
||||||
|
By default, the following logging configuration is used
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
global:
|
||||||
|
logging:
|
||||||
|
|
||||||
|
- outlet: "stdout"
|
||||||
|
level: "warn"
|
||||||
|
format: "human"
|
||||||
```
|
```
|
||||||
**Note**: Currently, only one instance of an outlet type can be instantiated {{< zrepl-issue 20 >}}
|
|
||||||
|
|
||||||
{{% notice info %}}
|
{{% notice info %}}
|
||||||
Output to **stderr** should always be considered a **critical error**.<br />
|
Output to **stderr** should always be considered a **critical error**.<br />
|
||||||
@ -56,30 +75,37 @@ Outlets are ... well ... outlets for log entries into the world.
|
|||||||
|
|
||||||
#### **`stdout`**
|
#### **`stdout`**
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
| Parameter | Default | Comment |
|
||||||
|-----------| --------- | ----------- |
|
|-----------| --------- | ----------- |
|
||||||
|`level` | *none* | minimum [log level](#levels) |
|
|`outlet` | *none* | required |
|
||||||
|`format` | `human` | [format](#formats) |
|
|`level` | *none* | minimum [log level](#levels), required |
|
||||||
|
|`format` | *none* | output [format](#formats), required |
|
||||||
|
|
||||||
Writes all log entries with minimum level `level` formatted by `format` to stdout.
|
Writes all log entries with minimum level `level` formatted by `format` to stdout.
|
||||||
|
|
||||||
|
Can only be specified once.
|
||||||
|
|
||||||
#### **`syslog`**
|
#### **`syslog`**
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
| Parameter | Default | Comment |
|
||||||
|-----------| --------- | ----------- |
|
|-----------| --------- | ----------- |
|
||||||
|`enable` | false | boolean |
|
|`outlet` | *none* | required |
|
||||||
|`format` | `human` | [format](#formats) |
|
|`level` | *none* | minimum [log level](#levels), required, usually `debug` |
|
||||||
|
|`format` | *none* | output [format](#formats), required|
|
||||||
|`retry_interval`| 0 | Interval between reconnection attempts to syslog |
|
|`retry_interval`| 0 | Interval between reconnection attempts to syslog |
|
||||||
|
|
||||||
Writes all log entries formatted by `format` to syslog.
|
Writes all log entries formatted by `format` to syslog.
|
||||||
On normal setups, you should not need to change the `retry_interval`.
|
On normal setups, you should not need to change the `retry_interval`.
|
||||||
|
|
||||||
|
Can only be specified once.
|
||||||
|
|
||||||
#### **`tcp`**
|
#### **`tcp`**
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
| Parameter | Default | Comment |
|
||||||
|-----------| --------- | ----------- |
|
|-----------| --------- | ----------- |
|
||||||
|`level` | *none* | minimum [log level](#levels) |
|
|`outlet` | *none* | required |
|
||||||
|`format` | *none* | [format](#formats) |
|
|`level` | *none* | minimum [log level](#levels), required |
|
||||||
|
|`format` | *none* | output [format](#formats), required |
|
||||||
|`net`|*none*|`tcp` in most cases|
|
|`net`|*none*|`tcp` in most cases|
|
||||||
|`address`|*none*|remote network, e.g. `logs.example.com:10202`|
|
|`address`|*none*|remote network, e.g. `logs.example.com:10202`|
|
||||||
|`retry_interval`|*none*|Interval between reconnection attempts to `address`|
|
|`retry_interval`|*none*|Interval between reconnection attempts to `address`|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user