nginx_log_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. package nginx_log
  2. import (
  3. "strings"
  4. "testing"
  5. )
  6. // TestScanForLogDirectivesRemoval tests that removed log directives are properly cleaned up
  7. func TestScanForLogDirectivesRemoval(t *testing.T) {
  8. // Clear cache before test
  9. ClearLogCache()
  10. configPath := "/etc/nginx/sites-available/test.conf"
  11. // First scan with two log directives
  12. content1 := []byte(`
  13. server {
  14. listen 80;
  15. server_name example.com;
  16. access_log /var/log/nginx/access.log;
  17. error_log /var/log/nginx/error.log;
  18. }
  19. `)
  20. err := scanForLogDirectives(configPath, content1)
  21. if err != nil {
  22. t.Fatalf("First scan failed: %v", err)
  23. }
  24. // Check that both logs are cached
  25. logs := GetAllLogPaths()
  26. if len(logs) != 2 {
  27. t.Fatalf("Expected 2 logs after first scan, got %d", len(logs))
  28. }
  29. // Verify the config file is tracked
  30. accessFound := false
  31. errorFound := false
  32. for _, log := range logs {
  33. if log.ConfigFile != configPath {
  34. t.Errorf("Expected config file %s, got %s", configPath, log.ConfigFile)
  35. }
  36. if log.Type == "access" {
  37. accessFound = true
  38. }
  39. if log.Type == "error" {
  40. errorFound = true
  41. }
  42. }
  43. if !accessFound || !errorFound {
  44. t.Error("Expected both access and error logs to be found")
  45. }
  46. // Second scan with only one log directive (error_log removed)
  47. content2 := []byte(`
  48. server {
  49. listen 80;
  50. server_name example.com;
  51. access_log /var/log/nginx/access.log;
  52. }
  53. `)
  54. err = scanForLogDirectives(configPath, content2)
  55. if err != nil {
  56. t.Fatalf("Second scan failed: %v", err)
  57. }
  58. // Check that only access log remains
  59. logs = GetAllLogPaths()
  60. if len(logs) != 1 {
  61. t.Fatalf("Expected 1 log after second scan, got %d", len(logs))
  62. }
  63. if logs[0].Type != "access" {
  64. t.Errorf("Expected remaining log to be access log, got %s", logs[0].Type)
  65. }
  66. // Third scan with no log directives
  67. content3 := []byte(`
  68. server {
  69. listen 80;
  70. server_name example.com;
  71. }
  72. `)
  73. err = scanForLogDirectives(configPath, content3)
  74. if err != nil {
  75. t.Fatalf("Third scan failed: %v", err)
  76. }
  77. // Check that no logs remain
  78. logs = GetAllLogPaths()
  79. if len(logs) != 0 {
  80. t.Fatalf("Expected 0 logs after third scan, got %d", len(logs))
  81. }
  82. }
  83. // TestScanForLogDirectivesMultipleConfigs tests that logs from different config files are handled independently
  84. func TestScanForLogDirectivesMultipleConfigs(t *testing.T) {
  85. // Clear cache before test
  86. ClearLogCache()
  87. configPath1 := "/etc/nginx/sites-available/site1.conf"
  88. configPath2 := "/etc/nginx/sites-available/site2.conf"
  89. // Scan first config
  90. content1 := []byte(`
  91. server {
  92. listen 80;
  93. server_name site1.com;
  94. access_log /var/log/nginx/site1_access.log;
  95. }
  96. `)
  97. err := scanForLogDirectives(configPath1, content1)
  98. if err != nil {
  99. t.Fatalf("First config scan failed: %v", err)
  100. }
  101. // Scan second config
  102. content2 := []byte(`
  103. server {
  104. listen 80;
  105. server_name site2.com;
  106. access_log /var/log/nginx/site2_access.log;
  107. }
  108. `)
  109. err = scanForLogDirectives(configPath2, content2)
  110. if err != nil {
  111. t.Fatalf("Second config scan failed: %v", err)
  112. }
  113. // Should have 2 logs total
  114. logs := GetAllLogPaths()
  115. if len(logs) != 2 {
  116. t.Fatalf("Expected 2 logs from 2 configs, got %d", len(logs))
  117. }
  118. // Remove log from first config, should only affect that config
  119. emptyContent := []byte(`
  120. server {
  121. listen 80;
  122. server_name site1.com;
  123. }
  124. `)
  125. err = scanForLogDirectives(configPath1, emptyContent)
  126. if err != nil {
  127. t.Fatalf("Empty config scan failed: %v", err)
  128. }
  129. // Should have 1 log remaining (from config2)
  130. logs = GetAllLogPaths()
  131. if len(logs) != 1 {
  132. t.Fatalf("Expected 1 log after removing from config1, got %d", len(logs))
  133. }
  134. if logs[0].ConfigFile != configPath2 {
  135. t.Errorf("Expected remaining log to be from config2 (%s), got %s", configPath2, logs[0].ConfigFile)
  136. }
  137. }
  138. // TestScanForLogDirectivesIgnoreComments tests that commented log directives are ignored
  139. func TestScanForLogDirectivesIgnoreComments(t *testing.T) {
  140. // Clear cache before test
  141. ClearLogCache()
  142. configPath := "/etc/nginx/sites-available/test.conf"
  143. // Content with both active and commented log directives
  144. content := []byte(`
  145. server {
  146. listen 80;
  147. server_name example.com;
  148. # This is a commented access log - should be ignored
  149. # access_log /var/log/nginx/commented_access.log;
  150. # Multi-line comment block
  151. #error_log /var/log/nginx/commented_error.log;
  152. # Active log directives (not commented)
  153. access_log /var/log/nginx/active_access.log;
  154. error_log /var/log/nginx/active_error.log;
  155. # Another commented directive with indentation
  156. # access_log /var/log/nginx/indented_comment.log;
  157. # Inline comment after directive should still work
  158. access_log /var/log/nginx/inline_comment.log; # this is active with comment
  159. }
  160. `)
  161. err := scanForLogDirectives(configPath, content)
  162. if err != nil {
  163. t.Fatalf("Scan failed: %v", err)
  164. }
  165. // Should only find 3 active log directives (not the commented ones)
  166. logs := GetAllLogPaths()
  167. expectedCount := 3
  168. if len(logs) != expectedCount {
  169. t.Fatalf("Expected %d logs, got %d. Logs found: %+v", expectedCount, len(logs), logs)
  170. }
  171. // Verify the correct paths were found
  172. expectedPaths := map[string]bool{
  173. "/var/log/nginx/active_access.log": false,
  174. "/var/log/nginx/active_error.log": false,
  175. "/var/log/nginx/inline_comment.log": false,
  176. }
  177. for _, log := range logs {
  178. if _, exists := expectedPaths[log.Path]; !exists {
  179. t.Errorf("Unexpected log path found: %s", log.Path)
  180. } else {
  181. expectedPaths[log.Path] = true
  182. }
  183. }
  184. // Check that all expected paths were found
  185. for path, found := range expectedPaths {
  186. if !found {
  187. t.Errorf("Expected log path not found: %s", path)
  188. }
  189. }
  190. // Verify no commented paths were included
  191. commentedPaths := []string{
  192. "/var/log/nginx/commented_access.log",
  193. "/var/log/nginx/commented_error.log",
  194. "/var/log/nginx/indented_comment.log",
  195. }
  196. for _, log := range logs {
  197. for _, commentedPath := range commentedPaths {
  198. if log.Path == commentedPath {
  199. t.Errorf("Commented log path should not be included: %s", commentedPath)
  200. }
  201. }
  202. }
  203. }
  204. // TestLogDirectiveRegex tests the regex pattern and comment filtering logic
  205. func TestLogDirectiveRegex(t *testing.T) {
  206. testCases := []struct {
  207. name string
  208. content string
  209. expectedActive int // number of active (non-commented) matches expected
  210. }{
  211. {
  212. name: "Active directives",
  213. content: "access_log /var/log/nginx/access.log;\nerror_log /var/log/nginx/error.log;",
  214. expectedActive: 2,
  215. },
  216. {
  217. name: "Commented directives",
  218. content: "# access_log /var/log/nginx/access.log;\n#error_log /var/log/nginx/error.log;",
  219. expectedActive: 0,
  220. },
  221. {
  222. name: "Mixed active and commented",
  223. content: "access_log /var/log/nginx/access.log;\n# error_log /var/log/nginx/error.log;",
  224. expectedActive: 1,
  225. },
  226. {
  227. name: "Indented comments",
  228. content: " # access_log /var/log/nginx/access.log;\n error_log /var/log/nginx/error.log;",
  229. expectedActive: 1,
  230. },
  231. {
  232. name: "Inline comments after directive",
  233. content: "access_log /var/log/nginx/access.log; # this is a comment",
  234. expectedActive: 1,
  235. },
  236. }
  237. for _, tc := range testCases {
  238. t.Run(tc.name, func(t *testing.T) {
  239. // Find all matches using the regex
  240. matches := logDirectiveRegex.FindAllSubmatch([]byte(tc.content), -1)
  241. // Count how many are not commented
  242. activeCount := 0
  243. for _, match := range matches {
  244. if !isCommentedMatch([]byte(tc.content), match) {
  245. activeCount++
  246. }
  247. }
  248. if activeCount != tc.expectedActive {
  249. t.Errorf("Test '%s': expected %d active matches, got %d. Content: %s",
  250. tc.name, tc.expectedActive, activeCount, tc.content)
  251. }
  252. })
  253. }
  254. }
  255. // TestAccessLogOff tests that "access_log off;" directives are properly ignored
  256. func TestAccessLogOff(t *testing.T) {
  257. // Clear cache before test
  258. ClearLogCache()
  259. configPath := "/etc/nginx/sites-available/test.conf"
  260. // Content with various access_log directives including "off"
  261. content := []byte(`
  262. server {
  263. listen 80;
  264. server_name example.com;
  265. # Normal access log
  266. access_log /var/log/nginx/access.log;
  267. # Disabled access log - should be ignored
  268. access_log off;
  269. # Another normal access log with format
  270. access_log /var/log/nginx/custom.log combined;
  271. # Error log should work normally
  272. error_log /var/log/nginx/error.log;
  273. # Error log can also be turned off
  274. error_log off;
  275. }
  276. `)
  277. err := scanForLogDirectives(configPath, content)
  278. if err != nil {
  279. t.Fatalf("Scan failed: %v", err)
  280. }
  281. // Should only find 3 valid log paths (excluding "off" directives)
  282. logs := GetAllLogPaths()
  283. expectedCount := 3
  284. if len(logs) != expectedCount {
  285. t.Fatalf("Expected %d logs, got %d. Logs found: %+v", expectedCount, len(logs), logs)
  286. }
  287. // Verify the correct paths were found
  288. expectedPaths := map[string]string{
  289. "/var/log/nginx/access.log": "access",
  290. "/var/log/nginx/custom.log": "access",
  291. "/var/log/nginx/error.log": "error",
  292. }
  293. for _, log := range logs {
  294. expectedType, exists := expectedPaths[log.Path]
  295. if !exists {
  296. t.Errorf("Unexpected log path found: %s", log.Path)
  297. } else if log.Type != expectedType {
  298. t.Errorf("Expected log type %s for path %s, got %s", expectedType, log.Path, log.Type)
  299. }
  300. delete(expectedPaths, log.Path)
  301. }
  302. // Check that all expected paths were found
  303. for path := range expectedPaths {
  304. t.Errorf("Expected log path not found: %s", path)
  305. }
  306. // Verify "off" was not treated as a path
  307. for _, log := range logs {
  308. if log.Path == "off" || strings.Contains(log.Path, "off") {
  309. t.Errorf("'off' should not be treated as a log path: %s", log.Path)
  310. }
  311. }
  312. }
  313. // TestIsCommentedMatch tests the isCommentedMatch function directly
  314. func TestIsCommentedMatch(t *testing.T) {
  315. testCases := []struct {
  316. name string
  317. content string
  318. matchStr string
  319. isCommented bool
  320. }{
  321. {
  322. name: "Not commented",
  323. content: "access_log /var/log/nginx/access.log;",
  324. matchStr: "access_log /var/log/nginx/access.log;",
  325. isCommented: false,
  326. },
  327. {
  328. name: "Commented with #",
  329. content: "# access_log /var/log/nginx/access.log;",
  330. matchStr: "access_log /var/log/nginx/access.log;",
  331. isCommented: true,
  332. },
  333. {
  334. name: "Commented with spaces and #",
  335. content: " # access_log /var/log/nginx/access.log;",
  336. matchStr: "access_log /var/log/nginx/access.log;",
  337. isCommented: true,
  338. },
  339. {
  340. name: "Not commented with spaces",
  341. content: " access_log /var/log/nginx/access.log;",
  342. matchStr: "access_log /var/log/nginx/access.log;",
  343. isCommented: false,
  344. },
  345. {
  346. name: "Inline comment after directive",
  347. content: "access_log /var/log/nginx/access.log; # comment",
  348. matchStr: "access_log /var/log/nginx/access.log;",
  349. isCommented: false,
  350. },
  351. }
  352. for _, tc := range testCases {
  353. t.Run(tc.name, func(t *testing.T) {
  354. // Create a fake match to test with
  355. match := [][]byte{[]byte(tc.matchStr)}
  356. result := isCommentedMatch([]byte(tc.content), match)
  357. if result != tc.isCommented {
  358. t.Errorf("Test '%s': expected isCommented=%v, got %v. Content: %q, Match: %q",
  359. tc.name, tc.isCommented, result, tc.content, tc.matchStr)
  360. }
  361. })
  362. }
  363. }