mirror of
https://github.com/zrepl/zrepl.git
synced 2024-12-23 07:28:57 +01:00
add syslog outlet
This commit is contained in:
parent
e0e362c4ff
commit
c4c38d5b23
@ -12,6 +12,10 @@ type LoggingConfig struct {
|
|||||||
Outlets logger.Outlets
|
Outlets logger.Outlets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SetNoMetadataFormatter interface {
|
||||||
|
SetNoMetadata(noMetadata bool)
|
||||||
|
}
|
||||||
|
|
||||||
func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
||||||
|
|
||||||
c = &LoggingConfig{}
|
c = &LoggingConfig{}
|
||||||
@ -31,6 +35,11 @@ func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
|||||||
Address string
|
Address string
|
||||||
RetryInterval string `mapstructure:"retry_interval"`
|
RetryInterval string `mapstructure:"retry_interval"`
|
||||||
}
|
}
|
||||||
|
Syslog struct {
|
||||||
|
Enable bool
|
||||||
|
Format string
|
||||||
|
RetryInterval string `mapstructure:"retry_interval"`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err = mapstructure.Decode(i, &asMap); err != nil {
|
if err = mapstructure.Decode(i, &asMap); err != nil {
|
||||||
return nil, errors.Wrap(err, "mapstructure error")
|
return nil, errors.Wrap(err, "mapstructure error")
|
||||||
@ -41,19 +50,19 @@ func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
|||||||
if asMap.Stdout.Level != "" {
|
if asMap.Stdout.Level != "" {
|
||||||
|
|
||||||
out := WriterOutlet{
|
out := WriterOutlet{
|
||||||
HumanFormatter{},
|
&HumanFormatter{},
|
||||||
os.Stdout,
|
os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
level, err := logger.ParseLevel(asMap.Stdout.Level)
|
level, err := logger.ParseLevel(asMap.Stdout.Level)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "cannot parse stdout log level")
|
return nil, errors.Wrap(err, "cannot parse 'level'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if asMap.Stdout.Format != "" {
|
if asMap.Stdout.Format != "" {
|
||||||
out.Formatter, err = parseLogFormat(asMap.Stdout.Format)
|
out.Formatter, err = parseLogFormat(asMap.Stdout.Format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "cannot parse log format")
|
return nil, errors.Wrap(err, "cannot parse 'format'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +76,7 @@ func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
|||||||
|
|
||||||
out.Formatter, err = parseLogFormat(asMap.TCP.Format)
|
out.Formatter, err = parseLogFormat(asMap.TCP.Format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "cannot parse log 'format'")
|
return nil, errors.Wrap(err, "cannot parse 'format'")
|
||||||
}
|
}
|
||||||
|
|
||||||
lvl, err := logger.ParseLevel(asMap.TCP.Level)
|
lvl, err := logger.ParseLevel(asMap.TCP.Level)
|
||||||
@ -85,6 +94,34 @@ func parseLogging(i interface{}) (c *LoggingConfig, err error) {
|
|||||||
c.Outlets.Add(out, lvl)
|
c.Outlets.Add(out, lvl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -100,7 +137,7 @@ func parseLogFormat(i interface{}) (f EntryFormatter, err error) {
|
|||||||
|
|
||||||
switch is {
|
switch is {
|
||||||
case "human":
|
case "human":
|
||||||
return HumanFormatter{}, nil
|
return &HumanFormatter{}, nil
|
||||||
case "json":
|
case "json":
|
||||||
return &JSONFormatter{}, nil
|
return &JSONFormatter{}, nil
|
||||||
default:
|
default:
|
||||||
|
@ -36,13 +36,23 @@ func (f NoFormatter) Format(e *logger.Entry) ([]byte, error) {
|
|||||||
return []byte(e.Message), nil
|
return []byte(e.Message), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type HumanFormatter struct{}
|
type HumanFormatter struct {
|
||||||
|
NoMetadata bool
|
||||||
|
}
|
||||||
|
|
||||||
func (f HumanFormatter) Format(e *logger.Entry) (out []byte, err error) {
|
var _ SetNoMetadataFormatter = &HumanFormatter{}
|
||||||
|
|
||||||
|
func (f *HumanFormatter) SetNoMetadata(noMetadata bool) {
|
||||||
|
f.NoMetadata = noMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *HumanFormatter) Format(e *logger.Entry) (out []byte, err error) {
|
||||||
|
|
||||||
var line bytes.Buffer
|
var line bytes.Buffer
|
||||||
|
|
||||||
|
if !f.NoMetadata {
|
||||||
fmt.Fprintf(&line, "[%s]", e.Level.Short())
|
fmt.Fprintf(&line, "[%s]", e.Level.Short())
|
||||||
|
}
|
||||||
|
|
||||||
prefixFields := []string{logJobField, logTaskField, logFSField}
|
prefixFields := []string{logJobField, logTaskField, logFSField}
|
||||||
prefixed := make(map[string]bool, len(prefixFields)+2)
|
prefixed := make(map[string]bool, len(prefixFields)+2)
|
||||||
@ -69,7 +79,10 @@ func (f HumanFormatter) Format(e *logger.Entry) (out []byte, err error) {
|
|||||||
prefixed[logIncFromField], prefixed[logIncToField] = true, true
|
prefixed[logIncFromField], prefixed[logIncToField] = true, true
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&line, ": %s", e.Message)
|
if line.Len() > 0 {
|
||||||
|
fmt.Fprint(&line, ": ")
|
||||||
|
}
|
||||||
|
fmt.Fprint(&line, e.Message)
|
||||||
|
|
||||||
for field, value := range e.Fields {
|
for field, value := range e.Fields {
|
||||||
|
|
||||||
@ -88,7 +101,7 @@ func (f HumanFormatter) Format(e *logger.Entry) (out []byte, err error) {
|
|||||||
|
|
||||||
type JSONFormatter struct{}
|
type JSONFormatter struct{}
|
||||||
|
|
||||||
func (f JSONFormatter) Format(e *logger.Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(e *logger.Entry) ([]byte, error) {
|
||||||
data := make(logger.Fields, len(e.Fields)+3)
|
data := make(logger.Fields, len(e.Fields)+3)
|
||||||
for k, v := range e.Fields {
|
for k, v := range e.Fields {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zrepl/zrepl/logger"
|
"github.com/zrepl/zrepl/logger"
|
||||||
"io"
|
"io"
|
||||||
|
"log/syslog"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -61,3 +62,47 @@ func (h *TCPOutlet) WriteEntry(ctx context.Context, e logger.Entry) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SyslogOutlet struct {
|
||||||
|
Formatter EntryFormatter
|
||||||
|
RetryInterval time.Duration
|
||||||
|
writer *syslog.Writer
|
||||||
|
lastConnectAttempt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *SyslogOutlet) WriteEntry(ctx context.Context, entry logger.Entry) error {
|
||||||
|
|
||||||
|
bytes, err := o.Formatter.Format(&entry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := string(bytes)
|
||||||
|
|
||||||
|
if o.writer == nil {
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(o.lastConnectAttempt) < o.RetryInterval {
|
||||||
|
return nil // not an error toward logger
|
||||||
|
}
|
||||||
|
o.writer, err = syslog.New(syslog.LOG_LOCAL0, "zrepl")
|
||||||
|
o.lastConnectAttempt = time.Now()
|
||||||
|
if err != nil {
|
||||||
|
o.writer = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch entry.Level {
|
||||||
|
case logger.Debug:
|
||||||
|
return o.writer.Debug(s)
|
||||||
|
case logger.Info:
|
||||||
|
return o.writer.Info(s)
|
||||||
|
case logger.Warn:
|
||||||
|
return o.writer.Warning(s)
|
||||||
|
case logger.Error:
|
||||||
|
return o.writer.Err(s)
|
||||||
|
default:
|
||||||
|
return o.writer.Err(s) // write as error as reaching this case is in fact an error
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -66,7 +66,7 @@ func init() {
|
|||||||
func testCmdGlobalInit(cmd *cobra.Command, args []string) {
|
func testCmdGlobalInit(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
out := logger.NewOutlets()
|
out := logger.NewOutlets()
|
||||||
out.Add(WriterOutlet{NoFormatter{}, os.Stdout}, logger.Info)
|
out.Add(WriterOutlet{&NoFormatter{}, os.Stdout}, logger.Info)
|
||||||
log := logger.NewLogger(out, 1*time.Second)
|
log := logger.NewLogger(out, 1*time.Second)
|
||||||
testCmdGlobal.log = log
|
testCmdGlobal.log = log
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user