logging/facility.go (110 lines of code) (raw):
// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package logging
import (
"fmt"
"sync"
"github.com/uber-common/bark"
)
// Facility is a collection of named loggers that can be configured
// individually.
type Facility struct {
logger bark.Logger
levels map[string]Level
mu sync.RWMutex
}
// NewFacility creates a new log facility with the specified logger as the
// underlying logger. If no logger is passed, a no-op implementation is used.
func NewFacility(log bark.Logger) *Facility {
if log == nil {
log = NoLogger
}
return &Facility{
logger: log,
levels: make(map[string]Level),
}
}
// SetLevels is like SetLevel but for multiple named loggers.
func (f *Facility) SetLevels(levels map[string]Level) error {
for logName, level := range levels {
if level < Fatal {
return fmt.Errorf("cannot set a level above %s for %s", Fatal, logName)
}
}
// Prevent changing levels while a message is logged.
f.mu.Lock()
defer f.mu.Unlock()
for logName, level := range levels {
f.levels[logName] = level
}
return nil
}
// SetLevel sets the minimum severity level for a named logger. All messages
// produced by a named logger with a severity lower than the one set here are
// silenced.
// In most logger implementations, both Fatal and Panic stop the execution.
// This means that those levels only produce a single, and often important,
// message. Because of that, it's an error to pass a level above Fatal to this
// method.
func (f *Facility) SetLevel(logName string, level Level) error {
if level < Fatal {
return fmt.Errorf("cannot set a level above %s for %s", Fatal, logName)
}
// Prevent changing levels while a message is logged.
f.mu.Lock()
defer f.mu.Unlock()
f.levels[logName] = level
return nil
}
// SetLogger sets the underlying logger. All log messages produced, that are
// not silenced, are propagated to this logger.
func (f *Facility) SetLogger(log bark.Logger) {
// Prevent changing the logger while a message is logged.
f.mu.Lock()
defer f.mu.Unlock()
if log == nil {
log = NoLogger
}
f.logger = log
}
// Logger returns a new named logger.
func (f *Facility) Logger(logName string) bark.Logger {
return &namedLogger{
name: logName,
forwardTo: f,
}
}
// Log logs messages with a severity level equal to or higher than the one set
// with SetLevel. If that's not the case, the message is silenced.
// If the logName was not previously configured with SetLevel, the messages are
// never silenced.
// Instead on using this method directly, one can call Logger method to get a
// bark.Logger instance bound to a specific name.
func (f *Facility) Log(logName string, wantLevel Level, fields bark.Fields, msg []interface{}) {
f.mu.RLock()
defer f.mu.RUnlock()
if setLevel, ok := f.levels[logName]; ok {
// For me this is confusing, so here's a clarification.
// The levels are consecutive integers starting from 0 defined in this order:
// Panic=0, Fatal=1, Error=2, Warning=3, etc...
// For the condition to make more sense, consider a named
// logger set to Fatal and an incoming Error message.
if setLevel < wantLevel {
return
}
}
logger := f.logger
// If there are any fields, apply them.
if len(fields) > 0 {
logger = logger.WithFields(fields)
}
switch wantLevel {
case Debug:
logger.Debug(msg...)
case Info:
logger.Info(msg...)
case Warn:
logger.Warn(msg...)
case Error:
logger.Error(msg...)
case Fatal:
logger.Fatal(msg...)
case Panic:
logger.Panic(msg...)
}
}
// Logf is the same as Log but with fmt.Printf-like formatting
func (f *Facility) Logf(logName string, wantLevel Level, fields bark.Fields, format string, msg []interface{}) {
f.mu.RLock()
defer f.mu.RUnlock()
if setLevel, ok := f.levels[logName]; ok {
if setLevel < wantLevel {
return
}
}
logger := f.logger
if len(fields) > 0 {
logger = logger.WithFields(fields)
}
switch wantLevel {
case Debug:
logger.Debugf(format, msg...)
case Info:
logger.Infof(format, msg...)
case Warn:
logger.Warnf(format, msg...)
case Error:
logger.Errorf(format, msg...)
case Fatal:
logger.Fatalf(format, msg...)
case Panic:
logger.Panicf(format, msg...)
}
}