go-logger/file.go

137 lines
3.3 KiB
Go

package logger
import (
"fmt"
"log"
"os"
"strings"
"sync"
"time"
)
// FileLogger contains configuration options specific to logging into a file.
// It is enabled if the FilePath != ""
type FileLogger struct {
// Minimum log level for logging into a file
Level Level
// Absolute or relative path to log files to
Path string
// With this option the path of the log file will be appended with the current date
// so that an own log file for each day is used. The format of the date is 'YYYYMMDD'
AppendDate bool
// Internal dependency used to synchronize the access to the log file
fileSync *sync.RWMutex
// Additional file sync that is used during writing to the log file
fileSyncWrite *sync.RWMutex
logger *log.Logger
file *os.File
// Upper logger struct
rootLogger *Logger
}
// CloseFile closes the file that is currently used for logging messages to
// a file
func (l *FileLogger) CloseFile() {
if l.file != nil {
l.fileSync.Lock()
l.file.Close()
l.file = nil
l.logger = nil
l.fileSync.Unlock()
}
}
// openFile tries to open the file that is configured inside the loggers fild
// "LogFilePath" and initializes the mutex
func (l *FileLogger) openFile() {
// Initialize new mutex
if l.fileSync == nil {
l.fileSync = &sync.RWMutex{}
l.fileSyncWrite = &sync.RWMutex{}
}
l.fileSync.Lock()
path := l.getFilePath()
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
l.logger = log.New(file, "", 0)
l.file = file
} else {
l.rootLogger.Log(LevelError, fmt.Sprintf("Cannot access the log file '%s'\n%s", path, err.Error()))
}
l.fileSync.Unlock()
}
// writeToFile writes the given message to the opened log file
func (l *FileLogger) writeToFile(message string, level Level) {
l.fileSync.RLock()
l.fileSyncWrite.RLock()
// When append date is enabled we need to check if file path is still accurate
if l.AppendDate {
currentPath := l.getFilePath()
if l.file.Name() != currentPath {
// The file path is not up-to-date anymore → update the log file
l.fileSync.RUnlock()
l.fileSyncWrite.RUnlock()
l.fileSyncWrite.Lock()
// The syncWriter is now locked. So check again the file name against the current path because the file could already be changed
// in the time framew between locking and checking
if l.file.Name() != currentPath {
l.CloseFile()
l.openFile()
}
l.fileSyncWrite.Unlock()
// Lock previous locks again
l.fileSync.RLock()
l.fileSyncWrite.RLock()
}
}
l.logger.Println(message)
l.file.Sync()
l.fileSync.RUnlock()
l.fileSyncWrite.RUnlock()
// Close the file because for fatal log level the program is going to be exited
if level == LevelFatal {
l.CloseFile()
}
}
// getFilePath returns the path to use for the log file
func (l *FileLogger) getFilePath() string {
path := strings.ReplaceAll(l.Path, "\\", "/")
// Append the current date to the log path when enabled
if l.AppendDate {
lastSlash := strings.LastIndex(path, "/")
if lastSlash != -1 && (lastSlash+1) < len(path) {
path = path + "." + getFileDate()
} else if lastSlash == -1 {
path = path + "." + getFileDate()
} else {
path += getFileDate()
}
}
return path
}
// getFileDate returns the current date formatted as the log files path name
func getFileDate() string {
return time.Now().Format("2006-01-02")
}