Skip to content

Commit f83a583

Browse files
committed
preserve keys in LogResource so that context is intact
1 parent 65d945f commit f83a583

File tree

3 files changed

+184
-171
lines changed

3 files changed

+184
-171
lines changed

src/Http/Resources/LogResource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*/
1010
class LogResource extends JsonResource
1111
{
12+
public bool $preserveKeys = true;
13+
1214
public function toArray($request): array
1315
{
1416
$level = $this->getLevel();

tests/Unit/LaravelLogs/LaravelLogsTest.php

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,189 @@
11
<?php
22

3+
use Opcodes\LogViewer\LogLevels\LaravelLogLevel;
34
use Opcodes\LogViewer\Logs\LaravelLog;
45
use Opcodes\LogViewer\Utils\Utils;
6+
use function PHPUnit\Framework\assertEquals;
7+
8+
it('can understand the default Laravel log format', function () {
9+
$text = '[2022-08-25 11:16:17] local.DEBUG: Example log entry for the level debug';
10+
11+
$log = new LaravelLog($text, $fileIdentifier = 'laravel.log', $filePosition = 5200, $index = 10);
12+
13+
assertEquals($index, $log->index);
14+
assertEquals(LaravelLogLevel::Debug, $log->level);
15+
assertEquals('local', $log->extra['environment']);
16+
assertEquals('2022-08-25 11:16:17', $log->datetime->toDateTimeString());
17+
assertEquals('Example log entry for the level debug', $log->message);
18+
assertEquals('Example log entry for the level debug', $log->getOriginalText());
19+
assertEquals($fileIdentifier, $log->fileIdentifier);
20+
assertEquals($filePosition, $log->filePosition);
21+
});
22+
23+
it('can understand multi-line logs', function () {
24+
$logText = <<<'EOF'
25+
Example log entry for the level debug
26+
with multiple lines of content.
27+
can contain dumped objects or JSON as well - it's all part of the contents.
28+
EOF;
29+
$text = '[2022-08-25 11:16:17] local.DEBUG: '.$logText;
30+
31+
$log = new LaravelLog($text, 'laravel.log', 0, 0);
32+
33+
assertEquals('Example log entry for the level debug', $log->message);
34+
assertEquals($logText, $log->getOriginalText());
35+
});
36+
37+
it('extracts JSON from the log text', function () {
38+
config(['log-viewer.strip_extracted_context' => true]);
39+
$logText = <<<'EOF'
40+
Example log entry for the level debug
41+
with multiple lines of content.
42+
{"one":1,"two":"two","three":[1,2,3]}
43+
can contain dumped objects or JSON as well - it's all part of the contents.
44+
EOF;
45+
$jsonString = '{"one":1,"two":"two","three":[1,2,3]}';
46+
$text = '[2022-08-25 11:16:17] local.DEBUG: '.$logText;
47+
48+
$log = new LaravelLog($text, 'laravel.log', 0, 0);
49+
50+
assertEquals('Example log entry for the level debug', $log->message);
51+
assertEquals(rtrim(str_replace($jsonString, '', $logText)), $log->getOriginalText());
52+
assertEquals(json_decode($jsonString, true), $log->context);
53+
});
54+
55+
it('extracts JSON, but does not remove from the log text if the config is set to false', function () {
56+
config(['log-viewer.strip_extracted_context' => false]);
57+
$logText = <<<'EOF'
58+
Example log entry for the level debug
59+
with multiple lines of content.
60+
{"one":1,"two":"two","three":[1,2,3]}
61+
can contain dumped objects or JSON as well - it's all part of the contents.
62+
EOF;
63+
$text = '[2022-08-25 11:16:17] local.DEBUG: '.$logText;
64+
65+
$log = new LaravelLog($text, 'laravel.log');
66+
67+
assertEquals('Example log entry for the level debug', $log->message);
68+
assertEquals($logText, $log->getOriginalText());
69+
assertEquals(json_decode('{"one":1,"two":"two","three":[1,2,3]}', true), $log->context);
70+
});
71+
72+
it('extracts JSON from a complex log', function () {
73+
config(['log-viewer.strip_extracted_context' => true]);
74+
$logText = <<<'EOF'
75+
Initiating facebook login.
76+
[HTTP request]
77+
*User ID:* guest
78+
*Request:* GET https://system.test/book/arunas/submit-facebook
79+
*Agent:* Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/19G82 [FBAN/FBIOS;FBDV/iPhone9,3;FBMD/iPhone;FBSN/iOS;FBSV/15.6.1;FBSS/2;FBID/phone;FBLC/da_DK;FBOP/5]
80+
{"permalink":"arunas","session":{"_token":"BpqyiNyinnLamzer4jqzrh9NTyC6emFR41FitMpv","_previous":{"url":"https://system.test/book/arunas/center"},"_flash":{"old":[],"new":[]},"latest_permalink":"arunas"},"ip":"127.0.0.1","user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/19G82 [FBAN/FBIOS;FBDV/iPhone9,3;FBMD/iPhone;FBSN/iOS;FBSV/15.6.1;FBSS/2;FBID/phone;FBLC/da_DK;FBOP/5]"}
81+
EOF;
82+
83+
$jsonString = '{"permalink":"arunas","session":{"_token":"BpqyiNyinnLamzer4jqzrh9NTyC6emFR41FitMpv","_previous":{"url":"https://system.test/book/arunas/center"},"_flash":{"old":[],"new":[]},"latest_permalink":"arunas"},"ip":"127.0.0.1","user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/19G82 [FBAN/FBIOS;FBDV/iPhone9,3;FBMD/iPhone;FBSN/iOS;FBSV/15.6.1;FBSS/2;FBID/phone;FBLC/da_DK;FBOP/5]"}';
84+
$text = '[2022-08-25 11:16:17] local.DEBUG: '.$logText;
85+
86+
$log = new LaravelLog($text, 'laravel.log');
87+
88+
assertEquals('arunas', $log->context['permalink'] ?? null);
89+
assertEquals('Initiating facebook login.', $log->message);
90+
assertEquals(rtrim(str_replace($jsonString, '', $logText)), $log->getOriginalText());
91+
assertEquals(json_decode($jsonString, true), $log->context);
92+
});
93+
94+
it('can understand the optional microseconds in the timestamp', function () {
95+
$text = '[2022-08-25 11:16:17.125000] local.DEBUG: Example log entry for the level debug';
96+
97+
$log = new LaravelLog($text, 'laravel.log', 0, 0);
98+
99+
assertEquals(125000, $log->datetime->micro);
100+
});
101+
102+
it('can understand the optional time offset in the timestamp', function () {
103+
$text = '[2022-08-25 11:16:17.125000+02:00] local.DEBUG: Example log entry for the level debug';
104+
$expectedTimestamp = 1661418977;
105+
106+
$log = new LaravelLog($text, 'laravel.log', 0, 0);
107+
108+
assertEquals($expectedTimestamp, $log->datetime->timestamp);
109+
110+
// Meanwhile, if we switch the time offset an hour forward,
111+
// we can expect the timestamp to be reduced by 3600 seconds.
112+
$text = '[2022-08-25 11:16:17.125000+03:00] local.DEBUG: Example log entry for the level debug';
113+
$newExpectedTimestamp = $expectedTimestamp - 3600;
114+
115+
$log = new LaravelLog($text, 'laravel.log', 0, 0);
116+
117+
assertEquals($newExpectedTimestamp, $log->datetime->timestamp);
118+
});
119+
120+
it('can handle text in-between timestamp and environment/severity', function () {
121+
$text = '[2022-08-25 11:16:17] some additional text [!@#$%^&] and characters // !@#$ local.DEBUG: Example log entry for the level debug';
122+
$expectedAdditionalText = 'some additional text [!@#$%^&] and characters // !@#$';
123+
124+
$log = new LaravelLog($text, 'laravel.log', 0, 0);
125+
126+
assertEquals($expectedAdditionalText.' Example log entry for the level debug', $log->message);
127+
assertEquals($expectedAdditionalText.' Example log entry for the level debug', $log->getOriginalText());
128+
// got to make sure the rest of the data is still processed correctly!
129+
assertEquals('local', $log->extra['environment']);
130+
assertEquals(LaravelLogLevel::Debug, $log->level);
131+
});
132+
133+
it('finds the correct log level', function ($levelProvided, $levelExpected) {
134+
$text = "[2022-08-25 11:16:17] local.$levelProvided: Example log entry for the level debug";
135+
136+
$log = new LaravelLog($text, 'laravel.log', 0, 0);
137+
138+
assertEquals($levelExpected, $log->level);
139+
})->with([
140+
['INFO', LaravelLogLevel::Info],
141+
['DEBUG', LaravelLogLevel::Debug],
142+
['ERROR', LaravelLogLevel::Error],
143+
['WARNING', LaravelLogLevel::Warning],
144+
['CRITICAL', LaravelLogLevel::Critical],
145+
['ALERT', LaravelLogLevel::Alert],
146+
['EMERGENCY', LaravelLogLevel::Emergency],
147+
['info', LaravelLogLevel::Info],
148+
['iNfO', LaravelLogLevel::Info],
149+
['', LaravelLogLevel::None],
150+
]);
151+
152+
it('handles missing message', function () {
153+
$text = '[2022-11-07 17:51:33] production.ERROR: ';
154+
155+
$log = new LaravelLog($text, 'laravel.log', 0, 0);
156+
157+
assertEquals('2022-11-07 17:51:33', $log->datetime?->toDateTimeString());
158+
assertEquals(LaravelLogLevel::Error, $log->level);
159+
assertEquals('production', $log->extra['environment']);
160+
assertEquals('', $log->getOriginalText());
161+
});
162+
163+
it('strips extracted context when there\'s multiple contexts available', function () {
164+
config(['log-viewer.strip_extracted_context' => true]);
165+
$logText = <<<'EOF'
166+
[2023-08-16 14:00:25] testing.INFO: Test message. ["one","two"] {"memory_usage":"78 MB","process_id":1234}
167+
EOF;
168+
169+
$log = new LaravelLog($logText);
170+
171+
assertEquals('Test message.', $log->message);
172+
assertEquals(2, count($log->context));
173+
assertEquals(['one', 'two'], $log->context[0]);
174+
assertEquals(['memory_usage' => '78 MB', 'process_id' => 1234], $log->context[1]);
175+
});
176+
177+
it('correctly handles objects with number keys', function () {
178+
$logText = <<<'EOF'
179+
[2024-03-13 12:57:30] local.DEBUG: This is a log message {"array":{"10":"value1","20":"value2"}}
180+
EOF;
181+
182+
$log = new LaravelLog($logText);
183+
184+
assertEquals('This is a log message', $log->message);
185+
assertEquals(['array' => ['10' => 'value1', '20' => 'value2']], $log->context);
186+
});
5187

6188
it('can extract mail preview from a log', function () {
7189
$messageString = <<<'EOF'

tests/Unit/LogTest.php

Lines changed: 0 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,165 +1,8 @@
11
<?php
22

3-
use Opcodes\LogViewer\LogLevels\LaravelLogLevel;
43
use Opcodes\LogViewer\Logs\LaravelLog;
5-
64
use function PHPUnit\Framework\assertEquals;
75

8-
it('can understand the default Laravel log format', function () {
9-
$text = '[2022-08-25 11:16:17] local.DEBUG: Example log entry for the level debug';
10-
11-
$log = new LaravelLog($text, $fileIdentifier = 'laravel.log', $filePosition = 5200, $index = 10);
12-
13-
assertEquals($index, $log->index);
14-
assertEquals(LaravelLogLevel::Debug, $log->level);
15-
assertEquals('local', $log->extra['environment']);
16-
assertEquals('2022-08-25 11:16:17', $log->datetime->toDateTimeString());
17-
assertEquals('Example log entry for the level debug', $log->message);
18-
assertEquals('Example log entry for the level debug', $log->getOriginalText());
19-
assertEquals($fileIdentifier, $log->fileIdentifier);
20-
assertEquals($filePosition, $log->filePosition);
21-
});
22-
23-
it('can understand multi-line logs', function () {
24-
$logText = <<<'EOF'
25-
Example log entry for the level debug
26-
with multiple lines of content.
27-
can contain dumped objects or JSON as well - it's all part of the contents.
28-
EOF;
29-
$text = '[2022-08-25 11:16:17] local.DEBUG: '.$logText;
30-
31-
$log = new LaravelLog($text, 'laravel.log', 0, 0);
32-
33-
assertEquals('Example log entry for the level debug', $log->message);
34-
assertEquals($logText, $log->getOriginalText());
35-
});
36-
37-
it('extracts JSON from the log text', function () {
38-
config(['log-viewer.strip_extracted_context' => true]);
39-
$logText = <<<'EOF'
40-
Example log entry for the level debug
41-
with multiple lines of content.
42-
{"one":1,"two":"two","three":[1,2,3]}
43-
can contain dumped objects or JSON as well - it's all part of the contents.
44-
EOF;
45-
$jsonString = '{"one":1,"two":"two","three":[1,2,3]}';
46-
$text = '[2022-08-25 11:16:17] local.DEBUG: '.$logText;
47-
48-
$log = new LaravelLog($text, 'laravel.log', 0, 0);
49-
50-
assertEquals('Example log entry for the level debug', $log->message);
51-
assertEquals(rtrim(str_replace($jsonString, '', $logText)), $log->getOriginalText());
52-
assertEquals(json_decode($jsonString, true), $log->context);
53-
});
54-
55-
it('extracts JSON, but does not remove from the log text if the config is set to false', function () {
56-
config(['log-viewer.strip_extracted_context' => false]);
57-
$logText = <<<'EOF'
58-
Example log entry for the level debug
59-
with multiple lines of content.
60-
{"one":1,"two":"two","three":[1,2,3]}
61-
can contain dumped objects or JSON as well - it's all part of the contents.
62-
EOF;
63-
$text = '[2022-08-25 11:16:17] local.DEBUG: '.$logText;
64-
65-
$log = new LaravelLog($text, 'laravel.log');
66-
67-
assertEquals('Example log entry for the level debug', $log->message);
68-
assertEquals($logText, $log->getOriginalText());
69-
assertEquals(json_decode('{"one":1,"two":"two","three":[1,2,3]}', true), $log->context);
70-
});
71-
72-
it('extracts JSON from a complex log', function () {
73-
config(['log-viewer.strip_extracted_context' => true]);
74-
$logText = <<<'EOF'
75-
Initiating facebook login.
76-
[HTTP request]
77-
*User ID:* guest
78-
*Request:* GET https://system.test/book/arunas/submit-facebook
79-
*Agent:* Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/19G82 [FBAN/FBIOS;FBDV/iPhone9,3;FBMD/iPhone;FBSN/iOS;FBSV/15.6.1;FBSS/2;FBID/phone;FBLC/da_DK;FBOP/5]
80-
{"permalink":"arunas","session":{"_token":"BpqyiNyinnLamzer4jqzrh9NTyC6emFR41FitMpv","_previous":{"url":"https://system.test/book/arunas/center"},"_flash":{"old":[],"new":[]},"latest_permalink":"arunas"},"ip":"127.0.0.1","user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/19G82 [FBAN/FBIOS;FBDV/iPhone9,3;FBMD/iPhone;FBSN/iOS;FBSV/15.6.1;FBSS/2;FBID/phone;FBLC/da_DK;FBOP/5]"}
81-
EOF;
82-
83-
$jsonString = '{"permalink":"arunas","session":{"_token":"BpqyiNyinnLamzer4jqzrh9NTyC6emFR41FitMpv","_previous":{"url":"https://system.test/book/arunas/center"},"_flash":{"old":[],"new":[]},"latest_permalink":"arunas"},"ip":"127.0.0.1","user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/19G82 [FBAN/FBIOS;FBDV/iPhone9,3;FBMD/iPhone;FBSN/iOS;FBSV/15.6.1;FBSS/2;FBID/phone;FBLC/da_DK;FBOP/5]"}';
84-
$text = '[2022-08-25 11:16:17] local.DEBUG: '.$logText;
85-
86-
$log = new LaravelLog($text, 'laravel.log');
87-
88-
assertEquals('arunas', $log->context['permalink'] ?? null);
89-
assertEquals('Initiating facebook login.', $log->message);
90-
assertEquals(rtrim(str_replace($jsonString, '', $logText)), $log->getOriginalText());
91-
assertEquals(json_decode($jsonString, true), $log->context);
92-
});
93-
94-
it('can understand the optional microseconds in the timestamp', function () {
95-
$text = '[2022-08-25 11:16:17.125000] local.DEBUG: Example log entry for the level debug';
96-
97-
$log = new LaravelLog($text, 'laravel.log', 0, 0);
98-
99-
assertEquals(125000, $log->datetime->micro);
100-
});
101-
102-
it('can understand the optional time offset in the timestamp', function () {
103-
$text = '[2022-08-25 11:16:17.125000+02:00] local.DEBUG: Example log entry for the level debug';
104-
$expectedTimestamp = 1661418977;
105-
106-
$log = new LaravelLog($text, 'laravel.log', 0, 0);
107-
108-
assertEquals($expectedTimestamp, $log->datetime->timestamp);
109-
110-
// Meanwhile, if we switch the time offset an hour forward,
111-
// we can expect the timestamp to be reduced by 3600 seconds.
112-
$text = '[2022-08-25 11:16:17.125000+03:00] local.DEBUG: Example log entry for the level debug';
113-
$newExpectedTimestamp = $expectedTimestamp - 3600;
114-
115-
$log = new LaravelLog($text, 'laravel.log', 0, 0);
116-
117-
assertEquals($newExpectedTimestamp, $log->datetime->timestamp);
118-
});
119-
120-
it('can handle text in-between timestamp and environment/severity', function () {
121-
$text = '[2022-08-25 11:16:17] some additional text [!@#$%^&] and characters // !@#$ local.DEBUG: Example log entry for the level debug';
122-
$expectedAdditionalText = 'some additional text [!@#$%^&] and characters // !@#$';
123-
124-
$log = new LaravelLog($text, 'laravel.log', 0, 0);
125-
126-
assertEquals($expectedAdditionalText.' Example log entry for the level debug', $log->message);
127-
assertEquals($expectedAdditionalText.' Example log entry for the level debug', $log->getOriginalText());
128-
// got to make sure the rest of the data is still processed correctly!
129-
assertEquals('local', $log->extra['environment']);
130-
assertEquals(LaravelLogLevel::Debug, $log->level);
131-
});
132-
133-
it('finds the correct log level', function ($levelProvided, $levelExpected) {
134-
$text = "[2022-08-25 11:16:17] local.$levelProvided: Example log entry for the level debug";
135-
136-
$log = new LaravelLog($text, 'laravel.log', 0, 0);
137-
138-
assertEquals($levelExpected, $log->level);
139-
})->with([
140-
['INFO', LaravelLogLevel::Info],
141-
['DEBUG', LaravelLogLevel::Debug],
142-
['ERROR', LaravelLogLevel::Error],
143-
['WARNING', LaravelLogLevel::Warning],
144-
['CRITICAL', LaravelLogLevel::Critical],
145-
['ALERT', LaravelLogLevel::Alert],
146-
['EMERGENCY', LaravelLogLevel::Emergency],
147-
['info', LaravelLogLevel::Info],
148-
['iNfO', LaravelLogLevel::Info],
149-
['', LaravelLogLevel::None],
150-
]);
151-
152-
it('handles missing message', function () {
153-
$text = '[2022-11-07 17:51:33] production.ERROR: ';
154-
155-
$log = new LaravelLog($text, 'laravel.log', 0, 0);
156-
157-
assertEquals('2022-11-07 17:51:33', $log->datetime?->toDateTimeString());
158-
assertEquals(LaravelLogLevel::Error, $log->level);
159-
assertEquals('production', $log->extra['environment']);
160-
assertEquals('', $log->getOriginalText());
161-
});
162-
1636
it('can set a custom timezone of the log entry', function () {
1647
$text = '[2022-11-07 17:51:33] production.ERROR: test message';
1658
config(['log-viewer.timezone' => $tz = 'Europe/Vilnius']);
@@ -170,17 +13,3 @@
17013
$expectedTime = \Carbon\Carbon::parse('2022-11-07 17:51:33', 'UTC')->setTimezone($tz)->toDateTimeString();
17114
assertEquals($expectedTime, $log->datetime->toDateTimeString());
17215
});
173-
174-
it('strips extracted context when there\'s multiple contexts available', function () {
175-
config(['log-viewer.strip_extracted_context' => true]);
176-
$logText = <<<'EOF'
177-
[2023-08-16 14:00:25] testing.INFO: Test message. ["one","two"] {"memory_usage":"78 MB","process_id":1234}
178-
EOF;
179-
180-
$log = new LaravelLog($logText);
181-
182-
assertEquals('Test message.', $log->message);
183-
assertEquals(2, count($log->context));
184-
assertEquals(['one', 'two'], $log->context[0]);
185-
assertEquals(['memory_usage' => '78 MB', 'process_id' => 1234], $log->context[1]);
186-
});

0 commit comments

Comments
 (0)