// Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. package log import ( "encoding/json" "fmt" "log" "os" "runtime/debug" "strings" "sync" "time" "qoobing.com/gomod/str" ) const ( PANIC LogLevel = 0 FATAL LogLevel = 1 ERROR LogLevel = 2 NOTICE LogLevel = 3 WARNING LogLevel = 4 INFO LogLevel = 5 DEBUG LogLevel = 9 ErrLogLevel = "~~~~~invalid loglevel~~~~~" ErrLogPanic = "~~~~panic~~~~~~" ) var ( mapStrToLevel = map[string]LogLevel{ "PANIC": PANIC, "FATAL": FATAL, "ERROR": ERROR, "NOTICE": NOTICE, "WARNING": WARNING, "INFO": INFO, "DEBUG": DEBUG, } mapLevelToStr = map[LogLevel]string{ PANIC: "PANIC", FATAL: "FATAL", ERROR: "ERROR", NOTICE: "NOTICE", WARNING: "WARNING", INFO: "INFO", DEBUG: "DEBUG", } ) var ( golog *log.Logger = nil mylog *Logger = nil logidCreator LogidCreator = nil modulelogs map[string]*Logger = map[string]*Logger{} ) type LogLevel int type Logger struct { module string golog *log.Logger loglevel LogLevel } type LogidCreator interface { Cleanup() GetLogid() string SetLogid(logid string) } func New(module string) *Logger { if module == "" { module = "undefine" } if _, ok := modulelogs[module]; ok { return modulelogs[module] } else { modulelogs[module] = &Logger{ module: module, golog: golog, loglevel: DEBUG, } return modulelogs[module] } } // SetLogLevel set new log level func (log *Logger) SetLogLevel(newlv LogLevel) (oldlv LogLevel) { oldlv = log.loglevel log.loglevel = newlv return oldlv } // SetLogLevelByName set new log level by level name func (log *Logger) SetLogLevelByName(newlv string) (oldlv string) { if newlevel, ok := mapStrToLevel[newlv]; ok { oldlevel := log.loglevel log.loglevel = newlevel return mapLevelToStr[oldlevel] } return ErrLogLevel } func (log *Logger) Print(prefix string, v interface{}) { var str = "" if pkg, err := json.Marshal(v); err != nil { str = err.Error() } else { str = string(pkg) } log.logwrite(DEBUG, 3, prefix+"%s\n", str) } func (log *Logger) PrintPretty(prefix string, v interface{}) { var preety = "" if pkg, err := json.MarshalIndent(v, "==", " "); err != nil { preety = err.Error() } else { preety = string(pkg) } log.logwrite(DEBUG, 3, prefix+"%s\n", preety) } func (log *Logger) Debugf(format string, v ...interface{}) { log.logwrite(DEBUG, 3, format, v...) } func (log *Logger) DebugfWithDepth(calldepth int, format string, v ...interface{}) { calldepth += 3 log.logwrite(DEBUG, calldepth, format, v...) } func (log *Logger) Noticef(format string, v ...interface{}) { log.logwrite(NOTICE, 3, format, v...) } func (log *Logger) NoticefWithDepth(calldepth int, format string, v ...interface{}) { calldepth += 3 log.logwrite(NOTICE, calldepth, format, v...) } func (log *Logger) Warningf(format string, v ...interface{}) { log.logwrite(WARNING, 3, format, v...) } func (log *Logger) Infof(format string, v ...interface{}) { log.logwrite(INFO, 3, format, v...) } func (log *Logger) Errorf(format string, v ...interface{}) { log.logwrite(ERROR, 3, format, v...) } func (log *Logger) Fatalf(format string, v ...interface{}) { log.logwrite(FATAL, 3, format, v...) } func (log *Logger) Panicf(format string, v ...interface{}) { log.logwrite(PANIC, 3, format, v...) } func (log *Logger) GetLogidStr(format string) string { if logidCreator != nil { return fmt.Sprintf(format, logidCreator.GetLogid()) } return "" } func (log *Logger) logwrite(typ LogLevel, calldepth int, format string, v ...interface{}) { if typ > log.loglevel { return } var ( idstr = log.GetLogidStr("[%s] ") prestr = idstr // + "[" + log.module + "] " ) format = strings.Trim(format, "\n") switch typ { case PANIC: log.golog.SetPrefix("\x1b[31m" + "PANI ") case FATAL: log.golog.SetPrefix("\x1b[31m" + "FATA ") case WARNING: log.golog.SetPrefix("\x1b[32m" + "WARN ") case ERROR: log.golog.SetPrefix("\x1b[32m" + "ERRO ") case INFO: log.golog.SetPrefix("INFO ") case NOTICE: log.golog.SetPrefix("NOTI ") case DEBUG: log.golog.SetPrefix("DBUG ") default: log.golog.SetPrefix("UNKN ") } if mylog == log { calldepth = calldepth + 1 } if typ == FATAL || typ == WARNING || typ == ERROR { log.golog.Output(calldepth, prestr+fmt.Sprintf(format+"\x1b[0m\n", v...)) } else if typ == NOTICE { calldepth = calldepth + 2 log.golog.Output(calldepth, prestr+fmt.Sprintf(format+"\n", v...)) } else if typ == PANIC { stack := strings.Replace(string(debug.Stack()), "\n", "\n== ", -1) stack = str.SkipLine(stack, calldepth*2+1) v = append(v, stack) panicstr := fmt.Sprintf(prestr+format+"\x1b[0m. Panic stack:\n%s\n", v...) log.golog.Output(calldepth, panicstr) panic(ErrLogPanic) } else { log.golog.Output(calldepth, prestr+fmt.Sprintf(format+"\n", v...)) } } func needWriteLogToFile() bool { if cls, ok := os.LookupEnv("QOOLOG_CLOSE_FILELOG"); ok && cls != "N" && cls != "n" { return false } else if ink8s, ok := os.LookupEnv("KUBERNETES_PORT"); ok && ink8s != "" { return false } return true } // ////////////////////////////////////////////////////////////////////////////////// // ///////////////////////Initialize///////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////////// var once sync.Once = sync.Once{} // init func init() { once.Do(initlog) } // initlog func initlog() { // Step 1. create base writer & cache writer for ALL log var ( logDir = "./logs/" logName = getExeFilename() logDirsOptional = []string{"./log", "/var/log"} logSplitTag = "20060102" //20060102150405 baseWriter = NewBaseWriter(nil, os.Stdout, nil) gologFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile ) // Step 2. init golog(golang std logger) & mylog(myself defined logger) golog = log.New(NewCacheWriter(baseWriter), "", gologFlags) mylog = New("system") // Step 3. backgroud split log (log rotate) if need if needWriteLogToFile() { go func() { baseWriter.OpenLogFile(logDir, logName, logDirsOptional) for { tag := time.Now().Local().Format(logSplitTag) baseWriter.TryBackupLogFile(tag) time.Sleep(2 * time.Minute) } }() } }