272 lines
8.4 KiB
Go
272 lines
8.4 KiB
Go
// Copyright (C) 2014 Space Monkey, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package spacelog
|
|
|
|
import (
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
)
|
|
|
|
var (
|
|
// If set, these prefixes will be stripped out of automatic logger names.
|
|
IgnoredPrefixes []string
|
|
|
|
badChars = regexp.MustCompile("[^a-zA-Z0-9_.-]")
|
|
slashes = regexp.MustCompile("[/]")
|
|
)
|
|
|
|
func callerName() string {
|
|
pc, _, _, ok := runtime.Caller(2)
|
|
if !ok {
|
|
return "unknown.unknown"
|
|
}
|
|
f := runtime.FuncForPC(pc)
|
|
if f == nil {
|
|
return "unknown.unknown"
|
|
}
|
|
name := f.Name()
|
|
for _, prefix := range IgnoredPrefixes {
|
|
name = strings.TrimPrefix(name, prefix)
|
|
}
|
|
return badChars.ReplaceAllLiteralString(
|
|
slashes.ReplaceAllLiteralString(name, "."), "_")
|
|
}
|
|
|
|
// LoggerCollections contain all of the loggers a program might use. Typically
|
|
// a codebase will just use the default logger collection.
|
|
type LoggerCollection struct {
|
|
mtx sync.Mutex
|
|
loggers map[string]*Logger
|
|
level LogLevel
|
|
handler Handler
|
|
}
|
|
|
|
// NewLoggerCollection creates a new logger collection. It's unlikely you will
|
|
// ever practically need this method. Use the DefaultLoggerCollection instead.
|
|
func NewLoggerCollection() *LoggerCollection {
|
|
return &LoggerCollection{
|
|
loggers: make(map[string]*Logger),
|
|
level: DefaultLevel,
|
|
handler: defaultHandler}
|
|
}
|
|
|
|
// GetLogger returns a new Logger with a name automatically generated using
|
|
// the callstack. If you want to avoid automatic name generation check out
|
|
// GetLoggerNamed
|
|
func (c *LoggerCollection) GetLogger() *Logger {
|
|
return c.GetLoggerNamed(callerName())
|
|
}
|
|
|
|
func (c *LoggerCollection) getLogger(name string, level LogLevel,
|
|
handler Handler) *Logger {
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
|
|
logger, exists := c.loggers[name]
|
|
if !exists {
|
|
logger = &Logger{level: level,
|
|
collection: c,
|
|
name: name,
|
|
handler: handler}
|
|
c.loggers[name] = logger
|
|
}
|
|
return logger
|
|
}
|
|
|
|
// ConfigureLoggers configures loggers according to the given string
|
|
// specification, which specifies a set of loggers and their associated
|
|
// logging levels. Loggers are semicolon-separated; each
|
|
// configuration is specified as <logger>=<level>. White space outside of
|
|
// logger names and levels is ignored. The default level is specified
|
|
// with the name "DEFAULT".
|
|
//
|
|
// An example specification:
|
|
// `DEFAULT=ERROR; foo.bar=WARNING`
|
|
func (c *LoggerCollection) ConfigureLoggers(specification string) error {
|
|
confs := strings.Split(strings.TrimSpace(specification), ";")
|
|
for i := range confs {
|
|
conf := strings.SplitN(confs[i], "=", 2)
|
|
levelstr := strings.TrimSpace(conf[1])
|
|
name := strings.TrimSpace(conf[0])
|
|
level, err := LevelFromString(levelstr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if name == "DEFAULT" {
|
|
c.SetLevel(nil, level)
|
|
continue
|
|
}
|
|
logger := c.GetLoggerNamed(name)
|
|
logger.setLevel(level)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetLoggerNamed returns a new Logger with the provided name. GetLogger is
|
|
// more frequently used.
|
|
func (c *LoggerCollection) GetLoggerNamed(name string) *Logger {
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
|
|
logger, exists := c.loggers[name]
|
|
if !exists {
|
|
logger = &Logger{level: c.level,
|
|
collection: c,
|
|
name: name,
|
|
handler: c.handler}
|
|
c.loggers[name] = logger
|
|
}
|
|
return logger
|
|
}
|
|
|
|
// SetLevel will set the current log level for all loggers with names that
|
|
// match a provided regular expression. If the regular expression is nil, then
|
|
// all loggers match.
|
|
func (c *LoggerCollection) SetLevel(re *regexp.Regexp, level LogLevel) {
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
|
|
if re == nil {
|
|
c.level = level
|
|
}
|
|
for name, logger := range c.loggers {
|
|
if re == nil || re.MatchString(name) {
|
|
logger.setLevel(level)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetHandler will set the current log handler for all loggers with names that
|
|
// match a provided regular expression. If the regular expression is nil, then
|
|
// all loggers match.
|
|
func (c *LoggerCollection) SetHandler(re *regexp.Regexp, handler Handler) {
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
|
|
if re == nil {
|
|
c.handler = handler
|
|
}
|
|
for name, logger := range c.loggers {
|
|
if re == nil || re.MatchString(name) {
|
|
logger.setHandler(handler)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetTextTemplate will set the current text template for all loggers with
|
|
// names that match a provided regular expression. If the regular expression
|
|
// is nil, then all loggers match. Note that not every handler is guaranteed
|
|
// to support text templates and a text template will only apply to
|
|
// text-oriented and unstructured handlers.
|
|
func (c *LoggerCollection) SetTextTemplate(re *regexp.Regexp,
|
|
t *template.Template) {
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
|
|
if re == nil {
|
|
c.handler.SetTextTemplate(t)
|
|
}
|
|
for name, logger := range c.loggers {
|
|
if re == nil || re.MatchString(name) {
|
|
logger.getHandler().SetTextTemplate(t)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetTextOutput will set the current output interface for all loggers with
|
|
// names that match a provided regular expression. If the regular expression
|
|
// is nil, then all loggers match. Note that not every handler is guaranteed
|
|
// to support text output and a text output interface will only apply to
|
|
// text-oriented and unstructured handlers.
|
|
func (c *LoggerCollection) SetTextOutput(re *regexp.Regexp,
|
|
output TextOutput) {
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
|
|
if re == nil {
|
|
c.handler.SetTextOutput(output)
|
|
}
|
|
for name, logger := range c.loggers {
|
|
if re == nil || re.MatchString(name) {
|
|
logger.getHandler().SetTextOutput(output)
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
// It's unlikely you'll need to use this directly
|
|
DefaultLoggerCollection = NewLoggerCollection()
|
|
)
|
|
|
|
// GetLogger returns an automatically-named logger on the default logger
|
|
// collection.
|
|
func GetLogger() *Logger {
|
|
return DefaultLoggerCollection.GetLoggerNamed(callerName())
|
|
}
|
|
|
|
// GetLoggerNamed returns a new Logger with the provided name on the default
|
|
// logger collection. GetLogger is more frequently used.
|
|
func GetLoggerNamed(name string) *Logger {
|
|
return DefaultLoggerCollection.GetLoggerNamed(name)
|
|
}
|
|
|
|
// ConfigureLoggers configures loggers according to the given string
|
|
// specification, which specifies a set of loggers and their associated
|
|
// logging levels. Loggers are colon- or semicolon-separated; each
|
|
// configuration is specified as <logger>=<level>. White space outside of
|
|
// logger names and levels is ignored. The DEFAULT module is specified
|
|
// with the name "DEFAULT".
|
|
//
|
|
// An example specification:
|
|
// `DEFAULT=ERROR; foo.bar=WARNING`
|
|
func ConfigureLoggers(specification string) error {
|
|
return DefaultLoggerCollection.ConfigureLoggers(specification)
|
|
}
|
|
|
|
// SetLevel will set the current log level for all loggers on the default
|
|
// collection with names that match a provided regular expression. If the
|
|
// regular expression is nil, then all loggers match.
|
|
func SetLevel(re *regexp.Regexp, level LogLevel) {
|
|
DefaultLoggerCollection.SetLevel(re, level)
|
|
}
|
|
|
|
// SetHandler will set the current log handler for all loggers on the default
|
|
// collection with names that match a provided regular expression. If the
|
|
// regular expression is nil, then all loggers match.
|
|
func SetHandler(re *regexp.Regexp, handler Handler) {
|
|
DefaultLoggerCollection.SetHandler(re, handler)
|
|
}
|
|
|
|
// SetTextTemplate will set the current text template for all loggers on the
|
|
// default collection with names that match a provided regular expression. If
|
|
// the regular expression is nil, then all loggers match. Note that not every
|
|
// handler is guaranteed to support text templates and a text template will
|
|
// only apply to text-oriented and unstructured handlers.
|
|
func SetTextTemplate(re *regexp.Regexp, t *template.Template) {
|
|
DefaultLoggerCollection.SetTextTemplate(re, t)
|
|
}
|
|
|
|
// SetTextOutput will set the current output interface for all loggers on the
|
|
// default collection with names that match a provided regular expression. If
|
|
// the regular expression is nil, then all loggers match. Note that not every
|
|
// handler is guaranteed to support text output and a text output interface
|
|
// will only apply to text-oriented and unstructured handlers.
|
|
func SetTextOutput(re *regexp.Regexp, output TextOutput) {
|
|
DefaultLoggerCollection.SetTextOutput(re, output)
|
|
}
|