diff --git a/Gopkg.lock b/Gopkg.lock index 47f02e0..4239fbb 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -55,6 +55,12 @@ packages = ["."] revision = "7cafcd837844e784b526369c9bce262804aebc60" +[[projects]] + name = "github.com/mattn/go-isatty" + packages = ["."] + revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" + version = "v0.0.3" + [[projects]] branch = "master" name = "github.com/mitchellh/mapstructure" @@ -91,9 +97,15 @@ revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" version = "v1.1.4" +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix"] + revision = "bf42f188b9bc6f2cf5b8ee5a912ef1aedd0eba4c" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "d654c5e91ee04baf1a373bb241b94ab41d3838fb69a56e91fe54cc8b962eab56" + inputs-digest = "b2174984fa0452d4469597063f891904b70081586b239262690cf1b6477a3116" solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/config_logging.go b/cmd/config_logging.go index dfd6679..a2ade17 100644 --- a/cmd/config_logging.go +++ b/cmd/config_logging.go @@ -3,6 +3,7 @@ package cmd import ( "crypto/tls" "crypto/x509" + "github.com/mattn/go-isatty" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/zrepl/zrepl/logger" @@ -15,14 +16,15 @@ type LoggingConfig struct { Outlets logger.Outlets } -type SetNoMetadataFormatter interface { - SetNoMetadata(noMetadata bool) -} +type MetadataFlags int64 -type OutletCommon struct { - MinLevel logger.Level - Formatter EntryFormatter -} +const ( + MetadataTime MetadataFlags = 1 << iota + MetadataLevel + + MetadataNone MetadataFlags = 0 + MetadataAll MetadataFlags = ^0 +) func parseLogging(i interface{}) (c *LoggingConfig, err error) { @@ -109,13 +111,12 @@ func parseOutlet(i interface{}) (o logger.Outlet, level logger.Level, err error) return } - common := &OutletCommon{} - common.MinLevel, err = logger.ParseLevel(in.Level) + minLevel, err := logger.ParseLevel(in.Level) if err != nil { err = errors.Wrap(err, "cannot parse 'level' field") return } - common.Formatter, err = parseLogFormat(in.Format) + formatter, err := parseLogFormat(in.Format) if err != nil { err = errors.Wrap(err, "cannot parse") return @@ -123,29 +124,45 @@ func parseOutlet(i interface{}) (o logger.Outlet, level logger.Level, err error) switch in.Outlet { case "stdout": - o, err = parseStdoutOutlet(i, common) + o, err = parseStdoutOutlet(i, formatter) case "tcp": - o, err = parseTCPOutlet(i, common) + o, err = parseTCPOutlet(i, formatter) case "syslog": - o, err = parseSyslogOutlet(i, common) + o, err = parseSyslogOutlet(i, formatter) default: err = errors.Errorf("unknown outlet type '%s'", in.Outlet) } - return o, common.MinLevel, err + return o, minLevel, err } -func parseStdoutOutlet(i interface{}, common *OutletCommon) (WriterOutlet, error) { +func parseStdoutOutlet(i interface{}, formatter EntryFormatter) (WriterOutlet, error) { + + var in struct { + Date bool + } + if err := mapstructure.Decode(i, &in); err != nil { + return WriterOutlet{}, errors.Wrap(err, "invalid structure for stdout outlet") + } + + flags := MetadataAll + writer := os.Stdout + if !isatty.IsTerminal(writer.Fd()) && !in.Date { + flags &= ^MetadataTime + } + + formatter.SetMetadataFlags(flags) return WriterOutlet{ - common.Formatter, + formatter, os.Stdout, }, nil } -func parseTCPOutlet(i interface{}, common *OutletCommon) (out *TCPOutlet, err error) { +func parseTCPOutlet(i interface{}, formatter EntryFormatter) (out *TCPOutlet, err error) { out = &TCPOutlet{} - out.Formatter = common.Formatter + out.Formatter = formatter + out.Formatter.SetMetadataFlags(MetadataAll) var in struct { Net string @@ -206,7 +223,7 @@ func parseTCPOutlet(i interface{}, common *OutletCommon) (out *TCPOutlet, err er } -func parseSyslogOutlet(i interface{}, common *OutletCommon) (out *SyslogOutlet, err error) { +func parseSyslogOutlet(i interface{}, formatter EntryFormatter) (out *SyslogOutlet, err error) { var in struct { RetryInterval string `mapstructure:"retry_interval"` @@ -216,10 +233,8 @@ func parseSyslogOutlet(i interface{}, common *OutletCommon) (out *SyslogOutlet, } out = &SyslogOutlet{} - out.Formatter = common.Formatter - if f, ok := out.Formatter.(SetNoMetadataFormatter); ok { - f.SetNoMetadata(true) - } + out.Formatter = formatter + out.Formatter.SetMetadataFlags(MetadataNone) out.RetryInterval = 0 // default to 0 as we assume local syslog will just work if in.RetryInterval != "" { diff --git a/cmd/logging_formatters.go b/cmd/logging_formatters.go index 676ae31..dc668ca 100644 --- a/cmd/logging_formatters.go +++ b/cmd/logging_formatters.go @@ -11,6 +11,7 @@ import ( ) type EntryFormatter interface { + SetMetadataFlags(flags MetadataFlags) Format(e *logger.Entry) ([]byte, error) } @@ -32,25 +33,28 @@ const ( type NoFormatter struct{} +func (f NoFormatter) SetMetadataFlags(flags MetadataFlags) {} + func (f NoFormatter) Format(e *logger.Entry) ([]byte, error) { return []byte(e.Message), nil } type HumanFormatter struct { - NoMetadata bool + metadataFlags MetadataFlags } -var _ SetNoMetadataFormatter = &HumanFormatter{} - -func (f *HumanFormatter) SetNoMetadata(noMetadata bool) { - f.NoMetadata = noMetadata +func (f *HumanFormatter) SetMetadataFlags(flags MetadataFlags) { + f.metadataFlags = flags } func (f *HumanFormatter) Format(e *logger.Entry) (out []byte, err error) { var line bytes.Buffer - if !f.NoMetadata { + if f.metadataFlags&MetadataTime != 0 { + fmt.Fprintf(&line, "%s ", e.Time.Format(time.RFC3339)) + } + if f.metadataFlags&MetadataLevel != 0 { fmt.Fprintf(&line, "[%s]", e.Level.Short()) } @@ -100,7 +104,13 @@ func (f *HumanFormatter) Format(e *logger.Entry) (out []byte, err error) { return line.Bytes(), nil } -type JSONFormatter struct{} +type JSONFormatter struct { + metadataFlags MetadataFlags +} + +func (f *JSONFormatter) SetMetadataFlags(flags MetadataFlags) { + f.metadataFlags = flags +} func (f *JSONFormatter) Format(e *logger.Entry) ([]byte, error) { data := make(logger.Fields, len(e.Fields)+3) @@ -128,21 +138,21 @@ func (f *JSONFormatter) Format(e *logger.Entry) ([]byte, error) { } type LogfmtFormatter struct { - NoMetadata bool + metadataFlags MetadataFlags } -var _ SetNoMetadataFormatter = &LogfmtFormatter{} - -func (f *LogfmtFormatter) SetNoMetadata(noMetadata bool) { - f.NoMetadata = noMetadata +func (f *LogfmtFormatter) SetMetadataFlags(flags MetadataFlags) { + f.metadataFlags = flags } func (f *LogfmtFormatter) Format(e *logger.Entry) ([]byte, error) { var buf bytes.Buffer enc := logfmt.NewEncoder(&buf) - if !f.NoMetadata { + if f.metadataFlags&MetadataTime != 0 { enc.EncodeKeyval(FieldTime, e.Time) + } + if f.metadataFlags&MetadataLevel != 0 { enc.EncodeKeyval(FieldLevel, e.Level) } diff --git a/docs/configuration/logging.rst b/docs/configuration/logging.rst index c430ffd..4196052 100644 --- a/docs/configuration/logging.rst +++ b/docs/configuration/logging.rst @@ -115,8 +115,11 @@ Formats - minimum :ref:`log level ` * - ``format`` - output :ref:`format ` + * - ``time`` + - always include time in output (``true`` or ``false``) Writes all log entries with minimum level ``level`` formatted by ``format`` to stdout. +If stdout is a tty, interactive usage is assumed and the current time is included in the output. Can only be specified once.