// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved. // Package log4go provides level-based and highly configurable logging. // // Enhanced Logging // // This is inspired by the logging functionality in Java. Essentially, you create a Logger // object and create output filters for it. You can send whatever you want to the Logger, // and it will filter that based on your settings and send it to the outputs. This way, you // can put as much debug code in your program as you want, and when you're done you can filter // out the mundane messages so only the important ones show up. // // Utility functions are provided to make life easier. Here is some example code to get started: // // log := log4go.NewLogger() // log.AddFilter("stdout", log4go.DEBUG, log4go.NewConsoleLogWriter()) // log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true)) // log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02")) // // The first two lines can be combined with the utility NewDefaultLogger: // // log := log4go.NewDefaultLogger(log4go.DEBUG) // log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true)) // log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02")) // // Usage notes: // - The ConsoleLogWriter does not display the source of the message to standard // output, but the FileLogWriter does. // - The utility functions (Info, Debug, Warn, etc) derive their source from the // calling function, and this incurs extra overhead. // // Changes from 2.0: // - The external interface has remained mostly stable, but a lot of the // internals have been changed, so if you depended on any of this or created // your own LogWriter, then you will probably have to update your code. In // particular, Logger is now a map and ConsoleLogWriter is now a channel // behind-the-scenes, and the LogWrite method no longer has return values. // // Future work: (please let me know if you think I should work on any of these particularly) // - Log file rotation // - Logging configuration files ala log4j // - Have the ability to remove filters? // - Have GetInfoChannel, GetDebugChannel, etc return a chan string that allows // for another method of logging // - Add an XML filter type package log4go import ( "errors" "fmt" "os" "runtime" "strings" "time" ) // Version information const ( L4G_VERSION = "log4go-v3.0.1" L4G_MAJOR = 3 L4G_MINOR = 0 L4G_BUILD = 1 ) /****** Constants ******/ // These are the integer logging levels used by the logger type Level int const ( FINEST Level = iota FINE DEBUG TRACE INFO WARNING ERROR CRITICAL ) // Logging level strings var ( levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"} ) func (l Level) String() string { if l < 0 || int(l) > len(levelStrings) { return "UNKNOWN" } return levelStrings[int(l)] } /****** Variables ******/ var ( // LogBufferLength specifies how many log messages a particular log4go // logger can buffer at a time before writing them. LogBufferLength = 32 ) /****** LogRecord ******/ // A LogRecord contains all of the pertinent information for each message type LogRecord struct { Level Level // The log level Created time.Time // The time at which the log message was created (nanoseconds) Source string // The message source Message string // The log message } /****** LogWriter ******/ // This is an interface for anything that should be able to write logs type LogWriter interface { // This will be called to log a LogRecord message. LogWrite(rec *LogRecord) // This should clean up anything lingering about the LogWriter, as it is called before // the LogWriter is removed. LogWrite should not be called after Close. Close() } /****** Logger ******/ // A Filter represents the log level below which no log records are written to // the associated LogWriter. type Filter struct { Level Level LogWriter } // A Logger represents a collection of Filters through which log messages are // written. type Logger map[string]*Filter // Create a new logger. // // DEPRECATED: Use make(Logger) instead. func NewLogger() Logger { os.Stderr.WriteString("warning: use of deprecated NewLogger\n") return make(Logger) } // Create a new logger with a "stdout" filter configured to send log messages at // or above lvl to standard output. // // DEPRECATED: use NewDefaultLogger instead. func NewConsoleLogger(lvl Level) Logger { os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n") return Logger{ "stdout": &Filter{lvl, NewConsoleLogWriter()}, } } // Create a new logger with a "stdout" filter configured to send log messages at // or above lvl to standard output. func NewDefaultLogger(lvl Level) Logger { return Logger{ "stdout": &Filter{lvl, NewConsoleLogWriter()}, } } // Closes all log writers in preparation for exiting the program or a // reconfiguration of logging. Calling this is not really imperative, unless // you want to guarantee that all log messages are written. Close removes // all filters (and thus all LogWriters) from the logger. func (log Logger) Close() { // Close all open loggers for name, filt := range log { filt.Close() delete(log, name) } } // Add a new LogWriter to the Logger which will only log messages at lvl or // higher. This function should not be called from multiple goroutines. // Returns the logger for chaining. func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger { log[name] = &Filter{lvl, writer} return log } /******* Logging *******/ // Send a formatted log message internally func (log Logger) intLogf(lvl Level, format string, args ...interface{}) { skip := true // Determine if any logging will be done for _, filt := range log { if lvl >= filt.Level { skip = false break } } if skip { return } // Determine caller func pc, _, lineno, ok := runtime.Caller(2) src := "" if ok { src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno) } msg := format if len(args) > 0 { msg = fmt.Sprintf(format, args...) } // Make the log record rec := &LogRecord{ Level: lvl, Created: time.Now(), Source: src, Message: msg, } // Dispatch the logs for _, filt := range log { if lvl < filt.Level { continue } filt.LogWrite(rec) } } // Send a closure log message internally func (log Logger) intLogc(lvl Level, closure func() string) { skip := true // Determine if any logging will be done for _, filt := range log { if lvl >= filt.Level { skip = false break } } if skip { return } // Determine caller func pc, _, lineno, ok := runtime.Caller(2) src := "" if ok { src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno) } // Make the log record rec := &LogRecord{ Level: lvl, Created: time.Now(), Source: src, Message: closure(), } // Dispatch the logs for _, filt := range log { if lvl < filt.Level { continue } filt.LogWrite(rec) } } // Send a log message with manual level, source, and message. func (log Logger) Log(lvl Level, source, message string) { skip := true // Determine if any logging will be done for _, filt := range log { if lvl >= filt.Level { skip = false break } } if skip { return } // Make the log record rec := &LogRecord{ Level: lvl, Created: time.Now(), Source: source, Message: message, } // Dispatch the logs for _, filt := range log { if lvl < filt.Level { continue } filt.LogWrite(rec) } } // Logf logs a formatted log message at the given log level, using the caller as // its source. func (log Logger) Logf(lvl Level, format string, args ...interface{}) { log.intLogf(lvl, format, args...) } // Logc logs a string returned by the closure at the given log level, using the caller as // its source. If no log message would be written, the closure is never called. func (log Logger) Logc(lvl Level, closure func() string) { log.intLogc(lvl, closure) } // Finest logs a message at the finest log level. // See Debug for an explanation of the arguments. func (log Logger) Finest(arg0 interface{}, args ...interface{}) { const ( lvl = FINEST ) switch first := arg0.(type) { case string: // Use the string as a format string log.intLogf(lvl, first, args...) case func() string: // Log the closure (no other arguments used) log.intLogc(lvl, first) default: // Build a format string so that it will be similar to Sprint log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) } } // Fine logs a message at the fine log level. // See Debug for an explanation of the arguments. func (log Logger) Fine(arg0 interface{}, args ...interface{}) { const ( lvl = FINE ) switch first := arg0.(type) { case string: // Use the string as a format string log.intLogf(lvl, first, args...) case func() string: // Log the closure (no other arguments used) log.intLogc(lvl, first) default: // Build a format string so that it will be similar to Sprint log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) } } // Debug is a utility method for debug log messages. // The behavior of Debug depends on the first argument: // - arg0 is a string // When given a string as the first argument, this behaves like Logf but with // the DEBUG log level: the first argument is interpreted as a format for the // latter arguments. // - arg0 is a func()string // When given a closure of type func()string, this logs the string returned by // the closure iff it will be logged. The closure runs at most one time. // - arg0 is interface{} // When given anything else, the log message will be each of the arguments // formatted with %v and separated by spaces (ala Sprint). func (log Logger) Debug(arg0 interface{}, args ...interface{}) { const ( lvl = DEBUG ) switch first := arg0.(type) { case string: // Use the string as a format string log.intLogf(lvl, first, args...) case func() string: // Log the closure (no other arguments used) log.intLogc(lvl, first) default: // Build a format string so that it will be similar to Sprint log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) } } // Trace logs a message at the trace log level. // See Debug for an explanation of the arguments. func (log Logger) Trace(arg0 interface{}, args ...interface{}) { const ( lvl = TRACE ) switch first := arg0.(type) { case string: // Use the string as a format string log.intLogf(lvl, first, args...) case func() string: // Log the closure (no other arguments used) log.intLogc(lvl, first) default: // Build a format string so that it will be similar to Sprint log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) } } // Info logs a message at the info log level. // See Debug for an explanation of the arguments. func (log Logger) Info(arg0 interface{}, args ...interface{}) { const ( lvl = INFO ) switch first := arg0.(type) { case string: // Use the string as a format string log.intLogf(lvl, first, args...) case func() string: // Log the closure (no other arguments used) log.intLogc(lvl, first) default: // Build a format string so that it will be similar to Sprint log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) } } // Warn logs a message at the warning log level and returns the formatted error. // At the warning level and higher, there is no performance benefit if the // message is not actually logged, because all formats are processed and all // closures are executed to format the error message. // See Debug for further explanation of the arguments. func (log Logger) Warn(arg0 interface{}, args ...interface{}) error { const ( lvl = WARNING ) var msg string switch first := arg0.(type) { case string: // Use the string as a format string msg = fmt.Sprintf(first, args...) case func() string: // Log the closure (no other arguments used) msg = first() default: // Build a format string so that it will be similar to Sprint msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) } log.intLogf(lvl, msg) return errors.New(msg) } // Error logs a message at the error log level and returns the formatted error, // See Warn for an explanation of the performance and Debug for an explanation // of the parameters. func (log Logger) Error(arg0 interface{}, args ...interface{}) error { const ( lvl = ERROR ) var msg string switch first := arg0.(type) { case string: // Use the string as a format string msg = fmt.Sprintf(first, args...) case func() string: // Log the closure (no other arguments used) msg = first() default: // Build a format string so that it will be similar to Sprint msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) } log.intLogf(lvl, msg) return errors.New(msg) } // Critical logs a message at the critical log level and returns the formatted error, // See Warn for an explanation of the performance and Debug for an explanation // of the parameters. func (log Logger) Critical(arg0 interface{}, args ...interface{}) error { const ( lvl = CRITICAL ) var msg string switch first := arg0.(type) { case string: // Use the string as a format string msg = fmt.Sprintf(first, args...) case func() string: // Log the closure (no other arguments used) msg = first() default: // Build a format string so that it will be similar to Sprint msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) } log.intLogf(lvl, msg) return errors.New(msg) }