diff --git a/cmd/config_logging.go b/cmd/config_logging.go index a670cba..5566bf6 100644 --- a/cmd/config_logging.go +++ b/cmd/config_logging.go @@ -13,7 +13,7 @@ import ( ) type LoggingConfig struct { - Outlets *logger.Outlets + Outlets logger.Outlets } type MetadataFlags int64 @@ -26,10 +26,9 @@ const ( MetadataAll MetadataFlags = ^0 ) -func LoggerErrorOutlet() logger.Outlet { - +func StdoutOutlet() logger.Outlet { formatter := &HumanFormatter{} - writer := os.Stderr + writer := os.Stdout formatter.SetMetadataFlags(MetadataAll) return WriterOutlet{ formatter, writer, @@ -39,8 +38,7 @@ func LoggerErrorOutlet() logger.Outlet { func parseLogging(i interface{}) (c *LoggingConfig, err error) { c = &LoggingConfig{} - - c.Outlets = logger.NewOutlets(LoggerErrorOutlet()) + c.Outlets = logger.NewOutlets() var asList []interface{} if err = mapstructure.Decode(i, &asList); err != nil { diff --git a/cmd/test.go b/cmd/test.go index 6258b67..148719b 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -65,7 +65,7 @@ func init() { func testCmdGlobalInit(cmd *cobra.Command, args []string) { - out := logger.NewOutlets(WriterOutlet{&NoFormatter{}, os.Stderr}) + out := logger.NewOutlets() out.Add(WriterOutlet{&NoFormatter{}, os.Stdout}, logger.Info) log := logger.NewLogger(out, 1*time.Second) testCmdGlobal.log = log diff --git a/docs/configuration/logging.rst b/docs/configuration/logging.rst index 4196052..da2b46e 100644 --- a/docs/configuration/logging.rst +++ b/docs/configuration/logging.rst @@ -27,6 +27,10 @@ Check out :sampleconf:`random/logging.yml` for an example on how to configure mu jobs: ... +.. ATTENTION:: + The **first outlet is special**: if an error writing to any outlet occurs, the first outlet receives the error and can print it. + Thus, the first outlet must be the one that always works and does not block, e.g. ``stdout``, which is the default. + Default Configuration --------------------- @@ -41,10 +45,6 @@ By default, the following logging configuration is used level: "warn" format: "human" -.. ATTENTION:: - Output to **stderr** should always be considered a **critical error**. - Only errors in the logging infrastructure itself, e.g. IO errors when writing to an outlet, are sent to stderr. - Building Blocks --------------- diff --git a/logger/datastructures.go b/logger/datastructures.go index b3bad8c..a5c0f23 100644 --- a/logger/datastructures.go +++ b/logger/datastructures.go @@ -78,28 +78,32 @@ type Outlet interface { WriteEntry(ctx context.Context, entry Entry) error } -type Outlets struct { - reg map[Level][]Outlet - e Outlet +type Outlets map[Level][]Outlet + +func NewOutlets() Outlets { + return make(map[Level][]Outlet, len(AllLevels)) } -func NewOutlets(loggerErrorOutlet Outlet) *Outlets { - return &Outlets{ - make(map[Level][]Outlet, len(AllLevels)), - loggerErrorOutlet, - } -} - -func (os *Outlets) Add(outlet Outlet, minLevel Level) { +func (os Outlets) Add(outlet Outlet, minLevel Level) { for _, l := range AllLevels[minLevel:] { - os.reg[l] = append(os.reg[l], outlet) + os[l] = append(os[l], outlet) } } -func (os *Outlets) Get(level Level) []Outlet { - return os.reg[level] +func (os Outlets) Get(level Level) []Outlet { + return os[level] } -func (os *Outlets) GetLoggerErrorOutlet() Outlet { - return os.e +// Return the first outlet added to this Outlets list using Add() +// with minLevel <= Error. +// If no such outlet is in this Outlets list, a discarding outlet is returned. +func (os Outlets) GetLoggerErrorOutlet() Outlet { + if len(os[Error]) < 1 { + return nullOutlet{} + } + return os[Error][0] } + +type nullOutlet struct{} + +func (nullOutlet) WriteEntry(ctx context.Context, entry Entry) error { return nil } diff --git a/logger/logger.go b/logger/logger.go index 10da381..560f117 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -17,13 +17,13 @@ const DefaultUserFieldCapacity = 5 type Logger struct { fields Fields - outlets *Outlets + outlets Outlets outletTimeout time.Duration mtx *sync.Mutex } -func NewLogger(outlets *Outlets, outletTimeout time.Duration) *Logger { +func NewLogger(outlets Outlets, outletTimeout time.Duration) *Logger { return &Logger{ make(Fields, DefaultUserFieldCapacity), outlets,