123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- /*
- * Public Domain Software
- *
- * I (Matthias Ladkau) am the author of the source code in this file.
- * I have placed the source code in this file in the public domain.
- *
- * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
- */
- /*
- Package logutil contains a simple leveled logging infrastructure supporting
- different log levels, package scopes, formatters and handlers.
- The main object is the Logger object which requires a scope. Use
- GetLogger(scope string) to get an instance. Log messages are published
- by various log methods (e.g. Info).
- The logger object is also used to add sinks which consume log messages.
- Each sinks requires a formatter which formats / decorades incoming log
- messages. Log messages are handled by the most specific scoped sinks which
- allow the message level.
- Example:
- logger = GetLogger("foo.bar")
- logger.AddLogSink(Info, SimpleFormatter(), myLogFile)
- logger.Info("A log message")
- */
- package logutil
- import (
- "fmt"
- "io"
- "log"
- "runtime/debug"
- "sort"
- "strings"
- "sync"
- )
- /*
- fallbackLogger is used if there are error during regular logging
- */
- var fallbackLogger = log.Print
- /*
- Level represents a logging level
- */
- type Level string
- /*
- Log levels
- */
- const (
- Debug Level = "Debug"
- Info = "Info"
- Warning = "Warning"
- Error = "Error"
- )
- /*
- LogLevelPriority is a map assigning priorities to log level (lower number means a higher priority)
- */
- var logLevelPriority = map[Level]int{
- Debug: 1,
- Info: 2,
- Warning: 3,
- Error: 4,
- }
- /*
- stringToLoglevel is a map assigning log levels to strings.
- */
- var stringToLoglevel = map[string]Level{
- strings.ToLower(fmt.Sprint(Debug)): Debug,
- strings.ToLower(fmt.Sprint(Info)): Info,
- strings.ToLower(fmt.Sprint(Warning)): Warning,
- strings.ToLower(fmt.Sprint(Error)): Error,
- }
- /*
- StringToLoglevel tries to turn a given string into a log level.
- */
- func StringToLoglevel(loglevelString string) Level {
- level, _ := stringToLoglevel[strings.ToLower(loglevelString)]
- return level
- }
- /*
- Logger is the main logging object which is used to add sinks and publish
- log messages. A log messages is only handled by the most appropriate sink
- in terms of level and scope. Multiple sinks can be registered for the same
- level and scope.
- */
- type Logger interface {
- /*
- AddLogSink adds a log sink to a logger. A log sink can be a file or console
- which satisfies the io.Writer interface.
- */
- AddLogSink(loglevel Level, formatter Formatter, appender io.Writer)
- /*
- Debug logs a message at debug level.
- */
- Debug(msg ...interface{})
- /*
- Info logs a message at info level.
- */
- Info(msg ...interface{})
- /*
- Warning logs a message at warning level.
- */
- Warning(msg ...interface{})
- /*
- Error logs a message at error level.
- */
- Error(msg ...interface{})
- /*
- Error logs a message at error level and a stacktrace.
- */
- LogStackTrace(loglevel Level, msg ...interface{})
- }
- /*
- GetLogger returns a logger of a certain scope. Use the empty string '' for the
- root scope.
- */
- func GetLogger(scope string) Logger {
- return &logger{scope}
- }
- /*
- ClearLogSinks removes all configured log sinks.
- */
- func ClearLogSinks() {
- logSinksLock.Lock()
- defer logSinksLock.Unlock()
- logSinks = make([][]*logSink, 0)
- }
- /*
- logger is the main Logger interface implementation.
- */
- type logger struct {
- scope string
- }
- /*
- AddLogSink adds a log sink to a logger. A log sink can be a file or console
- which satisfies the io.Writer interface.
- */
- func (l *logger) AddLogSink(loglevel Level, formatter Formatter, appender io.Writer) {
- addLogSink(loglevel, l.scope, formatter, appender)
- }
- /*
- Debug logs a message at debug level.
- */
- func (l *logger) Debug(msg ...interface{}) {
- publishLog(Debug, l.scope, msg...)
- }
- /*
- Info logs a message at info level.
- */
- func (l *logger) Info(msg ...interface{}) {
- publishLog(Info, l.scope, msg...)
- }
- /*
- Warning logs a message at warning level.
- */
- func (l *logger) Warning(msg ...interface{}) {
- publishLog(Warning, l.scope, msg...)
- }
- /*
- Error logs a message at error level.
- */
- func (l *logger) Error(msg ...interface{}) {
- publishLog(Error, l.scope, msg...)
- }
- /*
- Error logs a message at error level and a stacktrace.
- */
- func (l *logger) LogStackTrace(loglevel Level, msg ...interface{}) {
- msg = append(msg, fmt.Sprintln())
- msg = append(msg, string(debug.Stack()))
- publishLog(loglevel, l.scope, msg...)
- }
- // Singleton logger
- // ================
- /*
- logSink models a single log sink.
- */
- type logSink struct {
- io.Writer
- level Level
- scope string
- formatter Formatter
- }
- /*
- Implementation of sort interface for logSinks
- */
- type sinkSlice [][]*logSink
- func (p sinkSlice) Len() int { return len(p) }
- func (p sinkSlice) Less(i, j int) bool { return p[i][0].scope > p[j][0].scope }
- func (p sinkSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
- /*
- logSinks contains all registered log sinks.
- */
- var logSinks = make([][]*logSink, 0)
- var logSinksLock = sync.RWMutex{}
- /*
- addLogSink adds a new logging sink.
- */
- func addLogSink(level Level, scope string, formatter Formatter, sink io.Writer) {
- logSinksLock.Lock()
- defer logSinksLock.Unlock()
- // First see if the new sink can be appended to an existing list
- for i, scopeSinks := range logSinks {
- if scopeSinks[0].scope == scope {
- scopeSinks = append(scopeSinks, &logSink{sink, level, scope, formatter})
- logSinks[i] = scopeSinks
- return
- }
- }
- // Insert the new sink in the appropriate place
- logSinks = append(logSinks, []*logSink{{sink, level, scope, formatter}})
- sort.Sort(sinkSlice(logSinks))
- }
- /*
- publishLog publishes a log message.
- */
- func publishLog(loglevel Level, scope string, msg ...interface{}) {
- // Go through the sorted list of sinks
- for _, sinks := range logSinks {
- // Check if the log scope is within the message scope
- if strings.HasPrefix(scope, sinks[0].scope) {
- handled := false
- for _, sink := range sinks {
- // Check if the level is ok
- if logLevelPriority[sink.level] <= logLevelPriority[loglevel] {
- handled = true
- fmsg := sink.formatter.Format(loglevel, scope, msg...)
- if _, err := sink.Write([]byte(fmsg)); err != nil {
- // Something went wrong use the fallback logger
- fallbackLogger(fmt.Sprintf(
- "Cloud not publish log message: %v (message: %v)",
- err, fmsg))
- }
- }
- }
- if handled {
- return
- }
- }
- }
- // No handler for log message use the fallback logger
- fmsg := SimpleFormatter().Format(loglevel, scope, msg...)
- fallbackLogger(fmt.Sprintf("No log handler for log message: %v", fmsg))
- }
|