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
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)
|
|
}
|