diff --git a/src/api.go b/src/api.go
index ea6dbc9..27fc773 100644
--- a/src/api.go
+++ b/src/api.go
@@ -384,6 +384,7 @@ func initAPIs(targetMux *http.ServeMux) {
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
authRouter.HandleFunc("/api/log/summary", LogViewer.HandleReadLogSummary)
authRouter.HandleFunc("/api/log/errors", LogViewer.HandleLogErrorSummary)
+ authRouter.HandleFunc("/api/log/rotate/debug.trigger", SystemWideLogger.HandleDebugTriggerLogRotation)
//Debug
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
}
diff --git a/src/def.go b/src/def.go
index a2d6897..35b58eb 100644
--- a/src/def.go
+++ b/src/def.go
@@ -94,6 +94,11 @@ var (
allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
+ /* Logging Configuration Flags */
+ enableLog = flag.Bool("enablelog", true, "Enable system wide logging, set to false for writing log to STDOUT only")
+ enableLogCompression = flag.Bool("enablelogcompress", true, "Enable log compression for rotated log files")
+ logRotate = flag.Int("logrotate", 0, "Enable log rotation and set the maximum log file size in KB (e.g. 25 for 25KB), set to 0 for disable")
+
/* Default Configuration Flags */
defaultInboundPort = flag.Int("default_inbound_port", 443, "Default web server listening port")
defaultEnableInboundTraffic = flag.Bool("default_inbound_enabled", true, "If web server is enabled by default")
diff --git a/src/mod/info/logger/logger.go b/src/mod/info/logger/logger.go
index 06b0e45..0474486 100644
--- a/src/mod/info/logger/logger.go
+++ b/src/mod/info/logger/logger.go
@@ -18,12 +18,15 @@ import (
*/
type Logger struct {
- Prefix string //Prefix for log files
- LogFolder string //Folder to store the log file
- CurrentLogFile string //Current writing filename
- RotateOption RotateOption //Options for log rotation, see rotate.go
- logger *log.Logger
- file *os.File
+ Prefix string //Prefix for log files
+ LogFolder string //Folder to store the log file
+ CurrentLogFile string //Current writing filename
+ RotateOption *RotateOption //Options for log rotation, see rotate.go
+
+ //Internal
+ logRotateTicker *time.Ticker
+ logger *log.Logger
+ file *os.File
}
// Create a new logger that log to files
@@ -47,6 +50,17 @@ func NewLogger(logFilePrefix string, logFolder string) (*Logger, error) {
thisLogger.CurrentLogFile = logFilePath
thisLogger.file = f
+ //Initiate the log rotation ticker
+ thisLogger.logRotateTicker = time.NewTicker(1 * time.Hour)
+ go func() {
+ for range thisLogger.logRotateTicker.C {
+ err := thisLogger.RotateLog()
+ if err != nil {
+ log.Println("Log rotation error: ", err.Error())
+ }
+ }
+ }()
+
//Start the logger
logger := log.New(f, "", log.Flags()&^(log.Ldate|log.Ltime))
logger.SetFlags(0)
@@ -66,6 +80,11 @@ func NewFmtLogger() (*Logger, error) {
}, nil
}
+// SetRotateOption will set the log rotation option
+func (l *Logger) SetRotateOption(option *RotateOption) {
+ l.RotateOption = option
+}
+
func (l *Logger) getLogFilepath() string {
year, month, _ := time.Now().Date()
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
@@ -119,6 +138,12 @@ func (l *Logger) ValidateAndUpdateLogFilepath() {
l.file.Close()
l.file = nil
+ //Archive the old log file
+ err := l.ArchiveLog(l.CurrentLogFile)
+ if err != nil {
+ log.Println("Unable to archive old log file: ", err.Error())
+ }
+
//Create a new log file
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
if err != nil {
@@ -136,5 +161,8 @@ func (l *Logger) ValidateAndUpdateLogFilepath() {
}
func (l *Logger) Close() {
- l.file.Close()
+ if l.file != nil {
+ l.file.Close()
+ }
+ l.StopLogRotateTicker()
}
diff --git a/src/mod/info/logger/rotate.go b/src/mod/info/logger/rotate.go
index c27edcb..0562dba 100644
--- a/src/mod/info/logger/rotate.go
+++ b/src/mod/info/logger/rotate.go
@@ -4,20 +4,31 @@ import (
"archive/zip"
"fmt"
"io"
+ "net/http"
"os"
"path/filepath"
"sort"
"time"
+
+ "imuslab.com/zoraxy/mod/utils"
)
type RotateOption struct {
- Enabled bool //Whether log rotation is enabled
+ Enabled bool //Whether log rotation is enabled, default false
MaxSize int64 //Maximum size of the log file in bytes before rotation (e.g. 10 * 1024 * 1024 for 10MB)
MaxBackups int //Maximum number of backup files to keep
Compress bool //Whether to compress the rotated files
BackupDir string //Directory to store backup files, if empty, use the same directory as the log file
}
+// Stop the log rotation ticker
+func (l *Logger) StopLogRotateTicker() {
+ if l.logRotateTicker != nil {
+ l.logRotateTicker.Stop()
+ }
+}
+
+// Check if the log file needs rotation
func (l *Logger) LogNeedRotate(filename string) bool {
if !l.RotateOption.Enabled {
return false
@@ -29,19 +40,82 @@ func (l *Logger) LogNeedRotate(filename string) bool {
return info.Size() >= l.RotateOption.MaxSize
}
+// Handle web request trigger log ratation
+func (l *Logger) HandleDebugTriggerLogRotation(w http.ResponseWriter, r *http.Request) {
+ err := l.RotateLog()
+ if err != nil {
+ utils.SendErrorResponse(w, "Log rotation error: "+err.Error())
+ return
+ }
+ l.PrintAndLog("logger", "Log rotation triggered via REST API", nil)
+ utils.SendOK(w)
+}
+
+// ArchiveLog will archive the given log file, use during month change
+func (l *Logger) ArchiveLog(filename string) error {
+ if l.RotateOption == nil || !l.RotateOption.Enabled {
+ return nil
+ }
+
+ // Determine backup directory
+ backupDir := l.RotateOption.BackupDir
+ if backupDir == "" {
+ backupDir = filepath.Dir(filename)
+ }
+
+ // Ensure backup directory exists
+ if err := os.MkdirAll(backupDir, 0755); err != nil {
+ return err
+ }
+
+ // Generate archived filename with timestamp
+ timestamp := time.Now().Format("20060102-150405")
+ baseName := filepath.Base(filename)
+ baseName = baseName[:len(baseName)-len(filepath.Ext(baseName))]
+ archivedName := fmt.Sprintf("%s.%s.log", baseName, timestamp)
+ archivedPath := filepath.Join(backupDir, archivedName)
+
+ // Rename current log file to archived file
+ if err := os.Rename(filename, archivedPath); err != nil {
+ return err
+ }
+
+ // Optionally compress the archived file
+ if l.RotateOption.Compress {
+ if err := compressFile(archivedPath); err != nil {
+ return err
+ }
+ os.Remove(archivedPath)
+ }
+
+ return nil
+}
+
+// Execute log rotation
func (l *Logger) RotateLog() error {
- if !l.RotateOption.Enabled {
+ if l.RotateOption == nil || !l.RotateOption.Enabled {
return nil
}
needRotate := l.LogNeedRotate(l.CurrentLogFile)
+ l.PrintAndLog("logger", fmt.Sprintf("Log rotation check: need rotate = %v", needRotate), nil)
if !needRotate {
return nil
}
- //Close current file
+ // Close current file with retry on failure
if l.file != nil {
- l.file.Close()
+ var closeErr error
+ for i := 0; i < 5; i++ {
+ closeErr = l.file.Close()
+ if closeErr == nil {
+ break
+ }
+ time.Sleep(1 * time.Second)
+ }
+ if closeErr != nil {
+ return closeErr
+ }
}
// Determine backup directory
@@ -58,7 +132,8 @@ func (l *Logger) RotateLog() error {
// Generate rotated filename with timestamp
timestamp := time.Now().Format("20060102-150405")
baseName := filepath.Base(l.CurrentLogFile)
- rotatedName := fmt.Sprintf("%s.%s", baseName, timestamp)
+ baseName = baseName[:len(baseName)-len(filepath.Ext(baseName))]
+ rotatedName := fmt.Sprintf("%s.%s.log", baseName, timestamp)
rotatedPath := filepath.Join(backupDir, rotatedName)
// Rename current log file to rotated file
@@ -95,7 +170,9 @@ func (l *Logger) RotateLog() error {
return err
}
l.file = file
-
+ if l.logger != nil {
+ l.logger.SetOutput(file)
+ }
return nil
}
diff --git a/src/start.go b/src/start.go
index b280871..e5e5c83 100644
--- a/src/start.go
+++ b/src/start.go
@@ -65,6 +65,27 @@ func startupSequence() {
} else {
panic(err)
}
+
+ if !*enableLog {
+ //Disable file logging, use fmt logger instead
+ l, err = logger.NewFmtLogger()
+ if err != nil {
+ panic(err)
+ }
+ SystemWideLogger = l
+ SystemWideLogger.Println("System wide logging is disabled, all logs will be printed to STDOUT only")
+ } else {
+ l.SetRotateOption(&logger.RotateOption{
+ Enabled: *logRotate != 0,
+ MaxSize: int64(*logRotate) * 1024, //Convert to bytes
+ MaxBackups: 10,
+ Compress: *enableLogCompression,
+ BackupDir: "",
+ })
+ SystemWideLogger = l
+ SystemWideLogger.Println("System wide logging is enabled")
+ }
+
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
RootFolder: *path_logFile,
Extension: LOG_EXTENSION,
@@ -72,9 +93,10 @@ func startupSequence() {
//Create database
backendType := database.GetRecommendedBackendType()
- if *databaseBackend == "leveldb" {
+ switch *databaseBackend {
+ case "leveldb":
backendType = dbinc.BackendLevelDB
- } else if *databaseBackend == "boltdb" {
+ case "boltdb":
backendType = dbinc.BackendBoltDB
}
l.PrintAndLog("database", "Using "+backendType.String()+" as the database backend", nil)
diff --git a/src/web/snippet/logview.html b/src/web/snippet/logview.html
index 17ffe1c..35a6beb 100644
--- a/src/web/snippet/logview.html
+++ b/src/web/snippet/logview.html
@@ -121,9 +121,9 @@
-