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

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