2018-08-27 19:10:55 +02:00
|
|
|
package logging
|
|
|
|
|
|
|
|
import (
|
2018-08-27 22:21:45 +02:00
|
|
|
"context"
|
2018-08-27 19:10:55 +02:00
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2019-03-15 17:44:41 +01:00
|
|
|
"log/syslog"
|
2018-12-11 22:01:50 +01:00
|
|
|
"os"
|
|
|
|
|
2018-08-27 22:21:45 +02:00
|
|
|
"github.com/mattn/go-isatty"
|
|
|
|
"github.com/pkg/errors"
|
2018-12-11 22:01:50 +01:00
|
|
|
|
2018-08-27 22:21:45 +02:00
|
|
|
"github.com/zrepl/zrepl/config"
|
2019-07-26 21:12:21 +02:00
|
|
|
"github.com/zrepl/zrepl/daemon/hooks"
|
2018-08-30 13:30:18 +02:00
|
|
|
"github.com/zrepl/zrepl/daemon/pruner"
|
2018-12-11 22:01:50 +01:00
|
|
|
"github.com/zrepl/zrepl/daemon/snapper"
|
2018-08-27 22:21:45 +02:00
|
|
|
"github.com/zrepl/zrepl/endpoint"
|
2018-08-27 19:10:55 +02:00
|
|
|
"github.com/zrepl/zrepl/logger"
|
2019-03-11 13:46:36 +01:00
|
|
|
"github.com/zrepl/zrepl/replication/driver"
|
2019-03-21 17:00:13 +01:00
|
|
|
"github.com/zrepl/zrepl/replication/logic"
|
2018-12-11 22:01:50 +01:00
|
|
|
"github.com/zrepl/zrepl/rpc"
|
|
|
|
"github.com/zrepl/zrepl/rpc/transportmux"
|
2018-08-27 22:21:45 +02:00
|
|
|
"github.com/zrepl/zrepl/tlsconf"
|
2018-12-11 22:01:50 +01:00
|
|
|
"github.com/zrepl/zrepl/transport"
|
2018-08-27 19:10:55 +02:00
|
|
|
)
|
|
|
|
|
2018-08-31 21:50:59 +02:00
|
|
|
func OutletsFromConfig(in config.LoggingOutletEnumList) (*logger.Outlets, error) {
|
2018-08-27 19:10:55 +02:00
|
|
|
|
|
|
|
outlets := logger.NewOutlets()
|
|
|
|
|
|
|
|
if len(in) == 0 {
|
|
|
|
// Default config
|
|
|
|
out := WriterOutlet{&HumanFormatter{}, os.Stdout}
|
|
|
|
outlets.Add(out, logger.Warn)
|
|
|
|
return outlets, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var syslogOutlets, stdoutOutlets int
|
|
|
|
for lei, le := range in {
|
|
|
|
|
2019-08-20 17:04:13 +02:00
|
|
|
outlet, minLevel, err := ParseOutlet(le)
|
2018-08-27 19:10:55 +02:00
|
|
|
if err != nil {
|
|
|
|
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++
|
|
|
|
}
|
|
|
|
|
|
|
|
outlets.Add(outlet, minLevel)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if syslogOutlets > 1 {
|
|
|
|
return nil, errors.Errorf("can only define one 'syslog' outlet")
|
|
|
|
}
|
|
|
|
if stdoutOutlets > 1 {
|
|
|
|
return nil, errors.Errorf("can only define one 'stdout' outlet")
|
|
|
|
}
|
|
|
|
|
|
|
|
return outlets, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-12-11 22:01:50 +01:00
|
|
|
type Subsystem string
|
|
|
|
|
2018-08-27 22:21:45 +02:00
|
|
|
const (
|
2018-12-11 22:01:50 +01:00
|
|
|
SubsysReplication Subsystem = "repl"
|
|
|
|
SubsyEndpoint Subsystem = "endpoint"
|
|
|
|
SubsysPruning Subsystem = "pruning"
|
|
|
|
SubsysSnapshot Subsystem = "snapshot"
|
2019-07-26 21:12:21 +02:00
|
|
|
SubsysHooks Subsystem = "hook"
|
2018-12-11 22:01:50 +01:00
|
|
|
SubsysTransport Subsystem = "transport"
|
|
|
|
SubsysTransportMux Subsystem = "transportmux"
|
|
|
|
SubsysRPC Subsystem = "rpc"
|
|
|
|
SubsysRPCControl Subsystem = "rpc.ctrl"
|
|
|
|
SubsysRPCData Subsystem = "rpc.data"
|
2018-08-27 22:21:45 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func WithSubsystemLoggers(ctx context.Context, log logger.Logger) context.Context {
|
2019-03-21 17:00:13 +01:00
|
|
|
ctx = logic.WithLogger(ctx, log.WithField(SubsysField, SubsysReplication))
|
2019-03-11 13:46:36 +01:00
|
|
|
ctx = driver.WithLogger(ctx, log.WithField(SubsysField, SubsysReplication))
|
2018-12-11 22:01:50 +01:00
|
|
|
ctx = endpoint.WithLogger(ctx, log.WithField(SubsysField, SubsyEndpoint))
|
|
|
|
ctx = pruner.WithLogger(ctx, log.WithField(SubsysField, SubsysPruning))
|
|
|
|
ctx = snapper.WithLogger(ctx, log.WithField(SubsysField, SubsysSnapshot))
|
2019-07-26 21:12:21 +02:00
|
|
|
ctx = hooks.WithLogger(ctx, log.WithField(SubsysField, SubsysHooks))
|
2018-12-11 22:01:50 +01:00
|
|
|
ctx = transport.WithLogger(ctx, log.WithField(SubsysField, SubsysTransport))
|
|
|
|
ctx = transportmux.WithLogger(ctx, log.WithField(SubsysField, SubsysTransportMux))
|
|
|
|
ctx = rpc.WithLoggers(ctx,
|
|
|
|
rpc.Loggers{
|
|
|
|
General: log.WithField(SubsysField, SubsysRPC),
|
|
|
|
Control: log.WithField(SubsysField, SubsysRPCControl),
|
|
|
|
Data: log.WithField(SubsysField, SubsysRPCData),
|
|
|
|
},
|
|
|
|
)
|
2018-08-27 22:21:45 +02:00
|
|
|
return ctx
|
|
|
|
}
|
2018-08-27 19:10:55 +02:00
|
|
|
|
2018-12-11 22:01:50 +01:00
|
|
|
func LogSubsystem(log logger.Logger, subsys Subsystem) logger.Logger {
|
|
|
|
return log.ReplaceField(SubsysField, subsys)
|
|
|
|
}
|
|
|
|
|
2018-08-27 19:10:55 +02:00
|
|
|
func parseLogFormat(i interface{}) (f EntryFormatter, err error) {
|
|
|
|
var is string
|
|
|
|
switch j := i.(type) {
|
|
|
|
case string:
|
|
|
|
is = j
|
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("invalid log format: wrong type: %T", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch is {
|
|
|
|
case "human":
|
|
|
|
return &HumanFormatter{}, nil
|
|
|
|
case "logfmt":
|
|
|
|
return &LogfmtFormatter{}, nil
|
|
|
|
case "json":
|
|
|
|
return &JSONFormatter{}, nil
|
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("invalid log format: '%s'", is)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-08-20 17:04:13 +02:00
|
|
|
func ParseOutlet(in config.LoggingOutletEnum) (o logger.Outlet, level logger.Level, err error) {
|
2018-08-27 19:10:55 +02:00
|
|
|
|
|
|
|
parseCommon := func(common config.LoggingOutletCommon) (logger.Level, EntryFormatter, error) {
|
|
|
|
if common.Level == "" || common.Format == "" {
|
|
|
|
return 0, nil, errors.Errorf("must specify 'level' and 'format' field")
|
|
|
|
}
|
|
|
|
|
|
|
|
minLevel, err := logger.ParseLevel(common.Level)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, errors.Wrap(err, "cannot parse 'level' field")
|
|
|
|
}
|
|
|
|
formatter, err := parseLogFormat(common.Format)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, errors.Wrap(err, "cannot parse 'formatter' field")
|
|
|
|
}
|
|
|
|
return minLevel, formatter, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var f EntryFormatter
|
|
|
|
|
|
|
|
switch v := in.Ret.(type) {
|
2018-08-27 22:21:45 +02:00
|
|
|
case *config.StdoutLoggingOutlet:
|
2018-08-27 19:10:55 +02:00
|
|
|
level, f, err = parseCommon(v.LoggingOutletCommon)
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
o, err = parseStdoutOutlet(v, f)
|
2018-08-27 22:21:45 +02:00
|
|
|
case *config.TCPLoggingOutlet:
|
2018-08-27 19:10:55 +02:00
|
|
|
level, f, err = parseCommon(v.LoggingOutletCommon)
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
o, err = parseTCPOutlet(v, f)
|
2018-08-27 22:21:45 +02:00
|
|
|
case *config.SyslogLoggingOutlet:
|
2018-08-27 19:10:55 +02:00
|
|
|
level, f, err = parseCommon(v.LoggingOutletCommon)
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
o, err = parseSyslogOutlet(v, f)
|
|
|
|
default:
|
|
|
|
panic(v)
|
|
|
|
}
|
|
|
|
return o, level, err
|
|
|
|
}
|
|
|
|
|
2018-08-27 22:21:45 +02:00
|
|
|
func parseStdoutOutlet(in *config.StdoutLoggingOutlet, formatter EntryFormatter) (WriterOutlet, error) {
|
2018-08-27 19:10:55 +02:00
|
|
|
flags := MetadataAll
|
|
|
|
writer := os.Stdout
|
|
|
|
if !isatty.IsTerminal(writer.Fd()) && !in.Time {
|
|
|
|
flags &= ^MetadataTime
|
|
|
|
}
|
2018-08-30 13:30:18 +02:00
|
|
|
if isatty.IsTerminal(writer.Fd()) && !in.Color {
|
|
|
|
flags &= ^MetadataColor
|
|
|
|
}
|
2018-08-27 19:10:55 +02:00
|
|
|
|
|
|
|
formatter.SetMetadataFlags(flags)
|
|
|
|
return WriterOutlet{
|
|
|
|
formatter,
|
|
|
|
os.Stdout,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-08-27 22:21:45 +02:00
|
|
|
func parseTCPOutlet(in *config.TCPLoggingOutlet, formatter EntryFormatter) (out *TCPOutlet, err error) {
|
2018-08-27 19:10:55 +02:00
|
|
|
var tlsConfig *tls.Config
|
|
|
|
if in.TLS != nil {
|
|
|
|
tlsConfig, err = func(m *config.TCPLoggingOutletTLS, host string) (*tls.Config, error) {
|
|
|
|
clientCert, err := tls.LoadX509KeyPair(m.Cert, m.Key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "cannot load client cert")
|
|
|
|
}
|
|
|
|
|
|
|
|
var rootCAs *x509.CertPool
|
|
|
|
if m.CA == "" {
|
|
|
|
if rootCAs, err = x509.SystemCertPool(); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "cannot open system cert pool")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rootCAs, err = tlsconf.ParseCAFile(m.CA)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "cannot parse CA cert")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if rootCAs == nil {
|
|
|
|
panic("invariant violated")
|
|
|
|
}
|
|
|
|
|
|
|
|
return tlsconf.ClientAuthClient(host, rootCAs, clientCert)
|
|
|
|
}(in.TLS, in.Address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("cannot not parse TLS config in field 'tls'")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
formatter.SetMetadataFlags(MetadataAll)
|
|
|
|
return NewTCPOutlet(formatter, in.Net, in.Address, tlsConfig, in.RetryInterval), nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-08-27 22:21:45 +02:00
|
|
|
func parseSyslogOutlet(in *config.SyslogLoggingOutlet, formatter EntryFormatter) (out *SyslogOutlet, err error) {
|
2018-08-27 19:10:55 +02:00
|
|
|
out = &SyslogOutlet{}
|
|
|
|
out.Formatter = formatter
|
|
|
|
out.Formatter.SetMetadataFlags(MetadataNone)
|
2019-03-15 17:44:41 +01:00
|
|
|
out.Facility = syslog.Priority(*in.Facility)
|
2018-08-27 19:10:55 +02:00
|
|
|
out.RetryInterval = in.RetryInterval
|
|
|
|
return out, nil
|
|
|
|
}
|