Added wip log rotate feature

- Added log rotate function interface
- Added darwin amd64 support in make file (Intel Macs)
- Added log summary and error API
This commit is contained in:
Toby Chui
2025-08-31 22:22:45 +08:00
parent d9fd38260f
commit c3afdefe45
6 changed files with 503 additions and 400 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
*/