package nginx_log import ( "fmt" "os" "path/filepath" "regexp" "github.com/0xJacky/Nginx-UI/internal/cache" "github.com/0xJacky/Nginx-UI/internal/helper" "github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/settings" "github.com/uozi-tech/cosy/logger" ) // Regular expression for log directives - matches access_log or error_log var logDirectiveRegex = regexp.MustCompile(`(?m)(access_log|error_log)\s+([^\s;]+)(?:\s+[^;]+)?;`) // Use init function to automatically register callback func init() { // Register the callback directly with the global registry cache.RegisterCallback(scanForLogDirectives) } // scanForLogDirectives scans and parses configuration files for log directives func scanForLogDirectives(configPath string, content []byte) error { // Find log directives using regex matches := logDirectiveRegex.FindAllSubmatch(content, -1) // Parse log paths for _, match := range matches { if len(match) >= 3 { directiveType := string(match[1]) // "access_log" or "error_log" logPath := string(match[2]) // Path to log file // Validate log path if IsLogPathUnderWhiteList(logPath) && isValidLogPath(logPath) { logType := "access" if directiveType == "error_log" { logType = "error" } // Add to cache AddLogPath(logPath, logType, filepath.Base(logPath)) } } } return nil } // GetAllLogs returns all log paths func GetAllLogs(filters ...func(*NginxLogCache) bool) []*NginxLogCache { return GetAllLogPaths(filters...) } // isValidLogPath checks if a log path is valid: // 1. It must be a regular file or a symlink to a regular file // 2. It must not point to a console or special device // 3. It must be under the whitelist directories func isValidLogPath(logPath string) bool { // First check if the path is in the whitelist if !IsLogPathUnderWhiteList(logPath) { logger.Warn("Log path is not under whitelist:", logPath) return false } // Check if the path exists fileInfo, err := os.Lstat(logPath) if err != nil { // If the file doesn't exist, it might be created later // We'll assume it's valid for now return true } // If it's a symlink, follow it if fileInfo.Mode()&os.ModeSymlink != 0 { linkTarget, err := os.Readlink(logPath) if err != nil { return false } // Make the link target path absolute if it's relative if !filepath.IsAbs(linkTarget) { linkTarget = filepath.Join(filepath.Dir(logPath), linkTarget) } // Check the target file targetInfo, err := os.Stat(linkTarget) if err != nil { return false } // Only accept regular files as targets return targetInfo.Mode().IsRegular() } // For non-symlinks, just check if it's a regular file return fileInfo.Mode().IsRegular() } // IsLogPathUnderWhiteList checks if a log path is under one of the paths in LogDirWhiteList func IsLogPathUnderWhiteList(path string) bool { cacheKey := fmt.Sprintf("isLogPathUnderWhiteList:%s", path) res, ok := cache.Get(cacheKey) // Deep copy the whitelist logDirWhiteList := append([]string{}, settings.NginxSettings.LogDirWhiteList...) accessLogPath := nginx.GetAccessLogPath() errorLogPath := nginx.GetErrorLogPath() if accessLogPath != "" { logDirWhiteList = append(logDirWhiteList, filepath.Dir(accessLogPath)) } if errorLogPath != "" { logDirWhiteList = append(logDirWhiteList, filepath.Dir(errorLogPath)) } // No cache, check it if !ok { for _, whitePath := range logDirWhiteList { if helper.IsUnderDirectory(path, whitePath) { cache.Set(cacheKey, true, 0) return true } } return false } return res.(bool) }