Skip to content

Commit 605d32d

Browse files
authored
Merge pull request #415 from opcodesio/bug/nginx-error-log-multiline-message
fix nginx error logs having multiline messages
2 parents e9e4c41 + 4bc90c1 commit 605d32d

File tree

6 files changed

+65
-15
lines changed

6 files changed

+65
-15
lines changed

src/Logs/HttpNginxErrorLog.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
class HttpNginxErrorLog extends Log
1111
{
1212
public static string $name = 'HTTP Errors (Nginx)';
13-
public static string $regex = '/^(?P<datetime>[\d+\/ :]+) \[(?P<level>.+)\] .*?: (?P<errormessage>.+?)(?:, client: (?P<client>.+?))?(?:, server: (?P<server>.+?))?(?:, request: "?(?P<request>.+?)"?)?(?:, host: "?(?P<host>.+?)"?)?$/';
13+
public static string $regex = '~^(?P<datetime>[\d+\/ :]+) \[(?P<level>.+?)\] .*?: (?P<errormessage>(?:(?!, client: |, server: |, request: |, upstream: |, host: |, referrer: ).)*(?:\n(?![\d/]|\Z).*)*?)(?:, client: (?P<client>.+?))?(?:, server: (?P<server>.+?))?(?:, request: "?(?P<request>.+?)"?)?(?:, upstream: "?(?P<upstream>.+?)"?)?(?:, host: "?(?P<host>.+?)"?)?(?:, referrer: "?(?P<referrer>.+?)"?)?$~ms';
1414
public static string $levelClass = NginxStatusLevel::class;
1515

1616
protected function fillMatches(array $matches = []): void
@@ -26,6 +26,8 @@ protected function fillMatches(array $matches = []): void
2626
'server' => $matches['server'] ?? null,
2727
'request' => $matches['request'] ?? null,
2828
'host' => $matches['host'] ?? null,
29+
'upstream' => $matches['upstream'] ?? null,
30+
'referrer' => $matches['referrer'] ?? null,
2931
];
3032
}
3133

tests/Feature/Authorization/CanDownloadFoldersTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ function assertCanDownloadFolder(string $folderName, string $expectedFileName):
2121

2222
function assertCannotDownloadFolder(string $folderName): void
2323
{
24-
get(route('log-viewer.folders.request-download', $folderName))
25-
->assertForbidden();
24+
get(route('log-viewer.folders.request-download', $folderName))
25+
->assertForbidden();
2626
}
2727

2828
test('can download every folder by default', function () {
@@ -33,9 +33,9 @@ function assertCannotDownloadFolder(string $folderName): void
3333
});
3434

3535
test('cannot download a folder that\'s not found', function () {
36-
get(route('log-viewer.folders.request-download', 'notfound'))
37-
->assertNotFound();
38-
});
36+
get(route('log-viewer.folders.request-download', 'notfound'))
37+
->assertNotFound();
38+
});
3939

4040
test('"downloadLogFolder" gate can prevent folder download', function () {
4141
generateLogFiles([$fileName = 'laravel.log']);

tests/Feature/Authorization/CanDownloadLogFileTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ function assertCanDownloadFile(string $fileName): void
2020

2121
function assertCannotDownloadFile(string $fileName): void
2222
{
23-
get(route('log-viewer.files.request-download', $fileName))
24-
->assertForbidden();
23+
get(route('log-viewer.files.request-download', $fileName))
24+
->assertForbidden();
2525
}
2626

2727
test('can download every file by default', function () {
@@ -31,9 +31,9 @@ function assertCannotDownloadFile(string $fileName): void
3131
});
3232

3333
test('cannot download a file that\'s not found', function () {
34-
get(route('log-viewer.files.request-download', 'notfound.log'))
35-
->assertNotFound();
36-
});
34+
get(route('log-viewer.files.request-download', 'notfound.log'))
35+
->assertNotFound();
36+
});
3737

3838
test('"downloadLogFile" gate can prevent file download', function () {
3939
generateLogFiles([$fileName = 'laravel.log']);

tests/Feature/Authorization/CanViewLogViewerTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use function Pest\Laravel\get;
77

88
test('can define an "auth" callback for authorization', function () {
9-
get(route('log-viewer.index'))->assertOk();
9+
get(route('log-viewer.index'))->assertOk();
1010

1111
// with the gate defined and a false value, it should not be possible to access the log viewer
1212
LogViewer::auth(fn ($request) => false);
@@ -15,7 +15,7 @@
1515
// now let's give them access
1616
LogViewer::auth(fn ($request) => true);
1717
get(route('log-viewer.index'))->assertOk();
18-
});
18+
});
1919

2020
test('the "auth" callback is given with a Request object to check against', function () {
2121
LogViewer::auth(function ($request) {
@@ -28,14 +28,14 @@
2828
});
2929

3030
test('can define a "viewLogViewer" gate as an alternative', function () {
31-
get(route('log-viewer.index'))->assertOk();
31+
get(route('log-viewer.index'))->assertOk();
3232

3333
Gate::define('viewLogViewer', fn ($user = null) => false);
3434
get(route('log-viewer.index'))->assertForbidden();
3535

3636
Gate::define('viewLogViewer', fn ($user = null) => true);
3737
get(route('log-viewer.index'))->assertOk();
38-
});
38+
});
3939

4040
test('local environment can use Log Viewer by default', function () {
4141
app()->detectEnvironment(fn () => 'local');
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2024/08/21 09:08:18 [error] 2761052#2761052: *84719 upstream sent too big header while reading response header from upstream, client: 123.123.123.123, server: xxx, request: "GET /api/xx/yy/zz?lang=de HTTP/2.0", upstream: "fastcgi://unix:/var/run/php/php8.1-fpm.sock:", host: "xxx", referrer: "http://some-ip:3000/"
2+
2024/08/21 09:08:19 [error] 2761052#2761052: *84719 FastCGI sent in stderr: "PHP message: [2024-08-21 11:08:18] develop.DEBUG: ActivityService: some message
3+
PHP message: [2024-08-21 11:08:18] develop.DEBUG: ActivityService: another message
4+
PHP message: [2024-08-21 11:08:18] develop.DEBUG: ActivityService: blabla: [{"id":308363,"lat":"xx","lng":"yy"}]
5+
PHP message: [2024-08-21 11:08:18] develop.INFO: provider Payload: {"attr1":"t","attr2":14400,"sources":[{"id":"source","lat":xx,"lng":yy,"tm":{"t":{"maxT":2},"c":{"":""}}}],"t":[{"id":308363,"lat":"xx","lng":"yy"}],"g_s_client":false,"reversed":false,"polygon":{"id":4326},"pathSerializer":"g"}
6+
PHP message: [2024-08-21 11:08:18] develop.DEBUG: Sending request to final Url (V1) https://someurl/path/v1/endpoint?param=***

tests/Unit/AccessLogs/HttpNginxErrorLogTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,45 @@
4343
->and($log->context['request'])->toBe(null)
4444
->and($log->context['host'])->toBe(null);
4545
});
46+
47+
it('can parse multiline nginx log entries', function () {
48+
$file = new \Opcodes\LogViewer\LogFile(__DIR__.'/Fixtures/multiline_nginx_error_dummy.log');
49+
$file->logs()->scan();
50+
51+
$logs = $file->logs()->get();
52+
53+
expect($logs)->toHaveCount(2);
54+
55+
/** @var HttpNginxErrorLog $firstLog */
56+
$firstLog = $logs[0];
57+
58+
// 2024/08/21 09:08:18 [error] 2761052#2761052: *84719 upstream sent too big header while reading response header from upstream,
59+
// client: 123.123.123.123, server: xxx, request: "GET /api/xx/yy/zz?lang=de HTTP/2.0",
60+
// upstream: "fastcgi://unix:/var/run/php/php8.1-fpm.sock:", host: "xxx", referrer: "http://some-ip:3000/"
61+
expect($firstLog->index)->toBe(0)
62+
->and($firstLog)->toBeInstanceOf(HttpNginxErrorLog::class)
63+
->and($firstLog->datetime->toDateTimeString())->toBe('2024-08-21 09:08:18')
64+
->and($firstLog->level)->toBe('error')
65+
->and($firstLog->message)->toBe('*84719 upstream sent too big header while reading response header from upstream')
66+
->and($firstLog->context['client'])->toBe('123.123.123.123')
67+
->and($firstLog->context['server'])->toBe('xxx')
68+
->and($firstLog->context['request'])->toBe('GET /api/xx/yy/zz?lang=de HTTP/2.0')
69+
->and($firstLog->context['upstream'])->toBe('fastcgi://unix:/var/run/php/php8.1-fpm.sock:')
70+
->and($firstLog->context['host'])->toBe('xxx')
71+
->and($firstLog->context['referrer'])->toBe('http://some-ip:3000/');
72+
73+
$secondLog = $logs[1];
74+
75+
expect($secondLog->index)->toBe(1)
76+
->and($secondLog)->toBeInstanceOf(HttpNginxErrorLog::class)
77+
->and($secondLog->datetime->toDateTimeString())->toBe('2024-08-21 09:08:19')
78+
->and($secondLog->level)->toBe('error')
79+
->and($secondLog->message)->toBe(<<<'EOF'
80+
*84719 FastCGI sent in stderr: "PHP message: [2024-08-21 11:08:18] develop.DEBUG: ActivityService: some message
81+
PHP message: [2024-08-21 11:08:18] develop.DEBUG: ActivityService: another message
82+
PHP message: [2024-08-21 11:08:18] develop.DEBUG: ActivityService: blabla: [{"id":308363,"lat":"xx","lng":"yy"}]
83+
PHP message: [2024-08-21 11:08:18] develop.INFO: provider Payload: {"attr1":"t","attr2":14400,"sources":[{"id":"source","lat":xx,"lng":yy,"tm":{"t":{"maxT":2},"c":{"":""}}}],"t":[{"id":308363,"lat":"xx","lng":"yy"}],"g_s_client":false,"reversed":false,"polygon":{"id":4326},"pathSerializer":"g"}
84+
PHP message: [2024-08-21 11:08:18] develop.DEBUG: Sending request to final Url (V1) https://someurl/path/v1/endpoint?param=***
85+
EOF);
86+
87+
});

0 commit comments

Comments
 (0)