package ripsawlogger import ( "fmt" "io/ioutil" "os" "time" ) const ( LOG_INFO = "info" LOG_WARN = "warn" LOG_ERROR = "error" LOG_AUDIT = "audit" LOG_ACCESS = "access" ) var ( levels = []string{LOG_INFO, LOG_WARN, LOG_ERROR, LOG_AUDIT, LOG_ACCESS} defaultLogger Logger = new(multiLogger) // a list of loggers to send each log entry to defaults = true // whether to append default data to logs hostname, _ = os.Hostname() ) // A LogEntry represents a single line of data destined for a log file and is a simple // JSON-like map[string]interface{}. May include any arbitrary data. type LogEntry struct { Time time.Time Level string Contents map[string]interface{} } // The Logger interface is implemented by logging targets, which direct the log data to // an appopriate place. type Logger interface { Log(data LogEntry) Flush() } type multiLogger []Logger func (ml multiLogger) Log(data LogEntry) { // wrap in default properties if defaults == true { data.Contents["_hostname"] = hostname data.Contents["_language"] = "golang" } for _, logger := range ml { logger.Log(data) } } func (ml multiLogger) Flush() { for _, logger := range ml { logger.Flush() } } type filteredLogger struct { logger Logger levels []string } func NewFilteredLogger(log Logger, levels ...string) filteredLogger { return filteredLogger{ logger: log, levels: levels, } } func (l filteredLogger) Log(data LogEntry) { for _, v := range l.levels { if data.Level == v { l.logger.Log(data) } } } func (l filteredLogger) Flush() { // nothing } func NewStandardLogger() Logger { return multiLogger([]Logger{ NewFilteredLogger(NewWriterLogger(os.Stdout), LOG_INFO, LOG_WARN, LOG_ERROR), NewFilteredLogger(NewWriterLogger(ioutil.Discard), LOG_AUDIT, LOG_ACCESS), }) // return NewWriterLogger(map[string]io.Writer{ // LOG_INFO: os.Stdout, // LOG_WARN: os.Stdout, // LOG_ERROR: os.Stderr, // LOG_AUDIT: ioutil.Discard, // LOG_ACCESS: ioutil.Discard, // }) } func Configure(loggers []Logger) { logger := multiLogger(loggers) defaultLogger = &logger } func EnableDefaults(def bool) { defaults = def } func Info(args ...interface{}) { log(newLogEntry(LOG_INFO, args...)) } func Warn(args ...interface{}) { log(newLogEntry(LOG_WARN, args...)) } func Error(args ...interface{}) { log(newLogEntry(LOG_ERROR, args...)) } func Audit(args ...interface{}) { log(newLogEntry(LOG_AUDIT, args...)) } func Flush() { defaultLogger.Flush() } func getDefaultLogger() Logger { return defaultLogger } func log(entry LogEntry) { defaultLogger.Log(entry) } // formats generic interface{} arguments into a map[string]interface{} that // can be cleanly converted to JSON and logged. func newLogEntry(level string, args ...interface{}) LogEntry { baseObject := make(map[string]interface{}, 0) var logTime time.Time if len(args) > 0 { var message string // if the first argument is a time.Time object, use it as the message time if time, ok := args[0].(time.Time); ok { logTime = time args = args[1:] } // if the final argument is a map, use that if base, ok := args[len(args)-1].(map[string]interface{}); ok { baseObject = base args = args[:len(args)-1] } // if we still have any, find a way to messagize them if len(args) > 0 { if str, ok := args[0].(string); ok && str != "" { message = fmt.Sprintf(str, args[1:]...) } else { message = fmt.Sprint(args...) } } if message != "" { baseObject["message"] = message } } return LogEntry{ Time: logTime, Level: level, Contents: baseObject, } }