log_path_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. package nginx
  2. import (
  3. "path/filepath"
  4. "regexp"
  5. "testing"
  6. )
  7. // Mock nginx -T output for testing purposes
  8. const mockNginxTOutput = `
  9. # configuration file /etc/nginx/nginx.conf:
  10. user nginx;
  11. worker_processes auto;
  12. error_log /var/log/nginx/error.log notice;
  13. error_log /var/log/nginx/error.local.log notice;
  14. pid /var/run/nginx.pid;
  15. events {
  16. worker_connections 1024;
  17. }
  18. http {
  19. include /etc/nginx/mime.types;
  20. default_type application/octet-stream;
  21. log_format main '$remote_addr - $remote_user [$time_local] "$request" '
  22. '$status $body_bytes_sent "$http_referer" '
  23. '"$http_user_agent" "$http_x_forwarded_for"';
  24. access_log /var/log/nginx/access.log main;
  25. access_log /var/log/nginx/access.local.log main;
  26. sendfile on;
  27. keepalive_timeout 65;
  28. gzip on;
  29. server {
  30. listen 80;
  31. server_name localhost;
  32. access_log /var/log/nginx/server.access.log;
  33. error_log /var/log/nginx/server.error.log warn;
  34. location / {
  35. root /usr/share/nginx/html;
  36. index index.html index.htm;
  37. }
  38. }
  39. }
  40. stream {
  41. error_log /var/log/nginx/stream.error.log info;
  42. server {
  43. listen 3306;
  44. proxy_pass backend;
  45. }
  46. }
  47. `
  48. // Mock nginx -T output with relative paths
  49. const mockNginxTOutputRelative = `
  50. # configuration file /etc/nginx/nginx.conf:
  51. user nginx;
  52. worker_processes auto;
  53. error_log logs/error.log notice;
  54. pid /var/run/nginx.pid;
  55. http {
  56. access_log logs/access.log main;
  57. server {
  58. listen 80;
  59. server_name localhost;
  60. access_log logs/server.access.log;
  61. error_log logs/server.error.log warn;
  62. }
  63. }
  64. `
  65. // Mock nginx -T output with access_log off
  66. const mockNginxTOutputOff = `
  67. # configuration file /etc/nginx/nginx.conf:
  68. user nginx;
  69. worker_processes auto;
  70. error_log /var/log/nginx/error.log notice;
  71. http {
  72. access_log off;
  73. server {
  74. listen 80;
  75. server_name localhost;
  76. access_log /var/log/nginx/server.access.log;
  77. error_log /var/log/nginx/server.error.log warn;
  78. }
  79. }
  80. `
  81. // Mock nginx -T output with commented log directives
  82. const mockNginxTOutputCommented = `
  83. # configuration file /etc/nginx/nginx.conf:
  84. user nginx;
  85. worker_processes auto;
  86. # error_log /var/log/nginx/commented.error.log notice;
  87. error_log /var/log/nginx/error.log notice;
  88. http {
  89. # access_log /var/log/nginx/commented.access.log main;
  90. access_log /var/log/nginx/access.log main;
  91. server {
  92. listen 80;
  93. server_name localhost;
  94. # access_log /var/log/nginx/commented.server.access.log;
  95. access_log /var/log/nginx/server.access.log;
  96. # error_log /var/log/nginx/commented.server.error.log warn;
  97. error_log /var/log/nginx/server.error.log warn;
  98. }
  99. }
  100. `
  101. func TestAccessLogRegexParsing(t *testing.T) {
  102. testCases := []struct {
  103. name string
  104. nginxTOutput string
  105. expectedPath string
  106. shouldHaveLog bool
  107. }{
  108. {
  109. name: "standard access log",
  110. nginxTOutput: "access_log /var/log/nginx/access.log main;",
  111. expectedPath: "/var/log/nginx/access.log",
  112. shouldHaveLog: true,
  113. },
  114. {
  115. name: "access log turned off",
  116. nginxTOutput: "access_log off;",
  117. expectedPath: "",
  118. shouldHaveLog: false,
  119. },
  120. {
  121. name: "no access log directive",
  122. nginxTOutput: "server_name localhost;",
  123. expectedPath: "",
  124. shouldHaveLog: false,
  125. },
  126. {
  127. name: "indented access log",
  128. nginxTOutput: " access_log /var/log/nginx/server.log;",
  129. expectedPath: "/var/log/nginx/server.log",
  130. shouldHaveLog: true,
  131. },
  132. {
  133. name: "multiple access logs - should get first",
  134. nginxTOutput: "access_log /var/log/nginx/access1.log main;\naccess_log /var/log/nginx/access2.log combined;",
  135. expectedPath: "/var/log/nginx/access1.log",
  136. shouldHaveLog: true,
  137. },
  138. {
  139. name: "commented access log should be ignored",
  140. nginxTOutput: "# access_log /var/log/nginx/commented.access.log main;\naccess_log /var/log/nginx/access.log main;",
  141. expectedPath: "/var/log/nginx/access.log",
  142. shouldHaveLog: true,
  143. },
  144. {
  145. name: "only commented access log",
  146. nginxTOutput: "# access_log /var/log/nginx/commented.access.log main;",
  147. expectedPath: "",
  148. shouldHaveLog: false,
  149. },
  150. }
  151. accessLogRegex := regexp.MustCompile(AccessLogRegexPattern)
  152. for _, tc := range testCases {
  153. t.Run(tc.name, func(t *testing.T) {
  154. matches := accessLogRegex.FindAllStringSubmatch(tc.nginxTOutput, -1)
  155. if !tc.shouldHaveLog {
  156. if len(matches) > 0 {
  157. // Check if it's the "off" directive
  158. if len(matches[0]) >= 2 {
  159. logPath := matches[0][1]
  160. if logPath != "off" {
  161. t.Errorf("Expected no valid access log, but found: %s", logPath)
  162. }
  163. }
  164. }
  165. return
  166. }
  167. if len(matches) == 0 {
  168. t.Errorf("Expected to find access log directive, but found none")
  169. return
  170. }
  171. if len(matches[0]) < 2 {
  172. t.Errorf("Expected regex match to have at least 2 groups, got %d", len(matches[0]))
  173. return
  174. }
  175. logPath := matches[0][1]
  176. if logPath != tc.expectedPath {
  177. t.Errorf("Expected access log path %s, got %s", tc.expectedPath, logPath)
  178. }
  179. })
  180. }
  181. }
  182. func TestErrorLogRegexParsing(t *testing.T) {
  183. testCases := []struct {
  184. name string
  185. nginxTOutput string
  186. expectedPath string
  187. shouldHaveLog bool
  188. }{
  189. {
  190. name: "standard error log",
  191. nginxTOutput: "error_log /var/log/nginx/error.log notice;",
  192. expectedPath: "/var/log/nginx/error.log",
  193. shouldHaveLog: true,
  194. },
  195. {
  196. name: "error log without level",
  197. nginxTOutput: "error_log /var/log/nginx/error.log;",
  198. expectedPath: "/var/log/nginx/error.log",
  199. shouldHaveLog: true,
  200. },
  201. {
  202. name: "no error log directive",
  203. nginxTOutput: "server_name localhost;",
  204. expectedPath: "",
  205. shouldHaveLog: false,
  206. },
  207. {
  208. name: "indented error log",
  209. nginxTOutput: " error_log /var/log/nginx/server.error.log warn;",
  210. expectedPath: "/var/log/nginx/server.error.log",
  211. shouldHaveLog: true,
  212. },
  213. {
  214. name: "multiple error logs - should get first",
  215. nginxTOutput: "error_log /var/log/nginx/error1.log notice;\nerror_log /var/log/nginx/error2.log warn;",
  216. expectedPath: "/var/log/nginx/error1.log",
  217. shouldHaveLog: true,
  218. },
  219. {
  220. name: "commented error log should be ignored",
  221. nginxTOutput: "# error_log /var/log/nginx/commented.error.log notice;\nerror_log /var/log/nginx/error.log notice;",
  222. expectedPath: "/var/log/nginx/error.log",
  223. shouldHaveLog: true,
  224. },
  225. {
  226. name: "only commented error log",
  227. nginxTOutput: "# error_log /var/log/nginx/commented.error.log notice;",
  228. expectedPath: "",
  229. shouldHaveLog: false,
  230. },
  231. }
  232. errorLogRegex := regexp.MustCompile(ErrorLogRegexPattern)
  233. for _, tc := range testCases {
  234. t.Run(tc.name, func(t *testing.T) {
  235. matches := errorLogRegex.FindAllStringSubmatch(tc.nginxTOutput, -1)
  236. if !tc.shouldHaveLog {
  237. if len(matches) > 0 {
  238. t.Errorf("Expected no error log directive, but found: %v", matches)
  239. }
  240. return
  241. }
  242. if len(matches) == 0 {
  243. t.Errorf("Expected to find error log directive, but found none")
  244. return
  245. }
  246. if len(matches[0]) < 2 {
  247. t.Errorf("Expected regex match to have at least 2 groups, got %d", len(matches[0]))
  248. return
  249. }
  250. logPath := matches[0][1]
  251. if logPath != tc.expectedPath {
  252. t.Errorf("Expected error log path %s, got %s", tc.expectedPath, logPath)
  253. }
  254. })
  255. }
  256. }
  257. func TestLogPathParsing(t *testing.T) {
  258. testCases := []struct {
  259. name string
  260. nginxTOutput string
  261. expectedAccessPath string
  262. expectedErrorPath string
  263. shouldHaveAccess bool
  264. shouldHaveError bool
  265. }{
  266. {
  267. name: "complete configuration",
  268. nginxTOutput: mockNginxTOutput,
  269. expectedAccessPath: "/var/log/nginx/access.log",
  270. expectedErrorPath: "/var/log/nginx/error.log",
  271. shouldHaveAccess: true,
  272. shouldHaveError: true,
  273. },
  274. {
  275. name: "configuration with commented directives",
  276. nginxTOutput: mockNginxTOutputCommented,
  277. expectedAccessPath: "/var/log/nginx/access.log",
  278. expectedErrorPath: "/var/log/nginx/error.log",
  279. shouldHaveAccess: true,
  280. shouldHaveError: true,
  281. },
  282. {
  283. name: "access log turned off",
  284. nginxTOutput: mockNginxTOutputOff,
  285. expectedAccessPath: "/var/log/nginx/server.access.log", // Should get the server-level access log
  286. expectedErrorPath: "/var/log/nginx/error.log",
  287. shouldHaveAccess: true,
  288. shouldHaveError: true,
  289. },
  290. {
  291. name: "empty configuration",
  292. nginxTOutput: "",
  293. expectedAccessPath: "",
  294. expectedErrorPath: "",
  295. shouldHaveAccess: false,
  296. shouldHaveError: false,
  297. },
  298. }
  299. for _, tc := range testCases {
  300. t.Run(tc.name, func(t *testing.T) {
  301. // Test access log parsing
  302. accessLogRegex := regexp.MustCompile(AccessLogRegexPattern)
  303. accessMatches := accessLogRegex.FindAllStringSubmatch(tc.nginxTOutput, -1)
  304. var foundAccessPath string
  305. for _, match := range accessMatches {
  306. if len(match) >= 2 {
  307. logPath := match[1]
  308. if logPath != "off" {
  309. foundAccessPath = logPath
  310. break
  311. }
  312. }
  313. }
  314. if tc.shouldHaveAccess {
  315. if foundAccessPath == "" {
  316. t.Errorf("Expected access log path %s, but found none", tc.expectedAccessPath)
  317. } else if foundAccessPath != tc.expectedAccessPath {
  318. t.Errorf("Expected access log path %s, got %s", tc.expectedAccessPath, foundAccessPath)
  319. }
  320. } else {
  321. if foundAccessPath != "" {
  322. t.Errorf("Expected no access log path, but found %s", foundAccessPath)
  323. }
  324. }
  325. // Test error log parsing
  326. errorLogRegex := regexp.MustCompile(ErrorLogRegexPattern)
  327. errorMatches := errorLogRegex.FindAllStringSubmatch(tc.nginxTOutput, -1)
  328. var foundErrorPath string
  329. if len(errorMatches) > 0 && len(errorMatches[0]) >= 2 {
  330. foundErrorPath = errorMatches[0][1]
  331. }
  332. if tc.shouldHaveError {
  333. if foundErrorPath == "" {
  334. t.Errorf("Expected error log path %s, but found none", tc.expectedErrorPath)
  335. } else if foundErrorPath != tc.expectedErrorPath {
  336. t.Errorf("Expected error log path %s, got %s", tc.expectedErrorPath, foundErrorPath)
  337. }
  338. } else {
  339. if foundErrorPath != "" {
  340. t.Errorf("Expected no error log path, but found %s", foundErrorPath)
  341. }
  342. }
  343. })
  344. }
  345. }
  346. func TestRelativePathHandling(t *testing.T) {
  347. // Mock GetPrefix function for testing
  348. originalGetPrefix := GetPrefix
  349. defer func() {
  350. // Restore original function (if needed for other tests)
  351. _ = originalGetPrefix
  352. }()
  353. testPrefix := "/usr/local/nginx"
  354. testCases := []struct {
  355. name string
  356. inputPath string
  357. expectedPath string
  358. isRelative bool
  359. }{
  360. {
  361. name: "absolute path",
  362. inputPath: "/var/log/nginx/access.log",
  363. expectedPath: "/var/log/nginx/access.log",
  364. isRelative: false,
  365. },
  366. {
  367. name: "relative path",
  368. inputPath: "logs/access.log",
  369. expectedPath: filepath.Join(testPrefix, "logs/access.log"),
  370. isRelative: true,
  371. },
  372. {
  373. name: "relative path with ./",
  374. inputPath: "./logs/access.log",
  375. expectedPath: filepath.Join(testPrefix, "./logs/access.log"),
  376. isRelative: true,
  377. },
  378. }
  379. for _, tc := range testCases {
  380. t.Run(tc.name, func(t *testing.T) {
  381. var result string
  382. if tc.isRelative {
  383. result = filepath.Join(testPrefix, tc.inputPath)
  384. } else {
  385. result = tc.inputPath
  386. }
  387. if result != tc.expectedPath {
  388. t.Errorf("Expected path %s, got %s", tc.expectedPath, result)
  389. }
  390. })
  391. }
  392. }
  393. func TestComplexNginxConfiguration(t *testing.T) {
  394. complexConfig := `
  395. # Main configuration
  396. user nginx;
  397. worker_processes auto;
  398. error_log /var/log/nginx/error.log warn;
  399. pid /var/run/nginx.pid;
  400. events {
  401. worker_connections 1024;
  402. }
  403. http {
  404. include /etc/nginx/mime.types;
  405. default_type application/octet-stream;
  406. log_format main '$remote_addr - $remote_user [$time_local] "$request" '
  407. '$status $body_bytes_sent "$http_referer" '
  408. '"$http_user_agent" "$http_x_forwarded_for"';
  409. access_log /var/log/nginx/access.log main;
  410. sendfile on;
  411. tcp_nopush on;
  412. tcp_nodelay on;
  413. keepalive_timeout 65;
  414. types_hash_max_size 2048;
  415. # Virtual Host Configs
  416. include /etc/nginx/conf.d/*.conf;
  417. include /etc/nginx/sites-enabled/*;
  418. server {
  419. listen 80 default_server;
  420. listen [::]:80 default_server;
  421. server_name _;
  422. root /var/www/html;
  423. index index.html index.htm index.nginx-debian.html;
  424. access_log /var/log/nginx/default.access.log;
  425. error_log /var/log/nginx/default.error.log;
  426. location / {
  427. try_files $uri $uri/ =404;
  428. }
  429. location ~ /\.ht {
  430. deny all;
  431. }
  432. }
  433. server {
  434. listen 443 ssl http2;
  435. server_name example.com;
  436. root /var/www/example.com;
  437. access_log /var/log/nginx/example.access.log combined;
  438. error_log /var/log/nginx/example.error.log info;
  439. ssl_certificate /etc/ssl/certs/example.com.pem;
  440. ssl_certificate_key /etc/ssl/private/example.com.key;
  441. }
  442. }
  443. stream {
  444. error_log /var/log/nginx/stream.error.log info;
  445. upstream backend {
  446. server 192.168.1.100:3306;
  447. server 192.168.1.101:3306;
  448. }
  449. server {
  450. listen 3306;
  451. proxy_pass backend;
  452. proxy_timeout 1s;
  453. proxy_responses 1;
  454. }
  455. }
  456. `
  457. // Test that we can extract the main access log and error log from complex config
  458. accessLogRegex := regexp.MustCompile(AccessLogRegexPattern)
  459. errorLogRegex := regexp.MustCompile(ErrorLogRegexPattern)
  460. // Find all access logs
  461. accessMatches := accessLogRegex.FindAllStringSubmatch(complexConfig, -1)
  462. if len(accessMatches) == 0 {
  463. t.Error("Expected to find access log directives in complex config")
  464. } else {
  465. firstAccessLog := accessMatches[0][1]
  466. expectedFirstAccess := "/var/log/nginx/access.log"
  467. if firstAccessLog != expectedFirstAccess {
  468. t.Errorf("Expected first access log to be %s, got %s", expectedFirstAccess, firstAccessLog)
  469. }
  470. t.Logf("Found %d access log directives, first: %s", len(accessMatches), firstAccessLog)
  471. }
  472. // Find all error logs
  473. errorMatches := errorLogRegex.FindAllStringSubmatch(complexConfig, -1)
  474. if len(errorMatches) == 0 {
  475. t.Error("Expected to find error log directives in complex config")
  476. } else {
  477. firstErrorLog := errorMatches[0][1]
  478. expectedFirstError := "/var/log/nginx/error.log"
  479. if firstErrorLog != expectedFirstError {
  480. t.Errorf("Expected first error log to be %s, got %s", expectedFirstError, firstErrorLog)
  481. }
  482. t.Logf("Found %d error log directives, first: %s", len(errorMatches), firstErrorLog)
  483. }
  484. }
  485. func TestCommentedDirectivesIgnored(t *testing.T) {
  486. testConfig := `
  487. # Main configuration
  488. user nginx;
  489. worker_processes auto;
  490. # These should be ignored
  491. # error_log /var/log/nginx/commented.error.log notice;
  492. # access_log /var/log/nginx/commented.access.log main;
  493. # Real directives
  494. error_log /var/log/nginx/error.log warn;
  495. http {
  496. # This should be ignored too
  497. # access_log /var/log/nginx/commented.http.access.log combined;
  498. # Real directive
  499. access_log /var/log/nginx/access.log main;
  500. server {
  501. listen 80;
  502. server_name example.com;
  503. # Commented server-level logs should be ignored
  504. # access_log /var/log/nginx/commented.server.access.log;
  505. # error_log /var/log/nginx/commented.server.error.log warn;
  506. # Real server-level logs
  507. access_log /var/log/nginx/server.access.log;
  508. error_log /var/log/nginx/server.error.log info;
  509. }
  510. }
  511. `
  512. // Test access log parsing ignores comments
  513. accessLogRegex := regexp.MustCompile(AccessLogRegexPattern)
  514. accessMatches := accessLogRegex.FindAllStringSubmatch(testConfig, -1)
  515. expectedAccessLogs := []string{
  516. "/var/log/nginx/access.log",
  517. "/var/log/nginx/server.access.log",
  518. }
  519. if len(accessMatches) != len(expectedAccessLogs) {
  520. t.Errorf("Expected %d access log matches, got %d", len(expectedAccessLogs), len(accessMatches))
  521. }
  522. for i, match := range accessMatches {
  523. if i < len(expectedAccessLogs) {
  524. if match[1] != expectedAccessLogs[i] {
  525. t.Errorf("Expected access log %d to be %s, got %s", i, expectedAccessLogs[i], match[1])
  526. }
  527. }
  528. }
  529. // Test error log parsing ignores comments
  530. errorLogRegex := regexp.MustCompile(ErrorLogRegexPattern)
  531. errorMatches := errorLogRegex.FindAllStringSubmatch(testConfig, -1)
  532. expectedErrorLogs := []string{
  533. "/var/log/nginx/error.log",
  534. "/var/log/nginx/server.error.log",
  535. }
  536. if len(errorMatches) != len(expectedErrorLogs) {
  537. t.Errorf("Expected %d error log matches, got %d", len(expectedErrorLogs), len(errorMatches))
  538. }
  539. for i, match := range errorMatches {
  540. if i < len(expectedErrorLogs) {
  541. if match[1] != expectedErrorLogs[i] {
  542. t.Errorf("Expected error log %d to be %s, got %s", i, expectedErrorLogs[i], match[1])
  543. }
  544. }
  545. }
  546. }