You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			351 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
		
		
			
		
	
	
			351 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
| 
											5 months ago
										 | package holmes | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"fmt" | ||
|  | 	"log" | ||
|  | 	"os" | ||
|  | 	"path" | ||
|  | 	"runtime" | ||
|  | 	"sync/atomic" | ||
|  | 	"time" | ||
|  | ) | ||
|  | 
 | ||
|  | // LogLevel is the log level type.
 | ||
|  | type LogLevel int | ||
|  | 
 | ||
|  | const ( | ||
|  | 	// DEBUG represents debug log level.
 | ||
|  | 	DEBUG LogLevel = iota | ||
|  | 	// INFO represents info log level.
 | ||
|  | 	INFO | ||
|  | 	// WARN represents warn log level.
 | ||
|  | 	WARN | ||
|  | 	// ERROR represents error log level.
 | ||
|  | 	ERROR | ||
|  | 	// FATAL represents fatal log level.
 | ||
|  | 	FATAL | ||
|  | ) | ||
|  | 
 | ||
|  | var ( | ||
|  | 	started        int32 | ||
|  | 	loggerInstance Logger | ||
|  | 	tagName        = map[LogLevel]string{ | ||
|  | 		DEBUG: "DEBUG", | ||
|  | 		INFO:  "INFO", | ||
|  | 		WARN:  "WARN", | ||
|  | 		ERROR: "ERROR", | ||
|  | 		FATAL: "FATAL", | ||
|  | 	} | ||
|  | ) | ||
|  | 
 | ||
|  | // Start returns a decorated innerLogger.
 | ||
|  | func Start(decorators ...func(Logger) Logger) Logger { | ||
|  | 	if atomic.CompareAndSwapInt32(&started, 0, 1) { | ||
|  | 		loggerInstance = Logger{} | ||
|  | 		for _, decorator := range decorators { | ||
|  | 			loggerInstance = decorator(loggerInstance) | ||
|  | 		} | ||
|  | 		var logger *log.Logger | ||
|  | 		var segment *logSegment | ||
|  | 		if loggerInstance.logPath != "" { | ||
|  | 			segment = newLogSegment(loggerInstance.unit, loggerInstance.logPath) | ||
|  | 		} | ||
|  | 		if segment != nil { | ||
|  | 			logger = log.New(segment, "", log.LstdFlags) | ||
|  | 		} else if loggerInstance.isStdout { | ||
|  | 			logger = log.New(os.Stdout, "", log.LstdFlags) | ||
|  | 		} else { | ||
|  | 			logger = log.New(os.Stderr, "", log.LstdFlags) | ||
|  | 		} | ||
|  | 		loggerInstance.logger = logger | ||
|  | 		return loggerInstance | ||
|  | 	} | ||
|  | 	panic("Start() already called") | ||
|  | } | ||
|  | 
 | ||
|  | // Stop stops the logger.
 | ||
|  | func (l Logger) Stop() { | ||
|  | 	if atomic.CompareAndSwapInt32(&l.stopped, 0, 1) { | ||
|  | 		if l.printStack { | ||
|  | 			traceInfo := make([]byte, 1<<16) | ||
|  | 			n := runtime.Stack(traceInfo, true) | ||
|  | 			l.logger.Printf("%s", traceInfo[:n]) | ||
|  | 			if l.isStdout { | ||
|  | 				log.Printf("%s", traceInfo[:n]) | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if l.segment != nil { | ||
|  | 			l.segment.Close() | ||
|  | 		} | ||
|  | 		l.segment = nil | ||
|  | 		l.logger = nil | ||
|  | 		atomic.StoreInt32(&started, 0) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // logSegment implements io.Writer
 | ||
|  | type logSegment struct { | ||
|  | 	unit         time.Duration | ||
|  | 	logPath      string | ||
|  | 	logFile      *os.File | ||
|  | 	timeToCreate <-chan time.Time | ||
|  | } | ||
|  | 
 | ||
|  | func newLogSegment(unit time.Duration, logPath string) *logSegment { | ||
|  | 	now := time.Now() | ||
|  | 	if logPath != "" { | ||
|  | 		err := os.MkdirAll(logPath, os.ModePerm) | ||
|  | 		if err != nil { | ||
|  | 			fmt.Fprintln(os.Stderr, err) | ||
|  | 			return nil | ||
|  | 		} | ||
|  | 		name := getLogFileName(time.Now()) | ||
|  | 		logFile, err := os.OpenFile(path.Join(logPath, name), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) | ||
|  | 		if err != nil { | ||
|  | 			if os.IsNotExist(err) { | ||
|  | 				logFile, err = os.Create(path.Join(logPath, name)) | ||
|  | 				if err != nil { | ||
|  | 					fmt.Fprintln(os.Stderr, err) | ||
|  | 					return nil | ||
|  | 				} | ||
|  | 			} else { | ||
|  | 				fmt.Fprintln(os.Stderr, err) | ||
|  | 				return nil | ||
|  | 			} | ||
|  | 		} | ||
|  | 		next := now.Truncate(unit).Add(unit) | ||
|  | 		var timeToCreate <-chan time.Time | ||
|  | 		if unit == time.Hour || unit == time.Minute { | ||
|  | 			timeToCreate = time.After(next.Sub(time.Now())) | ||
|  | 		} | ||
|  | 		return &logSegment{ | ||
|  | 			unit:         unit, | ||
|  | 			logPath:      logPath, | ||
|  | 			logFile:      logFile, | ||
|  | 			timeToCreate: timeToCreate, | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return nil | ||
|  | } | ||
|  | 
 | ||
|  | func (ls *logSegment) Write(p []byte) (n int, err error) { | ||
|  | 	if ls.timeToCreate != nil && ls.logFile != os.Stdout && ls.logFile != os.Stderr { | ||
|  | 		select { | ||
|  | 		case current := <-ls.timeToCreate: | ||
|  | 			ls.logFile.Close() | ||
|  | 			ls.logFile = nil | ||
|  | 			name := getLogFileName(current) | ||
|  | 			ls.logFile, err = os.Create(path.Join(ls.logPath, name)) | ||
|  | 			if err != nil { | ||
|  | 				// log into stderr if we can't create new file
 | ||
|  | 				fmt.Fprintln(os.Stderr, err) | ||
|  | 				ls.logFile = os.Stderr | ||
|  | 			} else { | ||
|  | 				next := current.Truncate(ls.unit).Add(ls.unit) | ||
|  | 				ls.timeToCreate = time.After(next.Sub(time.Now())) | ||
|  | 			} | ||
|  | 		default: | ||
|  | 			// do nothing
 | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return ls.logFile.Write(p) | ||
|  | } | ||
|  | 
 | ||
|  | func (ls *logSegment) Close() { | ||
|  | 	ls.logFile.Close() | ||
|  | } | ||
|  | 
 | ||
|  | func getLogFileName(t time.Time) string { | ||
|  | 	proc := path.Base(os.Args[0]) | ||
|  | 	now := time.Now() | ||
|  | 	year := now.Year() | ||
|  | 	month := now.Month() | ||
|  | 	day := now.Day() | ||
|  | 	hour := now.Hour() | ||
|  | 	minute := now.Minute() | ||
|  | 	pid := os.Getpid() | ||
|  | 	return fmt.Sprintf("%s.%04d-%02d-%02d-%02d-%02d.%d.log", | ||
|  | 		proc, year, month, day, hour, minute, pid) | ||
|  | } | ||
|  | 
 | ||
|  | // Logger is the logger type.
 | ||
|  | type Logger struct { | ||
|  | 	logger     *log.Logger | ||
|  | 	level      LogLevel | ||
|  | 	segment    *logSegment | ||
|  | 	stopped    int32 | ||
|  | 	logPath    string | ||
|  | 	unit       time.Duration | ||
|  | 	isStdout   bool | ||
|  | 	printStack bool | ||
|  | } | ||
|  | 
 | ||
|  | func (l Logger) doPrintf(level LogLevel, format string, v ...interface{}) { | ||
|  | 	if l.logger == nil { | ||
|  | 		return | ||
|  | 	} | ||
|  | 	if level >= l.level { | ||
|  | 		funcName, fileName, lineNum := getRuntimeInfo() | ||
|  | 		format = fmt.Sprintf("%5s [%s] (%s:%d) - %s", tagName[level], path.Base(funcName), path.Base(fileName), lineNum, format) | ||
|  | 		l.logger.Printf(format, v...) | ||
|  | 		if l.isStdout { | ||
|  | 			log.Printf(format, v...) | ||
|  | 		} | ||
|  | 		if level == FATAL { | ||
|  | 			// os.Exit(1)
 | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (l Logger) doPrintln(level LogLevel, v ...interface{}) { | ||
|  | 	if l.logger == nil { | ||
|  | 		return | ||
|  | 	} | ||
|  | 	if level >= l.level { | ||
|  | 		funcName, fileName, lineNum := getRuntimeInfo() | ||
|  | 		prefix := fmt.Sprintf("%5s [%s] (%s:%d) - ", tagName[level], path.Base(funcName), path.Base(fileName), lineNum) | ||
|  | 		value := fmt.Sprintf("%s%s", prefix, fmt.Sprintln(v...)) | ||
|  | 		l.logger.Print(value) | ||
|  | 		if l.isStdout { | ||
|  | 			log.Print(value) | ||
|  | 		} | ||
|  | 		if level == FATAL { | ||
|  | 			// os.Exit(1)
 | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func getRuntimeInfo() (string, string, int) { | ||
|  | 	pc, fn, ln, ok := runtime.Caller(3) // 3 steps up the stack frame
 | ||
|  | 	if !ok { | ||
|  | 		fn = "???" | ||
|  | 		ln = 0 | ||
|  | 	} | ||
|  | 	function := "???" | ||
|  | 	caller := runtime.FuncForPC(pc) | ||
|  | 	if caller != nil { | ||
|  | 		function = caller.Name() | ||
|  | 	} | ||
|  | 	return function, fn, ln | ||
|  | } | ||
|  | 
 | ||
|  | // DebugLevel sets log level to debug.
 | ||
|  | func DebugLevel(l Logger) Logger { | ||
|  | 	l.level = DEBUG | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // InfoLevel sets log level to info.
 | ||
|  | func InfoLevel(l Logger) Logger { | ||
|  | 	l.level = INFO | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // WarnLevel sets log level to warn.
 | ||
|  | func WarnLevel(l Logger) Logger { | ||
|  | 	l.level = WARN | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // ErrorLevel sets log level to error.
 | ||
|  | func ErrorLevel(l Logger) Logger { | ||
|  | 	l.level = ERROR | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // FatalLevel sets log level to fatal.
 | ||
|  | func FatalLevel(l Logger) Logger { | ||
|  | 	l.level = FATAL | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // LogFilePath returns a function to set the log file path.
 | ||
|  | func LogFilePath(p string) func(Logger) Logger { | ||
|  | 	return func(l Logger) Logger { | ||
|  | 		l.logPath = p | ||
|  | 		return l | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // EveryDay sets new log file created every day.
 | ||
|  | func EveryDay(l Logger) Logger { | ||
|  | 	l.unit = time.Hour * 24 | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // EveryHour sets new log file created every hour.
 | ||
|  | func EveryHour(l Logger) Logger { | ||
|  | 	l.unit = time.Hour | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // EveryMinute sets new log file created every minute.
 | ||
|  | func EveryMinute(l Logger) Logger { | ||
|  | 	l.unit = time.Minute | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // AlsoStdout sets log also output to stdio.
 | ||
|  | func AlsoStdout(l Logger) Logger { | ||
|  | 	l.isStdout = true | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // PrintStack sets log output the stack trace info.
 | ||
|  | func PrintStack(l Logger) Logger { | ||
|  | 	l.printStack = true | ||
|  | 	return l | ||
|  | } | ||
|  | 
 | ||
|  | // Debugf prints formatted debug log.
 | ||
|  | func Debugf(format string, v ...interface{}) { | ||
|  | 	loggerInstance.doPrintf(DEBUG, format, v...) | ||
|  | } | ||
|  | 
 | ||
|  | // Infof prints formatted info log.
 | ||
|  | func Infof(format string, v ...interface{}) { | ||
|  | 	loggerInstance.doPrintf(INFO, format, v...) | ||
|  | } | ||
|  | 
 | ||
|  | // Warnf prints formatted warn log.
 | ||
|  | func Warnf(format string, v ...interface{}) { | ||
|  | 	loggerInstance.doPrintf(WARN, format, v...) | ||
|  | } | ||
|  | 
 | ||
|  | // Errorf prints formatted error log.
 | ||
|  | func Errorf(format string, v ...interface{}) { | ||
|  | 	loggerInstance.doPrintf(ERROR, format, v...) | ||
|  | } | ||
|  | 
 | ||
|  | // Fatalf prints formatted fatal log and exits.
 | ||
|  | func Fatalf(format string, v ...interface{}) { | ||
|  | 	loggerInstance.doPrintf(FATAL, format, v...) | ||
|  | 	// os.Exit(1)
 | ||
|  | } | ||
|  | 
 | ||
|  | // Debugln prints debug log.
 | ||
|  | func Debugln(v ...interface{}) { | ||
|  | 	loggerInstance.doPrintln(DEBUG, v...) | ||
|  | } | ||
|  | 
 | ||
|  | // Infoln prints info log.
 | ||
|  | func Infoln(v ...interface{}) { | ||
|  | 	loggerInstance.doPrintln(INFO, v...) | ||
|  | } | ||
|  | 
 | ||
|  | // Warnln prints warn log.
 | ||
|  | func Warnln(v ...interface{}) { | ||
|  | 	loggerInstance.doPrintln(WARN, v...) | ||
|  | } | ||
|  | 
 | ||
|  | // Errorln prints error log.
 | ||
|  | func Errorln(v ...interface{}) { | ||
|  | 	loggerInstance.doPrintln(ERROR, v...) | ||
|  | } | ||
|  | 
 | ||
|  | // Fatalln prints fatal log and exits.
 | ||
|  | func Fatalln(v ...interface{}) { | ||
|  | 	loggerInstance.doPrintln(FATAL, v...) | ||
|  | 	// os.Exit(1)
 | ||
|  | } |