preflight.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package nginx_log
  2. import (
  3. "fmt"
  4. "os"
  5. "github.com/0xJacky/Nginx-UI/internal/event"
  6. "github.com/0xJacky/Nginx-UI/internal/nginx"
  7. "github.com/0xJacky/Nginx-UI/internal/nginx_log/indexer"
  8. "github.com/0xJacky/Nginx-UI/internal/nginx_log/searcher"
  9. "github.com/0xJacky/Nginx-UI/internal/nginx_log/utils"
  10. "github.com/uozi-tech/cosy/logger"
  11. )
  12. // FileInfo represents basic file information for internal use
  13. type FileInfo struct {
  14. Exists bool `json:"exists"`
  15. Readable bool `json:"readable"`
  16. Size int64 `json:"size,omitempty"`
  17. LastModified int64 `json:"last_modified,omitempty"`
  18. }
  19. // TimeRange represents a time range for log data for internal use
  20. type TimeRange struct {
  21. Start int64 `json:"start"`
  22. End int64 `json:"end"`
  23. }
  24. // PreflightResponse represents the response from preflight checks for internal use
  25. type PreflightResponse struct {
  26. Available bool `json:"available"`
  27. IndexStatus string `json:"index_status"`
  28. Message string `json:"message,omitempty"`
  29. TimeRange *TimeRange `json:"time_range,omitempty"`
  30. FileInfo *FileInfo `json:"file_info,omitempty"`
  31. }
  32. // Preflight handles preflight checks for log files
  33. type Preflight struct{}
  34. // NewPreflight creates a new preflight service
  35. func NewPreflight() *Preflight {
  36. return &Preflight{}
  37. }
  38. // CheckLogPreflight performs preflight checks for a log file
  39. func (ps *Preflight) CheckLogPreflight(logPath string) (*PreflightResponse, error) {
  40. // Use default access log path if logPath is empty
  41. if logPath == "" {
  42. defaultLogPath := nginx.GetAccessLogPath()
  43. if defaultLogPath != "" {
  44. logPath = defaultLogPath
  45. logger.Debugf("Using default access log path for preflight: %s", logPath)
  46. }
  47. }
  48. // Get searcher to check index status
  49. searcherService := GetSearcher()
  50. if searcherService == nil {
  51. return nil, ErrModernSearcherNotAvailable
  52. }
  53. // Check if the specific file is currently being indexed
  54. processingManager := event.GetProcessingStatusManager()
  55. currentStatus := processingManager.GetCurrentStatus()
  56. // First check if the file exists and get file info
  57. var fileInfo *os.FileInfo
  58. if logPath != "" {
  59. // Validate log path before accessing it
  60. if !utils.IsValidLogPath(logPath) {
  61. return &PreflightResponse{
  62. Available: false,
  63. IndexStatus: string(indexer.IndexStatusError),
  64. Message: fmt.Sprintf("Invalid log path: %s", logPath),
  65. FileInfo: &FileInfo{
  66. Exists: false,
  67. Readable: false,
  68. },
  69. }, nil
  70. }
  71. if stat, err := os.Stat(logPath); os.IsNotExist(err) {
  72. // File doesn't exist - check for historical data
  73. return ps.handleMissingFile(logPath, searcherService)
  74. } else if err != nil {
  75. // Permission or other file system error - map to error status
  76. return &PreflightResponse{
  77. Available: false,
  78. IndexStatus: string(indexer.IndexStatusError),
  79. Message: fmt.Sprintf("Cannot access log file %s: %v", logPath, err),
  80. FileInfo: &FileInfo{
  81. Exists: true,
  82. Readable: false,
  83. },
  84. }, nil
  85. } else {
  86. fileInfo = &stat
  87. }
  88. }
  89. // Check if searcher is healthy
  90. searcherHealthy := searcherService.IsHealthy()
  91. // Get detailed file status from log file manager
  92. return ps.buildPreflightResponse(logPath, fileInfo, searcherHealthy, &currentStatus)
  93. }
  94. // handleMissingFile handles the case when a log file doesn't exist
  95. func (ps *Preflight) handleMissingFile(logPath string, searcherService *searcher.Searcher) (*PreflightResponse, error) {
  96. searcherHealthy := searcherService.IsHealthy()
  97. logFileManager := GetLogFileManager()
  98. if logFileManager != nil {
  99. logGroup, err := logFileManager.GetLogByPath(logPath)
  100. if err == nil && logGroup != nil && logGroup.LastIndexed > 0 {
  101. // File has historical index data
  102. response := &PreflightResponse{
  103. Available: searcherHealthy,
  104. IndexStatus: string(indexer.IndexStatusIndexed),
  105. Message: "File indexed (historical data available)",
  106. FileInfo: &FileInfo{
  107. Exists: false,
  108. Readable: false,
  109. },
  110. }
  111. if logGroup.HasTimeRange {
  112. response.TimeRange = &TimeRange{
  113. Start: logGroup.TimeRangeStart,
  114. End: logGroup.TimeRangeEnd,
  115. }
  116. }
  117. return response, nil
  118. }
  119. }
  120. // File doesn't exist and no historical data
  121. return &PreflightResponse{
  122. Available: false,
  123. IndexStatus: string(indexer.IndexStatusNotIndexed),
  124. Message: "Log file does not exist",
  125. FileInfo: &FileInfo{
  126. Exists: false,
  127. Readable: false,
  128. },
  129. }, nil
  130. }
  131. // buildPreflightResponse builds the preflight response for existing files
  132. func (ps *Preflight) buildPreflightResponse(logPath string, fileInfo *os.FileInfo, searcherHealthy bool, currentStatus *event.ProcessingStatusData) (*PreflightResponse, error) {
  133. logFileManager := GetLogFileManager()
  134. var indexStatus string = string(indexer.IndexStatusNotIndexed)
  135. var available bool = false
  136. response := &PreflightResponse{}
  137. if logFileManager != nil && logPath != "" {
  138. logGroup, err := logFileManager.GetLogByPath(logPath)
  139. if err == nil && logGroup != nil {
  140. // Determine status based on indexing state
  141. if logGroup.LastIndexed > 0 {
  142. indexStatus = string(indexer.IndexStatusIndexed)
  143. available = searcherHealthy
  144. } else if currentStatus.NginxLogIndexing {
  145. indexStatus = string(indexer.IndexStatusIndexing)
  146. available = false
  147. } else {
  148. indexStatus = string(indexer.IndexStatusNotIndexed)
  149. available = false
  150. }
  151. response.Available = available
  152. response.IndexStatus = indexStatus
  153. // Add time range if available
  154. if logGroup.HasTimeRange {
  155. response.TimeRange = &TimeRange{
  156. Start: logGroup.TimeRangeStart,
  157. End: logGroup.TimeRangeEnd,
  158. }
  159. }
  160. } else {
  161. // File not in database or error getting it
  162. if currentStatus.NginxLogIndexing {
  163. indexStatus = string(indexer.IndexStatusQueued)
  164. } else {
  165. indexStatus = string(indexer.IndexStatusNotIndexed)
  166. }
  167. available = false
  168. response.Available = available
  169. response.IndexStatus = indexStatus
  170. response.Message = "Log file not indexed yet"
  171. }
  172. } else {
  173. // Fallback to basic status
  174. response.Available = searcherHealthy
  175. response.IndexStatus = string(indexer.IndexStatusNotIndexed)
  176. }
  177. // Add file information if available
  178. if fileInfo != nil {
  179. response.FileInfo = &FileInfo{
  180. Exists: true,
  181. Readable: true,
  182. Size: (*fileInfo).Size(),
  183. LastModified: (*fileInfo).ModTime().Unix(),
  184. }
  185. }
  186. logger.Debugf("Preflight response: log_path=%s, available=%v, index_status=%s",
  187. logPath, response.Available, response.IndexStatus)
  188. return response, nil
  189. }