logging: first outlet receives logger error message

Abandons stderr special-casing:

* looks weird on shell and IO redirection to same file because of
interleaving of stdout and stderr
* better than a separate dedicated outlet because it does not require
additional configuration

fixes #28

BREAK SEMANTICS CONFIG
This commit is contained in:
Christian Schwarz 2017-11-17 00:24:22 +01:00
parent a7f70a566d
commit 2bfcfa5be8
5 changed files with 31 additions and 29 deletions

View File

@ -13,7 +13,7 @@ import (
) )
type LoggingConfig struct { type LoggingConfig struct {
Outlets *logger.Outlets Outlets logger.Outlets
} }
type MetadataFlags int64 type MetadataFlags int64
@ -26,10 +26,9 @@ const (
MetadataAll MetadataFlags = ^0 MetadataAll MetadataFlags = ^0
) )
func LoggerErrorOutlet() logger.Outlet { func StdoutOutlet() logger.Outlet {
formatter := &HumanFormatter{} formatter := &HumanFormatter{}
writer := os.Stderr writer := os.Stdout
formatter.SetMetadataFlags(MetadataAll) formatter.SetMetadataFlags(MetadataAll)
return WriterOutlet{ return WriterOutlet{
formatter, writer, formatter, writer,
@ -39,8 +38,7 @@ func LoggerErrorOutlet() logger.Outlet {
func parseLogging(i interface{}) (c *LoggingConfig, err error) { func parseLogging(i interface{}) (c *LoggingConfig, err error) {
c = &LoggingConfig{} c = &LoggingConfig{}
c.Outlets = logger.NewOutlets()
c.Outlets = logger.NewOutlets(LoggerErrorOutlet())
var asList []interface{} var asList []interface{}
if err = mapstructure.Decode(i, &asList); err != nil { if err = mapstructure.Decode(i, &asList); err != nil {

View File

@ -65,7 +65,7 @@ func init() {
func testCmdGlobalInit(cmd *cobra.Command, args []string) { 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) 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

View File

@ -27,6 +27,10 @@ Check out :sampleconf:`random/logging.yml` for an example on how to configure mu
jobs: ... 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 Default Configuration
--------------------- ---------------------
@ -41,10 +45,6 @@ By default, the following logging configuration is used
level: "warn" level: "warn"
format: "human" 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 Building Blocks
--------------- ---------------

View File

@ -78,28 +78,32 @@ type Outlet interface {
WriteEntry(ctx context.Context, entry Entry) error WriteEntry(ctx context.Context, entry Entry) error
} }
type Outlets struct { type Outlets map[Level][]Outlet
reg map[Level][]Outlet
e Outlet func NewOutlets() Outlets {
return make(map[Level][]Outlet, len(AllLevels))
} }
func NewOutlets(loggerErrorOutlet Outlet) *Outlets { func (os Outlets) Add(outlet Outlet, minLevel Level) {
return &Outlets{
make(map[Level][]Outlet, len(AllLevels)),
loggerErrorOutlet,
}
}
func (os *Outlets) Add(outlet Outlet, minLevel Level) {
for _, l := range AllLevels[minLevel:] { 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 { func (os Outlets) Get(level Level) []Outlet {
return os.reg[level] return os[level]
} }
func (os *Outlets) GetLoggerErrorOutlet() Outlet { // Return the first outlet added to this Outlets list using Add()
return os.e // 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 }

View File

@ -17,13 +17,13 @@ const DefaultUserFieldCapacity = 5
type Logger struct { type Logger struct {
fields Fields fields Fields
outlets *Outlets outlets Outlets
outletTimeout time.Duration outletTimeout time.Duration
mtx *sync.Mutex mtx *sync.Mutex
} }
func NewLogger(outlets *Outlets, outletTimeout time.Duration) *Logger { func NewLogger(outlets Outlets, outletTimeout time.Duration) *Logger {
return &Logger{ return &Logger{
make(Fields, DefaultUserFieldCapacity), make(Fields, DefaultUserFieldCapacity),
outlets, outlets,