Added log start flags

- Added log rotate function (experimental)
- Added disable log function #802
- Added log compression for rotated file (experimental)
This commit is contained in:
Toby Chui
2025-09-06 00:44:54 +08:00
parent c3afdefe45
commit 14bef4ef98
6 changed files with 166 additions and 21 deletions

View File

@@ -384,6 +384,7 @@ func initAPIs(targetMux *http.ServeMux) {
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog) authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
authRouter.HandleFunc("/api/log/summary", LogViewer.HandleReadLogSummary) authRouter.HandleFunc("/api/log/summary", LogViewer.HandleReadLogSummary)
authRouter.HandleFunc("/api/log/errors", LogViewer.HandleLogErrorSummary) authRouter.HandleFunc("/api/log/errors", LogViewer.HandleLogErrorSummary)
authRouter.HandleFunc("/api/log/rotate/debug.trigger", SystemWideLogger.HandleDebugTriggerLogRotation)
//Debug //Debug
authRouter.HandleFunc("/api/info/pprof", pprof.Index) authRouter.HandleFunc("/api/info/pprof", pprof.Index)
} }

View File

@@ -94,6 +94,11 @@ var (
allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder") 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") 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 */ /* Default Configuration Flags */
defaultInboundPort = flag.Int("default_inbound_port", 443, "Default web server listening port") 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") defaultEnableInboundTraffic = flag.Bool("default_inbound_enabled", true, "If web server is enabled by default")

View File

@@ -18,12 +18,15 @@ import (
*/ */
type Logger struct { type Logger struct {
Prefix string //Prefix for log files Prefix string //Prefix for log files
LogFolder string //Folder to store the log file LogFolder string //Folder to store the log file
CurrentLogFile string //Current writing filename CurrentLogFile string //Current writing filename
RotateOption RotateOption //Options for log rotation, see rotate.go RotateOption *RotateOption //Options for log rotation, see rotate.go
logger *log.Logger
file *os.File //Internal
logRotateTicker *time.Ticker
logger *log.Logger
file *os.File
} }
// Create a new logger that log to files // Create a new logger that log to files
@@ -47,6 +50,17 @@ func NewLogger(logFilePrefix string, logFolder string) (*Logger, error) {
thisLogger.CurrentLogFile = logFilePath thisLogger.CurrentLogFile = logFilePath
thisLogger.file = f 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 //Start the logger
logger := log.New(f, "", log.Flags()&^(log.Ldate|log.Ltime)) logger := log.New(f, "", log.Flags()&^(log.Ldate|log.Ltime))
logger.SetFlags(0) logger.SetFlags(0)
@@ -66,6 +80,11 @@ func NewFmtLogger() (*Logger, error) {
}, nil }, nil
} }
// SetRotateOption will set the log rotation option
func (l *Logger) SetRotateOption(option *RotateOption) {
l.RotateOption = option
}
func (l *Logger) getLogFilepath() string { func (l *Logger) getLogFilepath() string {
year, month, _ := time.Now().Date() year, month, _ := time.Now().Date()
return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log") 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.Close()
l.file = nil 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 //Create a new log file
f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
if err != nil { if err != nil {
@@ -136,5 +161,8 @@ func (l *Logger) ValidateAndUpdateLogFilepath() {
} }
func (l *Logger) Close() { func (l *Logger) Close() {
l.file.Close() if l.file != nil {
l.file.Close()
}
l.StopLogRotateTicker()
} }

View File

@@ -4,20 +4,31 @@ import (
"archive/zip" "archive/zip"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"time" "time"
"imuslab.com/zoraxy/mod/utils"
) )
type RotateOption struct { 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) 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 MaxBackups int //Maximum number of backup files to keep
Compress bool //Whether to compress the rotated files 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 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 { func (l *Logger) LogNeedRotate(filename string) bool {
if !l.RotateOption.Enabled { if !l.RotateOption.Enabled {
return false return false
@@ -29,19 +40,82 @@ func (l *Logger) LogNeedRotate(filename string) bool {
return info.Size() >= l.RotateOption.MaxSize 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 { func (l *Logger) RotateLog() error {
if !l.RotateOption.Enabled { if l.RotateOption == nil || !l.RotateOption.Enabled {
return nil return nil
} }
needRotate := l.LogNeedRotate(l.CurrentLogFile) needRotate := l.LogNeedRotate(l.CurrentLogFile)
l.PrintAndLog("logger", fmt.Sprintf("Log rotation check: need rotate = %v", needRotate), nil)
if !needRotate { if !needRotate {
return nil return nil
} }
//Close current file // Close current file with retry on failure
if l.file != nil { 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 // Determine backup directory
@@ -58,7 +132,8 @@ func (l *Logger) RotateLog() error {
// Generate rotated filename with timestamp // Generate rotated filename with timestamp
timestamp := time.Now().Format("20060102-150405") timestamp := time.Now().Format("20060102-150405")
baseName := filepath.Base(l.CurrentLogFile) 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) rotatedPath := filepath.Join(backupDir, rotatedName)
// Rename current log file to rotated file // Rename current log file to rotated file
@@ -95,7 +170,9 @@ func (l *Logger) RotateLog() error {
return err return err
} }
l.file = file l.file = file
if l.logger != nil {
l.logger.SetOutput(file)
}
return nil return nil
} }

View File

@@ -65,6 +65,27 @@ func startupSequence() {
} else { } else {
panic(err) 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{ LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
RootFolder: *path_logFile, RootFolder: *path_logFile,
Extension: LOG_EXTENSION, Extension: LOG_EXTENSION,
@@ -72,9 +93,10 @@ func startupSequence() {
//Create database //Create database
backendType := database.GetRecommendedBackendType() backendType := database.GetRecommendedBackendType()
if *databaseBackend == "leveldb" { switch *databaseBackend {
case "leveldb":
backendType = dbinc.BackendLevelDB backendType = dbinc.BackendLevelDB
} else if *databaseBackend == "boltdb" { case "boltdb":
backendType = dbinc.BackendBoltDB backendType = dbinc.BackendBoltDB
} }
l.PrintAndLog("database", "Using "+backendType.String()+" as the database backend", nil) l.PrintAndLog("database", "Using "+backendType.String()+" as the database backend", nil)

View File

@@ -121,9 +121,9 @@
<a class="item panel_menu_btn" id="summaryMenu"> <a class="item panel_menu_btn" id="summaryMenu">
Summary Summary
</a> </a>
<a class="item panel_menu_btn" id="settingsMenu"> <!-- <a class="item panel_menu_btn" id="settingsMenu">
Settings Settings
</a> </a> -->
</div> </div>
</div> </div>
<div class="ui container"> <div class="ui container">
@@ -159,6 +159,11 @@
<button class="ui icon basic button logfile_menu_btn" onclick="openLogInNewTab();" title="Open in New Tab"> <button class="ui icon basic button logfile_menu_btn" onclick="openLogInNewTab();" title="Open in New Tab">
<i class="external alternate icon"></i> <i class="external alternate icon"></i>
</button> </button>
<!-- Refresh -->
<button class="ui icon basic button logfile_menu_btn" id="refreshLogBtn" title="Refresh Current Log">
<i class="black sync icon"></i>
</button>
</div> </div>
</div> </div>
<br><br> <br><br>
@@ -281,6 +286,16 @@ Pick a log file from the menu to start debugging
} }
}); });
/* Refresh Button */
$("#refreshLogBtn").on("click", function() {
if (currentLogFile) {
openLog(null, null, currentLogFile, currentFilter);
loadLogSummary(currentLogFile);
} else {
alert('Please select a log file first.');
}
});
/* Log file dropdown */ /* Log file dropdown */
function populateLogfileDropdown() { function populateLogfileDropdown() {
$.get("/api/log/list", function(data){ $.get("/api/log/list", function(data){
@@ -610,10 +625,7 @@ Pick a log file from the menu to start debugging
function renderErrorHighlights(errors) { function renderErrorHighlights(errors) {
if (!Array.isArray(errors) || errors.length === 0) { if (!Array.isArray(errors) || errors.length === 0) {
$("#errorHighlightWrapper").html( $("#errorHighlightWrapper").html(
`<div class="ui positive message"> `<p><i class="ui green circle check icon"></i> No error found</p>`
<div class="header">No errors found</div>
<p>This log file contains no errors.</p>
</div>`
); );
return; return;
} }