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 {
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 {

View File

@ -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

View File

@ -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
---------------

View File

@ -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 }

View File

@ -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,