mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-09-15 16:49:41 +02:00
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:
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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")
|
||||||
|
@@ -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()
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
src/start.go
26
src/start.go
@@ -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)
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user