mirror of https://github.com/stashapp/stash.git
317 lines
6.8 KiB
Go
317 lines
6.8 KiB
Go
package log
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type LogItem struct {
|
|
Time time.Time `json:"time"`
|
|
Type string `json:"type"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type Logger struct {
|
|
logger *logrus.Logger
|
|
progressLogger *logrus.Logger
|
|
mutex sync.Mutex
|
|
logCache []LogItem
|
|
logSubs []chan []LogItem
|
|
waiting bool
|
|
lastBroadcast time.Time
|
|
logBuffer []LogItem
|
|
}
|
|
|
|
func NewLogger() *Logger {
|
|
ret := &Logger{
|
|
logger: logrus.New(),
|
|
progressLogger: logrus.New(),
|
|
lastBroadcast: time.Now(),
|
|
}
|
|
|
|
ret.progressLogger.SetFormatter(new(ProgressFormatter))
|
|
|
|
return ret
|
|
}
|
|
|
|
// Init initialises the logger based on a logging configuration
|
|
func (log *Logger) Init(logFile string, logOut bool, logLevel string) {
|
|
var file *os.File
|
|
customFormatter := new(logrus.TextFormatter)
|
|
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
|
|
customFormatter.ForceColors = true
|
|
customFormatter.FullTimestamp = true
|
|
log.logger.SetOutput(os.Stderr)
|
|
log.logger.SetFormatter(customFormatter)
|
|
|
|
// #1837 - trigger the console to use color-mode since it won't be
|
|
// otherwise triggered until the first log entry
|
|
// this is covers the situation where the logger is only logging to file
|
|
// and therefore does not trigger the console color-mode - resulting in
|
|
// the access log colouring not being applied
|
|
_, _ = customFormatter.Format(logrus.NewEntry(log.logger))
|
|
|
|
if logFile != "" {
|
|
var err error
|
|
file, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
|
|
if err != nil {
|
|
fmt.Printf("Could not open '%s' for log output due to error: %s\n", logFile, err.Error())
|
|
}
|
|
}
|
|
|
|
if file != nil {
|
|
if logOut {
|
|
// log to file separately disabling colours
|
|
fileFormatter := new(logrus.TextFormatter)
|
|
fileFormatter.TimestampFormat = customFormatter.TimestampFormat
|
|
fileFormatter.FullTimestamp = customFormatter.FullTimestamp
|
|
log.logger.AddHook(&fileLogHook{
|
|
Writer: file,
|
|
Formatter: fileFormatter,
|
|
})
|
|
} else {
|
|
// logging to file only
|
|
// turn off the colouring for the file
|
|
customFormatter.ForceColors = false
|
|
log.logger.Out = file
|
|
}
|
|
}
|
|
|
|
// otherwise, output to StdErr
|
|
|
|
log.SetLogLevel(logLevel)
|
|
}
|
|
|
|
func (log *Logger) SetLogLevel(level string) {
|
|
log.logger.Level = logLevelFromString(level)
|
|
}
|
|
|
|
func logLevelFromString(level string) logrus.Level {
|
|
ret := logrus.InfoLevel
|
|
|
|
switch strings.ToLower(level) {
|
|
case "debug":
|
|
ret = logrus.DebugLevel
|
|
case "warning":
|
|
ret = logrus.WarnLevel
|
|
case "error":
|
|
ret = logrus.ErrorLevel
|
|
case "trace":
|
|
ret = logrus.TraceLevel
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (log *Logger) addToCache(l *LogItem) {
|
|
// assumes mutex held
|
|
// only add to cache if meets minimum log level
|
|
level := logLevelFromString(l.Type)
|
|
if level <= log.logger.Level {
|
|
log.logCache = append([]LogItem{*l}, log.logCache...)
|
|
if len(log.logCache) > 30 {
|
|
log.logCache = log.logCache[:len(log.logCache)-1]
|
|
}
|
|
}
|
|
}
|
|
|
|
func (log *Logger) addLogItem(l *LogItem) {
|
|
log.mutex.Lock()
|
|
l.Time = time.Now()
|
|
log.addToCache(l)
|
|
log.mutex.Unlock()
|
|
go log.broadcastLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) GetLogCache() []LogItem {
|
|
log.mutex.Lock()
|
|
|
|
ret := make([]LogItem, len(log.logCache))
|
|
copy(ret, log.logCache)
|
|
|
|
log.mutex.Unlock()
|
|
|
|
return ret
|
|
}
|
|
|
|
func (log *Logger) SubscribeToLog(stop chan int) <-chan []LogItem {
|
|
ret := make(chan []LogItem, 100)
|
|
|
|
go func() {
|
|
<-stop
|
|
log.unsubscribeFromLog(ret)
|
|
}()
|
|
|
|
log.mutex.Lock()
|
|
log.logSubs = append(log.logSubs, ret)
|
|
log.mutex.Unlock()
|
|
|
|
return ret
|
|
}
|
|
|
|
func (log *Logger) unsubscribeFromLog(toRemove chan []LogItem) {
|
|
log.mutex.Lock()
|
|
for i, c := range log.logSubs {
|
|
if c == toRemove {
|
|
log.logSubs = append(log.logSubs[:i], log.logSubs[i+1:]...)
|
|
}
|
|
}
|
|
close(toRemove)
|
|
log.mutex.Unlock()
|
|
}
|
|
|
|
func (log *Logger) doBroadcastLogItems() {
|
|
// assumes mutex held
|
|
|
|
for _, c := range log.logSubs {
|
|
// don't block waiting to broadcast
|
|
select {
|
|
case c <- log.logBuffer:
|
|
default:
|
|
}
|
|
}
|
|
|
|
log.logBuffer = nil
|
|
log.waiting = false
|
|
log.lastBroadcast = time.Now()
|
|
}
|
|
|
|
func (log *Logger) broadcastLogItem(l *LogItem) {
|
|
log.mutex.Lock()
|
|
|
|
log.logBuffer = append(log.logBuffer, *l)
|
|
|
|
// don't send more than once per second
|
|
if !log.waiting {
|
|
// if last broadcast was under a second ago, wait until a second has
|
|
// passed
|
|
timeSinceBroadcast := time.Since(log.lastBroadcast)
|
|
if timeSinceBroadcast.Seconds() < 1 {
|
|
log.waiting = true
|
|
time.AfterFunc(time.Second-timeSinceBroadcast, func() {
|
|
log.mutex.Lock()
|
|
log.doBroadcastLogItems()
|
|
log.mutex.Unlock()
|
|
})
|
|
} else {
|
|
log.doBroadcastLogItems()
|
|
}
|
|
}
|
|
|
|
// if waiting then adding it to the buffer is sufficient
|
|
log.mutex.Unlock()
|
|
}
|
|
|
|
func (log *Logger) Progressf(format string, args ...interface{}) {
|
|
log.progressLogger.Infof(format, args...)
|
|
l := &LogItem{
|
|
Type: "progress",
|
|
Message: fmt.Sprintf(format, args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Trace(args ...interface{}) {
|
|
log.logger.Trace(args...)
|
|
l := &LogItem{
|
|
Type: "trace",
|
|
Message: fmt.Sprint(args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Tracef(format string, args ...interface{}) {
|
|
log.logger.Tracef(format, args...)
|
|
l := &LogItem{
|
|
Type: "trace",
|
|
Message: fmt.Sprintf(format, args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Debug(args ...interface{}) {
|
|
log.logger.Debug(args...)
|
|
l := &LogItem{
|
|
Type: "debug",
|
|
Message: fmt.Sprint(args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Debugf(format string, args ...interface{}) {
|
|
log.logger.Debugf(format, args...)
|
|
l := &LogItem{
|
|
Type: "debug",
|
|
Message: fmt.Sprintf(format, args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Info(args ...interface{}) {
|
|
log.logger.Info(args...)
|
|
l := &LogItem{
|
|
Type: "info",
|
|
Message: fmt.Sprint(args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Infof(format string, args ...interface{}) {
|
|
log.logger.Infof(format, args...)
|
|
l := &LogItem{
|
|
Type: "info",
|
|
Message: fmt.Sprintf(format, args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Warn(args ...interface{}) {
|
|
log.logger.Warn(args...)
|
|
l := &LogItem{
|
|
Type: "warn",
|
|
Message: fmt.Sprint(args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Warnf(format string, args ...interface{}) {
|
|
log.logger.Warnf(format, args...)
|
|
l := &LogItem{
|
|
Type: "warn",
|
|
Message: fmt.Sprintf(format, args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Error(args ...interface{}) {
|
|
log.logger.Error(args...)
|
|
l := &LogItem{
|
|
Type: "error",
|
|
Message: fmt.Sprint(args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Errorf(format string, args ...interface{}) {
|
|
log.logger.Errorf(format, args...)
|
|
l := &LogItem{
|
|
Type: "error",
|
|
Message: fmt.Sprintf(format, args...),
|
|
}
|
|
log.addLogItem(l)
|
|
}
|
|
|
|
func (log *Logger) Fatal(args ...interface{}) {
|
|
log.logger.Fatal(args...)
|
|
}
|
|
|
|
func (log *Logger) Fatalf(format string, args ...interface{}) {
|
|
log.logger.Fatalf(format, args...)
|
|
}
|