diff --git a/src/Makefile b/src/Makefile index aa9b2c0..debae3c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ # PLATFORMS := darwin/amd64 darwin/arm64 freebsd/amd64 linux/386 linux/amd64 linux/arm linux/arm64 linux/mipsle windows/386 windows/amd64 windows/arm windows/arm64 -PLATFORMS := linux/amd64 linux/386 linux/arm linux/arm64 linux/mipsle linux/riscv64 windows/amd64 freebsd/amd64 +PLATFORMS := linux/amd64 linux/386 linux/arm linux/arm64 linux/mipsle linux/riscv64 windows/amd64 freebsd/amd64 darwin/amd64 temp = $(subst /, ,$@) os = $(word 1, $(temp)) arch = $(word 2, $(temp)) diff --git a/src/api.go b/src/api.go index e98f7fb..ea6dbc9 100644 --- a/src/api.go +++ b/src/api.go @@ -383,7 +383,7 @@ func initAPIs(targetMux *http.ServeMux) { authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog) authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog) authRouter.HandleFunc("/api/log/summary", LogViewer.HandleReadLogSummary) - + authRouter.HandleFunc("/api/log/errors", LogViewer.HandleLogErrorSummary) //Debug authRouter.HandleFunc("/api/info/pprof", pprof.Index) } diff --git a/src/mod/info/logger/logger.go b/src/mod/info/logger/logger.go index d71f97e..06b0e45 100644 --- a/src/mod/info/logger/logger.go +++ b/src/mod/info/logger/logger.go @@ -18,9 +18,10 @@ import ( */ type Logger struct { - Prefix string //Prefix for log files - LogFolder string //Folder to store the log file - CurrentLogFile string //Current writing filename + 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 } diff --git a/src/mod/info/logger/rotate.go b/src/mod/info/logger/rotate.go new file mode 100644 index 0000000..c27edcb --- /dev/null +++ b/src/mod/info/logger/rotate.go @@ -0,0 +1,127 @@ +package logger + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "time" +) + +type RotateOption struct { + Enabled bool //Whether log rotation is enabled + 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 +} + +func (l *Logger) LogNeedRotate(filename string) bool { + if !l.RotateOption.Enabled { + return false + } + info, err := os.Stat(filename) + if err != nil { + return false + } + return info.Size() >= l.RotateOption.MaxSize +} + +func (l *Logger) RotateLog() error { + if !l.RotateOption.Enabled { + return nil + } + + needRotate := l.LogNeedRotate(l.CurrentLogFile) + if !needRotate { + return nil + } + + //Close current file + if l.file != nil { + l.file.Close() + } + + // Determine backup directory + backupDir := l.RotateOption.BackupDir + if backupDir == "" { + backupDir = filepath.Dir(l.CurrentLogFile) + } + + // Ensure backup directory exists + if err := os.MkdirAll(backupDir, 0755); err != nil { + return err + } + + // Generate rotated filename with timestamp + timestamp := time.Now().Format("20060102-150405") + baseName := filepath.Base(l.CurrentLogFile) + rotatedName := fmt.Sprintf("%s.%s", baseName, timestamp) + rotatedPath := filepath.Join(backupDir, rotatedName) + + // Rename current log file to rotated file + if err := os.Rename(l.CurrentLogFile, rotatedPath); err != nil { + return err + } + + // Optionally compress the rotated file + if l.RotateOption.Compress { + if err := compressFile(rotatedPath); err != nil { + return err + } + // Remove the uncompressed rotated file after compression + os.Remove(rotatedPath) + rotatedPath += ".gz" + } + + // Remove old backups if exceeding MaxBackups + if l.RotateOption.MaxBackups > 0 { + files, err := filepath.Glob(filepath.Join(backupDir, baseName+".*")) + if err == nil && len(files) > l.RotateOption.MaxBackups { + sort.Slice(files, func(i, j int) bool { + return files[i] < files[j] + }) + for _, old := range files[:len(files)-l.RotateOption.MaxBackups] { + os.Remove(old) + } + } + } + + // Reopen a new log file + file, err := os.OpenFile(l.CurrentLogFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return err + } + l.file = file + + return nil +} + +// compressFile compresses the given file using zip format and creates a .gz file. +func compressFile(filename string) error { + zipFilename := filename + ".gz" + outFile, err := os.Create(zipFilename) + if err != nil { + return err + } + defer outFile.Close() + + zipWriter := zip.NewWriter(outFile) + defer zipWriter.Close() + + fileToCompress, err := os.Open(filename) + if err != nil { + return err + } + defer fileToCompress.Close() + + w, err := zipWriter.Create(filepath.Base(filename)) + if err != nil { + return err + } + + _, err = io.Copy(w, fileToCompress) + return err +} diff --git a/src/mod/info/logviewer/logviewer.go b/src/mod/info/logviewer/logviewer.go index f17d285..fb62c14 100644 --- a/src/mod/info/logviewer/logviewer.go +++ b/src/mod/info/logviewer/logviewer.go @@ -126,6 +126,52 @@ func (v *Viewer) HandleReadLogSummary(w http.ResponseWriter, r *http.Request) { utils.SendJSONResponse(w, summary) } +func (v *Viewer) HandleLogErrorSummary(w http.ResponseWriter, r *http.Request) { + filename, err := utils.GetPara(r, "file") + if err != nil { + utils.SendErrorResponse(w, "invalid filename given") + return + } + + content, err := v.LoadLogFile(strings.TrimSpace(filepath.Base(filename))) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Generate the error summary for log that is request and non 100 - 200 range status code + errorLines := [][]string{} + lines := strings.Split(content, "\n") + for _, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + // Only process router logs with a status code not in 1xx or 2xx + if strings.Contains(line, "[router:") { + //Extract date time from the line + timestamp := "" + if strings.HasPrefix(line, "[") && strings.Contains(line, "]") { + timestamp = line[1:strings.Index(line, "]")] + } + + //Trim out the request metadata + line = line[strings.LastIndex(line, "]")+1:] + fields := strings.Fields(strings.TrimSpace(line)) + + if len(fields) > 0 { + statusStr := fields[2] + if len(statusStr) == 3 && (statusStr[0] != '1' && statusStr[0] != '2' && statusStr[0] != '3') { + fieldsWithTimestamp := append([]string{timestamp}, strings.Fields(strings.TrimSpace(line))...) + errorLines = append(errorLines, fieldsWithTimestamp) + } + } + } + } + + js, _ := json.Marshal(errorLines) + utils.SendJSONResponse(w, string(js)) +} + /* Log Access Functions */ @@ -322,104 +368,3 @@ func (v *Viewer) LoadLogSummary(filename string) (string, error) { return "", errors.New("log file not found") } } - -/* -Log examples: - -[2025-08-18 21:02:15.664246] [router:host-http] [origin:test1.localhost] [client: 127.0.0.1] [useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0] GET /system/file_system/listDirHash?dir=s2%3A%2FMusic%2FMusic%20Bank%2FYear%202025%2F08-2025%2F 200 -[2025-08-18 21:02:20.682091] [router:host-http] [origin:test1.localhost] [client: 127.0.0.1] [useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0] GET /system/file_system/listDirHash?dir=s2%3A%2FMusic%2FMusic%20Bank%2FYear%202025%2F08-2025%2F 200 -[2025-08-18 21:02:20.725569] [internal] [system:info] mDNS scan result updated -[2025-08-17 20:24:38.669488] [uptime-monitor] [system:info] Uptime updated - 1755433478 -[2025-08-17 20:25:08.247535] [internal] [system:info] mDNS scan result updated -[2025-08-17 20:29:38.669187] [uptime-monitor] [system:info] Uptime updated - 1755433778 -[2025-08-17 20:34:38.669090] [uptime-monitor] [system:info] Uptime updated - 1755434078 -[2025-08-17 20:39:38.668610] [uptime-monitor] [system:info] Uptime updated - 1755434378 -[2025-08-17 20:40:08.248890] [internal] [system:info] mDNS scan result updated -[2025-08-17 20:44:38.669058] [uptime-monitor] [system:info] Uptime updated - 1755434678 -[2025-08-17 20:49:38.669340] [uptime-monitor] [system:info] Uptime updated - 1755434978 -[2025-08-17 20:54:38.668785] [uptime-monitor] [system:info] Uptime updated - 1755435278 -[2025-08-17 20:55:08.247715] [internal] [system:info] mDNS scan result updated -[2025-08-17 20:59:38.668575] [uptime-monitor] [system:info] Uptime updated - 1755435578 -[2025-08-17 21:04:38.669637] [uptime-monitor] [system:info] Uptime updated - 1755435878 -[2025-08-17 21:09:38.669109] [uptime-monitor] [system:info] Uptime updated - 1755436178 -[2025-08-17 21:10:08.247618] [internal] [system:info] mDNS scan result updated -[2025-08-17 21:14:38.668828] [uptime-monitor] [system:info] Uptime updated - 1755436478 -[2025-08-17 21:19:38.669091] [uptime-monitor] [system:info] Uptime updated - 1755436778 -[2025-08-17 21:24:38.668830] [uptime-monitor] [system:info] Uptime updated - 1755437078 -[2025-08-17 21:25:08.246931] [internal] [system:info] mDNS scan result updated -[2025-08-17 21:29:38.673217] [uptime-monitor] [system:info] Uptime updated - 1755437378 -[2025-08-17 21:34:38.668883] [uptime-monitor] [system:info] Uptime updated - 1755437678 -[2025-08-17 21:39:38.668980] [uptime-monitor] [system:info] Uptime updated - 1755437978 -[2025-08-17 21:40:08.266062] [internal] [system:info] mDNS scan result updated -[2025-08-17 21:44:38.669150] [uptime-monitor] [system:info] Uptime updated - 1755438278 -[2025-08-17 21:49:38.668640] [uptime-monitor] [system:info] Uptime updated - 1755438578 -[2025-08-17 21:54:38.669275] [uptime-monitor] [system:info] Uptime updated - 1755438878 -[2025-08-17 21:55:08.266425] [internal] [system:info] mDNS scan result updated -[2025-08-17 21:59:38.668861] [uptime-monitor] [system:info] Uptime updated - 1755439178 -[2025-08-17 22:04:38.668840] [uptime-monitor] [system:info] Uptime updated - 1755439478 -[2025-08-17 22:09:38.668798] [uptime-monitor] [system:info] Uptime updated - 1755439778 -[2025-08-17 22:10:08.266417] [internal] [system:info] mDNS scan result updated -[2025-08-17 22:14:38.669122] [uptime-monitor] [system:info] Uptime updated - 1755440078 -[2025-08-17 22:19:38.668810] [uptime-monitor] [system:info] Uptime updated - 1755440378 -[2025-08-17 22:21:35.947519] [netstat] [system:info] Netstats listener stopped -[2025-08-17 22:21:35.947519] [internal] [system:info] Shutting down Zoraxy -[2025-08-17 22:21:35.947519] [internal] [system:info] Closing Netstats Listener -[2025-08-17 22:21:35.970526] [plugin-manager] [system:error] plugin com.example.restful-example encounted a fatal error. Disabling plugin...: exit status 0xc000013a -[2025-08-17 22:21:35.970526] [plugin-manager] [system:error] plugin org.aroz.zoraxy.api_call_example encounted a fatal error. Disabling plugin...: exit status 0xc000013a -[2025-08-17 22:21:36.250929] [internal] [system:info] Closing Statistic Collector -[2025-08-17 22:21:36.318808] [internal] [system:info] Stopping mDNS Discoverer (might take a few minutes) -[2025-08-17 22:21:36.319829] [internal] [system:info] Shutting down load balancer -[2025-08-17 22:21:36.319829] [internal] [system:info] Closing Certificates Auto Renewer -[2025-08-17 22:21:36.319829] [internal] [system:info] Closing Access Controller -[2025-08-17 22:21:36.319829] [internal] [system:info] Shutting down plugin manager -[2025-08-17 22:21:36.319829] [internal] [system:info] Cleaning up tmp files -[2025-08-17 22:21:36.328033] [internal] [system:info] Stopping system database -[2025-08-18 20:31:49.673182] [database] [system:info] Using BoltDB as the database backend -[2025-08-18 20:31:49.784069] [auth] [system:info] Authentication session key loaded from database -[2025-08-18 20:31:50.290804] [LoadBalancer] [system:info] Upstream state cache ticker started -[2025-08-18 20:31:50.510300] [static-webserv] [system:info] Static Web Server started. Listeing on :5487 -[2025-08-18 20:31:51.017433] [internal] [system:info] Starting ACME handler -[2025-08-18 20:31:51.022545] [cert-renew] [system:info] ACME early renew set to 30 days and check interval set to 86400 seconds -[2025-08-18 20:31:51.073031] [plugin-manager] [system:info] Hot reload ticker started -[2025-08-18 20:31:51.357203] [plugin-manager] [system:info] Loaded plugin: API Call Example Plugin -[2025-08-18 20:31:51.357782] [plugin-manager] [system:info] Generated API key for plugin API Call Example Plugin -[2025-08-18 20:31:51.358293] [plugin-manager] [system:info] Starting plugin API Call Example Plugin at :5974 -[2025-08-18 20:31:51.406867] [plugin-manager] [system:info] [API Call Example Plugin:13316] Starting API Call Example Plugin on 127.0.0.1:5974 -[2025-08-18 20:31:51.466380] [plugin-manager] [system:info] Plugin list synced from plugin store -[2025-08-18 20:31:51.662866] [plugin-manager] [system:info] Loaded plugin: Restful Example -[2025-08-18 20:31:51.663383] [plugin-manager] [system:info] Starting plugin Restful Example at :5874 -[2025-08-18 20:31:51.688641] [plugin-manager] [system:info] [Restful Example:10500] Restful-example started at http://127.0.0.1:5874 -[2025-08-18 20:31:51.721309] [plugin-manager] [system:info] Plugin hash generated for: org.aroz.zoraxy.api_call_example -[2025-08-18 20:31:51.777523] [plugin-manager] [system:info] Plugin hash generated for: com.example.restful-example -[2025-08-18 20:31:51.789497] [internal] [system:info] Inbound port not set. Using default (443) -[2025-08-18 20:31:51.789497] [internal] [system:info] TLS mode enabled. Serving proxy request with TLS -[2025-08-18 20:31:51.789497] [internal] [system:info] Development mode enabled. Using no-store Cache Control policy -[2025-08-18 20:31:51.790016] [internal] [system:info] Force latest TLS mode disabled. Minimum TLS version is set to v1.0 -[2025-08-18 20:31:51.790016] [internal] [system:info] Port 80 listener enabled -[2025-08-18 20:31:51.790016] [internal] [system:info] Force HTTPS mode enabled -[2025-08-18 20:31:51.825385] [proxy-config] [system:info] *.yami.localhost -> 192.168.0.16:8080 routing rule loaded -[2025-08-18 20:31:51.833567] [proxy-config] [system:info] a.localhost -> imuslab.com routing rule loaded -[2025-08-18 20:31:51.849358] [proxy-config] [system:info] aroz.localhost -> 192.168.0.16:8080 routing rule loaded -[2025-08-18 20:31:51.852977] [proxy-config] [system:info] auth.localhost -> localhost:5488 routing rule loaded -[2025-08-18 20:31:51.866792] [proxy-config] [system:info] debug.localhost -> dc.imuslab.com:8080 routing rule loaded -[2025-08-18 20:31:51.878091] [proxy-config] [system:info] peer.localhost -> 192.168.0.16:8080 routing rule loaded -[2025-08-18 20:31:51.887843] [proxy-config] [system:info] / -> 127.0.0.1:5487 routing rule loaded -[2025-08-18 20:31:51.895039] [proxy-config] [system:info] test.imuslab.com -> 192.168.1.202:8443 routing rule loaded -[2025-08-18 20:31:51.909917] [proxy-config] [system:info] test.imuslab.internal -> 127.0.0.1:80 routing rule loaded -[2025-08-18 20:31:51.922685] [proxy-config] [system:info] test.localhost -> alanyeung.co routing rule loaded -[2025-08-18 20:31:51.937314] [proxy-config] [system:info] webdav.localhost -> 127.0.0.1:80/redirect routing rule loaded -[2025-08-18 20:31:52.239414] [dprouter] [system:info] Starting HTTP-to-HTTPS redirector (port 80) -[2025-08-18 20:31:52.239414] [internal] [system:info] Dynamic Reverse Proxy service started -[2025-08-18 20:31:52.239414] [dprouter] [system:info] Reverse proxy service started in the background (TLS mode) -[2025-08-18 20:31:52.289262] [internal] [system:info] Zoraxy started. Visit control panel at http://localhost:8000 -[2025-08-18 20:31:52.289262] [internal] [system:info] Assigned temporary port:36951 -[2025-08-18 20:31:54.513995] [internal] [system:info] Uptime Monitor background service started -[2025-08-18 20:32:20.725596] [internal] [system:info] mDNS Startup scan completed -[2025-08-18 20:36:52.239883] [uptime-monitor] [system:info] Uptime updated - 1755520612 -[2025-08-18 20:56:14.160166] [router:host-http] [origin:test1.localhost] [client: 127.0.0.1] [useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0] GET /system/file_system/preference?key=file_explorer/theme 200 -[2025-08-18 20:56:14.160166] [router:host-http] [origin:test1.localhost] [client: 127.0.0.1] [useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0] GET /system/file_system/preference?key=file_explorer/listmode 200 -[2025-08-18 20:56:14.160166] [router:host-http] [origin:test1.localhost] [client: 127.0.0.1] [useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0] GET /system/file_system/preference?key=file_explorer/listmode 200 -[2025-08-18 20:56:14.170270] [router:host-http] [origin:test1.localhost] [client: 127.0.0.1] [useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0] GET /system/file_system/listRoots?user=true 200 -[2025-08-18 20:56:14.171752] [router:host-http] [origin:test1.localhost] [client: 127.0.0.1] [useragent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0] GET /system/id/requestInfo 200 - -*/ diff --git a/src/web/snippet/logview.html b/src/web/snippet/logview.html index 0cfd8fe..17ffe1c 100644 --- a/src/web/snippet/logview.html +++ b/src/web/snippet/logview.html @@ -11,7 +11,6 @@ -
+ - +Welcome to LogVPro! Use the left menu to select a log file
-No error data loaded
+