mirror of
https://github.com/zrepl/zrepl.git
synced 2024-12-22 23:20:51 +01: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)
|
||||
}
|
||||
|
||||
type OutletCommon struct {
|
||||
MinLevel logger.Level
|
||||
Formatter EntryFormatter
|
||||
}
|
||||
|
||||
func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
||||
|
||||
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()
|
||||
|
||||
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{
|
||||
&HumanFormatter{},
|
||||
os.Stdout,
|
||||
}
|
||||
var syslogOutlets, stdoutOutlets int
|
||||
for lei, le := range asList {
|
||||
|
||||
level, err := logger.ParseLevel(asMap.Stdout.Level)
|
||||
outlet, minLevel, err := parseOutlet(le)
|
||||
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 != "" {
|
||||
out.Formatter, err = parseLogFormat(asMap.Stdout.Format)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse 'format'")
|
||||
}
|
||||
}
|
||||
|
||||
c.Outlets.Add(out, level)
|
||||
c.Outlets.Add(outlet, minLevel)
|
||||
|
||||
}
|
||||
|
||||
if asMap.TCP.Address != "" {
|
||||
|
||||
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 syslogOutlets > 1 {
|
||||
return nil, errors.Errorf("can only define one 'syslog' outlet")
|
||||
}
|
||||
|
||||
if asMap.Syslog.Enable {
|
||||
|
||||
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)
|
||||
|
||||
if stdoutOutlets > 1 {
|
||||
return nil, errors.Errorf("can only define one 'stdout' outlet")
|
||||
}
|
||||
|
||||
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:
|
||||
logging:
|
||||
stdout:
|
||||
|
||||
- outlet: stdout
|
||||
level: warn
|
||||
format: human
|
||||
tcp:
|
||||
|
||||
- outlet: tcp
|
||||
level: debug
|
||||
format: json
|
||||
net: tcp
|
||||
@ -13,8 +15,10 @@ global:
|
||||
ca: sampleconf/random/logging/logserver.crt
|
||||
cert: sampleconf/random/logging/client.crt
|
||||
key: sampleconf/random/logging/client.key
|
||||
syslog:
|
||||
enable: true
|
||||
|
||||
- outlet: syslog
|
||||
level: debug
|
||||
format: logfmt
|
||||
|
||||
|
||||
jobs: []
|
||||
|
@ -7,20 +7,39 @@ zrepl uses structured logging to provide users with easily processable log messa
|
||||
|
||||
## 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:
|
||||
|
||||
```yaml
|
||||
global:
|
||||
logging:
|
||||
OUTLET_TYPE:
|
||||
PARAM: VAUE
|
||||
...
|
||||
OUTLET_TYPE:
|
||||
...
|
||||
jobs:
|
||||
...
|
||||
|
||||
- outlet: OUTLET_TYPE
|
||||
level: MINIMUM_LEVEL
|
||||
format: FORMAT
|
||||
|
||||
- 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 %}}
|
||||
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`**
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| Parameter | Default | Comment |
|
||||
|-----------| --------- | ----------- |
|
||||
|`level` | *none* | minimum [log level](#levels) |
|
||||
|`format` | `human` | [format](#formats) |
|
||||
|`outlet` | *none* | required |
|
||||
|`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.
|
||||
|
||||
Can only be specified once.
|
||||
|
||||
#### **`syslog`**
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| Parameter | Default | Comment |
|
||||
|-----------| --------- | ----------- |
|
||||
|`enable` | false | boolean |
|
||||
|`format` | `human` | [format](#formats) |
|
||||
|`outlet` | *none* | required |
|
||||
|`level` | *none* | minimum [log level](#levels), required, usually `debug` |
|
||||
|`format` | *none* | output [format](#formats), required|
|
||||
|`retry_interval`| 0 | Interval between reconnection attempts to syslog |
|
||||
|
||||
Writes all log entries formatted by `format` to syslog.
|
||||
On normal setups, you should not need to change the `retry_interval`.
|
||||
|
||||
Can only be specified once.
|
||||
|
||||
#### **`tcp`**
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| Parameter | Default | Comment |
|
||||
|-----------| --------- | ----------- |
|
||||
|`level` | *none* | minimum [log level](#levels) |
|
||||
|`format` | *none* | [format](#formats) |
|
||||
|`outlet` | *none* | required |
|
||||
|`level` | *none* | minimum [log level](#levels), required |
|
||||
|`format` | *none* | output [format](#formats), required |
|
||||
|`net`|*none*|`tcp` in most cases|
|
||||
|`address`|*none*|remote network, e.g. `logs.example.com:10202`|
|
||||
|`retry_interval`|*none*|Interval between reconnection attempts to `address`|
|
||||
|
Loading…
Reference in New Issue
Block a user