logging: stdout outlet: include time in output if tty or forced through config

This commit is contained in:
Christian Schwarz 2017-11-14 21:51:19 +01:00
parent ed68bffea5
commit 476348689a
4 changed files with 77 additions and 37 deletions

14
Gopkg.lock generated
View File

@ -55,6 +55,12 @@
packages = ["."] packages = ["."]
revision = "7cafcd837844e784b526369c9bce262804aebc60" revision = "7cafcd837844e784b526369c9bce262804aebc60"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/mitchellh/mapstructure" name = "github.com/mitchellh/mapstructure"
@ -91,9 +97,15 @@
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
version = "v1.1.4" version = "v1.1.4"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "bf42f188b9bc6f2cf5b8ee5a912ef1aedd0eba4c"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "d654c5e91ee04baf1a373bb241b94ab41d3838fb69a56e91fe54cc8b962eab56" inputs-digest = "b2174984fa0452d4469597063f891904b70081586b239262690cf1b6477a3116"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -3,6 +3,7 @@ package cmd
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"github.com/mattn/go-isatty"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/logger" "github.com/zrepl/zrepl/logger"
@ -15,14 +16,15 @@ type LoggingConfig struct {
Outlets logger.Outlets Outlets logger.Outlets
} }
type SetNoMetadataFormatter interface { type MetadataFlags int64
SetNoMetadata(noMetadata bool)
}
type OutletCommon struct { const (
MinLevel logger.Level MetadataTime MetadataFlags = 1 << iota
Formatter EntryFormatter MetadataLevel
}
MetadataNone MetadataFlags = 0
MetadataAll MetadataFlags = ^0
)
func parseLogging(i interface{}) (c *LoggingConfig, err error) { 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 return
} }
common := &OutletCommon{} minLevel, err := logger.ParseLevel(in.Level)
common.MinLevel, err = logger.ParseLevel(in.Level)
if err != nil { if err != nil {
err = errors.Wrap(err, "cannot parse 'level' field") err = errors.Wrap(err, "cannot parse 'level' field")
return return
} }
common.Formatter, err = parseLogFormat(in.Format) formatter, err := parseLogFormat(in.Format)
if err != nil { if err != nil {
err = errors.Wrap(err, "cannot parse") err = errors.Wrap(err, "cannot parse")
return return
@ -123,29 +124,45 @@ func parseOutlet(i interface{}) (o logger.Outlet, level logger.Level, err error)
switch in.Outlet { switch in.Outlet {
case "stdout": case "stdout":
o, err = parseStdoutOutlet(i, common) o, err = parseStdoutOutlet(i, formatter)
case "tcp": case "tcp":
o, err = parseTCPOutlet(i, common) o, err = parseTCPOutlet(i, formatter)
case "syslog": case "syslog":
o, err = parseSyslogOutlet(i, common) o, err = parseSyslogOutlet(i, formatter)
default: default:
err = errors.Errorf("unknown outlet type '%s'", in.Outlet) 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{ return WriterOutlet{
common.Formatter, formatter,
os.Stdout, os.Stdout,
}, nil }, nil
} }
func parseTCPOutlet(i interface{}, common *OutletCommon) (out *TCPOutlet, err error) { func parseTCPOutlet(i interface{}, formatter EntryFormatter) (out *TCPOutlet, err error) {
out = &TCPOutlet{} out = &TCPOutlet{}
out.Formatter = common.Formatter out.Formatter = formatter
out.Formatter.SetMetadataFlags(MetadataAll)
var in struct { var in struct {
Net string 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 { var in struct {
RetryInterval string `mapstructure:"retry_interval"` RetryInterval string `mapstructure:"retry_interval"`
@ -216,10 +233,8 @@ func parseSyslogOutlet(i interface{}, common *OutletCommon) (out *SyslogOutlet,
} }
out = &SyslogOutlet{} out = &SyslogOutlet{}
out.Formatter = common.Formatter out.Formatter = formatter
if f, ok := out.Formatter.(SetNoMetadataFormatter); ok { out.Formatter.SetMetadataFlags(MetadataNone)
f.SetNoMetadata(true)
}
out.RetryInterval = 0 // default to 0 as we assume local syslog will just work out.RetryInterval = 0 // default to 0 as we assume local syslog will just work
if in.RetryInterval != "" { if in.RetryInterval != "" {

View File

@ -11,6 +11,7 @@ import (
) )
type EntryFormatter interface { type EntryFormatter interface {
SetMetadataFlags(flags MetadataFlags)
Format(e *logger.Entry) ([]byte, error) Format(e *logger.Entry) ([]byte, error)
} }
@ -32,25 +33,28 @@ const (
type NoFormatter struct{} type NoFormatter struct{}
func (f NoFormatter) SetMetadataFlags(flags MetadataFlags) {}
func (f NoFormatter) Format(e *logger.Entry) ([]byte, error) { 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 metadataFlags MetadataFlags
} }
var _ SetNoMetadataFormatter = &HumanFormatter{} func (f *HumanFormatter) SetMetadataFlags(flags MetadataFlags) {
f.metadataFlags = flags
func (f *HumanFormatter) SetNoMetadata(noMetadata bool) {
f.NoMetadata = noMetadata
} }
func (f *HumanFormatter) Format(e *logger.Entry) (out []byte, err error) { func (f *HumanFormatter) Format(e *logger.Entry) (out []byte, err error) {
var line bytes.Buffer 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()) 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 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) { 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)
@ -128,21 +138,21 @@ func (f *JSONFormatter) Format(e *logger.Entry) ([]byte, error) {
} }
type LogfmtFormatter struct { type LogfmtFormatter struct {
NoMetadata bool metadataFlags MetadataFlags
} }
var _ SetNoMetadataFormatter = &LogfmtFormatter{} func (f *LogfmtFormatter) SetMetadataFlags(flags MetadataFlags) {
f.metadataFlags = flags
func (f *LogfmtFormatter) SetNoMetadata(noMetadata bool) {
f.NoMetadata = noMetadata
} }
func (f *LogfmtFormatter) Format(e *logger.Entry) ([]byte, error) { func (f *LogfmtFormatter) Format(e *logger.Entry) ([]byte, error) {
var buf bytes.Buffer var buf bytes.Buffer
enc := logfmt.NewEncoder(&buf) enc := logfmt.NewEncoder(&buf)
if !f.NoMetadata { if f.metadataFlags&MetadataTime != 0 {
enc.EncodeKeyval(FieldTime, e.Time) enc.EncodeKeyval(FieldTime, e.Time)
}
if f.metadataFlags&MetadataLevel != 0 {
enc.EncodeKeyval(FieldLevel, e.Level) enc.EncodeKeyval(FieldLevel, e.Level)
} }

View File

@ -115,8 +115,11 @@ Formats
- minimum :ref:`log level <logging-levels>` - minimum :ref:`log level <logging-levels>`
* - ``format`` * - ``format``
- output :ref:`format <logging-formats>` - output :ref:`format <logging-formats>`
* - ``time``
- always include time in output (``true`` or ``false``)
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.
If stdout is a tty, interactive usage is assumed and the current time is included in the output.
Can only be specified once. Can only be specified once.