nginx_log.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package nginx_log
  2. import (
  3. "path/filepath"
  4. "regexp"
  5. "strings"
  6. "time"
  7. "github.com/0xJacky/Nginx-UI/internal/cache"
  8. "github.com/0xJacky/Nginx-UI/internal/nginx"
  9. "github.com/0xJacky/Nginx-UI/internal/nginx_log/utils"
  10. "github.com/uozi-tech/cosy/logger"
  11. )
  12. // Regular expression for log directives - matches access_log or error_log
  13. var (
  14. logDirectiveRegex = regexp.MustCompile(`(?m)(access_log|error_log)\s+([^\s;]+)(?:\s+[^;]+)?;`)
  15. )
  16. // Use init function to automatically register callback
  17. func init() {
  18. // Register the callback directly with the global registry
  19. cache.RegisterCallback("nginx_log.scanForLogDirectives", scanForLogDirectives)
  20. }
  21. // scanForLogDirectives scans and parses configuration files for log directives
  22. func scanForLogDirectives(configPath string, content []byte) error {
  23. // Step 1: Get nginx prefix
  24. prefix := nginx.GetPrefix()
  25. // Step 2: Remove existing log paths - with timeout protection
  26. removeSuccess := make(chan bool, 1)
  27. go func() {
  28. defer func() {
  29. if r := recover(); r != nil {
  30. logger.Warnf("[scanForLogDirectives] RemoveLogPathsFromConfig panicked for %s: %v", configPath, r)
  31. }
  32. }()
  33. RemoveLogPathsFromConfig(configPath)
  34. removeSuccess <- true
  35. }()
  36. select {
  37. case <-removeSuccess:
  38. // Success - no logging needed
  39. case <-time.After(2 * time.Second):
  40. logger.Warnf("[scanForLogDirectives] RemoveLogPathsFromConfig timed out after 2 seconds for config: %s", configPath)
  41. }
  42. // Step 3: Find log directives using regex
  43. matches := logDirectiveRegex.FindAllSubmatch(content, -1)
  44. // Step 4: Parse log paths
  45. for i, match := range matches {
  46. if len(match) >= 3 {
  47. // Check if this match is from a commented line
  48. if isCommentedMatch(content, match) {
  49. continue // Skip commented directives
  50. }
  51. directiveType := string(match[1]) // "access_log" or "error_log"
  52. logPath := string(match[2]) // Path to log file
  53. // Skip if log is disabled with "off"
  54. if logPath == "off" {
  55. continue
  56. }
  57. // Handle relative paths by joining with nginx prefix
  58. if !filepath.IsAbs(logPath) {
  59. logPath = filepath.Join(prefix, logPath)
  60. }
  61. // Validate log path
  62. if utils.IsValidLogPath(logPath) {
  63. logType := "access"
  64. if directiveType == "error_log" {
  65. logType = "error"
  66. }
  67. // Add to cache with config file path - with timeout protection
  68. addSuccess := make(chan bool, 1)
  69. go func() {
  70. defer func() {
  71. if r := recover(); r != nil {
  72. logger.Warnf("[scanForLogDirectives] AddLogPath panicked for match %d, path %s: %v", i, logPath, r)
  73. }
  74. }()
  75. AddLogPath(logPath, logType, filepath.Base(logPath), configPath)
  76. addSuccess <- true
  77. }()
  78. select {
  79. case <-addSuccess:
  80. // Success - no logging needed
  81. case <-time.After(1 * time.Second):
  82. logger.Warnf("[scanForLogDirectives] AddLogPath timed out after 1 second for match %d, path %s", i, logPath)
  83. }
  84. }
  85. }
  86. }
  87. return nil
  88. }
  89. // isCommentedMatch checks if a regex match is from a commented line
  90. func isCommentedMatch(content []byte, match [][]byte) bool {
  91. // Find the position of the match in the content
  92. matchStr := string(match[0])
  93. matchIndex := strings.Index(string(content), matchStr)
  94. if matchIndex == -1 {
  95. return false
  96. }
  97. // Find the start of the line containing this match
  98. lineStart := matchIndex
  99. for lineStart > 0 && content[lineStart-1] != '\n' {
  100. lineStart--
  101. }
  102. // Check if the line starts with # (possibly with leading whitespace)
  103. for i := lineStart; i < matchIndex; i++ {
  104. char := content[i]
  105. if char == '#' {
  106. return true // This is a commented line
  107. }
  108. if char != ' ' && char != '\t' {
  109. return false // Found non-whitespace before the directive, not a comment
  110. }
  111. }
  112. return false
  113. }