Skip to content

Commit 1da9daa

Browse files
authored
Add monolog json log support (#45)
1 parent 2919173 commit 1da9daa

File tree

7 files changed

+168
-11
lines changed

7 files changed

+168
-11
lines changed

dev/config/config.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ monolog:
3636
path: "%kernel.logs_dir%/%kernel.environment%.log"
3737
level: debug
3838
channels: [ "!event", "!deprecation" ]
39+
json:
40+
type: stream
41+
path: "%kernel.logs_dir%/json/%kernel.environment%.log"
42+
formatter: 'monolog.formatter.json'
43+
level: debug
44+
channels: [ "!event", "!deprecation" ]
3945
error:
4046
type: stream
4147
path: "%kernel.logs_dir%/error.log"
@@ -50,6 +56,14 @@ monolog:
5056
fd_log_viewer:
5157
log_files:
5258
monolog:
59+
type: monolog
60+
downloadable: true
61+
deletable: true
62+
monolog-json:
63+
type: monolog.json
64+
name: Json monolog
65+
finder:
66+
in: "%kernel.logs_dir%/json"
5367
downloadable: true
5468
deletable: true
5569
nginx-access:

docs/configuration-reference.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ This entry allows you to add more log file directories to the Log Viewer. Each e
5252

5353
### log_files.type
5454

55-
**type**: `string` (`enum: monolog|http-access|apache-error|nginx-error`)
55+
**type**: `string` (`enum: monolog(.json)|http-access|apache-error|nginx-error`)
5656

5757
This is the type of log file that will be read.
5858
- `monolog` is the default type and will read the default monolog log files.
59+
- `monolog.json` will read the monolog log files that use `formatter: 'monolog.formatter.json'`.
5960
- `http-access` will read the access log files of Apache and Nginx.
6061
- `apache-error` will read the error log files of Apache.
6162
- `nginx-error` will read the error log files of Nginx.

src/Resources/config/services.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,13 @@
102102
$services->set(LogFileParserProvider::class)
103103
->arg('$logParsers', tagged_iterator('fd.symfony.log.viewer.log_file_parser', 'name'));
104104
$services->set(LogQueryDtoFactory::class);
105-
$services->set(MonologFileParser::class)->tag('fd.symfony.log.viewer.log_file_parser', ['name' => 'monolog'])
105+
$services->set('fd.symfony.log.viewer.monolog_line_file_parser', MonologFileParser::class)
106+
->tag('fd.symfony.log.viewer.log_file_parser', ['name' => 'monolog'])
107+
->arg('$formatType', MonologFileParser::TYPE_LINE)
108+
->arg('$loggerLocator', tagged_iterator('fd.symfony.log.viewer.logger'));
109+
$services->set('fd.symfony.log.viewer.monolog_json_file_parser', MonologFileParser::class)
110+
->tag('fd.symfony.log.viewer.log_file_parser', ['name' => 'monolog.json'])
111+
->arg('$formatType', MonologFileParser::TYPE_JSON)
106112
->arg('$loggerLocator', tagged_iterator('fd.symfony.log.viewer.logger'));
107113
$services->set(HttpAccessFileParser::class)->tag('fd.symfony.log.viewer.log_file_parser', ['name' => 'http-access']);
108114
$services->set(NginxErrorFileParser::class)->tag('fd.symfony.log.viewer.log_file_parser', ['name' => 'nginx-error']);

src/Service/File/Monolog/MonologFileParser.php

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@
99
use FD\LogViewer\Entity\Request\LogQueryDto;
1010
use FD\LogViewer\Service\File\LogFileParserInterface;
1111
use FD\LogViewer\Service\File\LogParser;
12+
use InvalidArgumentException;
1213
use Monolog\Logger;
1314
use SplFileInfo;
1415

1516
class MonologFileParser implements LogFileParserInterface
1617
{
18+
public const TYPE_LINE = 'line';
19+
public const TYPE_JSON = 'json';
20+
1721
/**
22+
* @param self::TYPE_* $formatType
1823
* @param iterable<int, Logger> $loggerLocator
1924
*/
20-
public function __construct(private readonly iterable $loggerLocator, private readonly LogParser $logParser)
21-
{
25+
public function __construct(
26+
private readonly string $formatType,
27+
private readonly iterable $loggerLocator,
28+
private readonly LogParser $logParser
29+
) {
2230
}
2331

2432
/**
@@ -54,10 +62,14 @@ public function getChannels(): array
5462

5563
public function getLogIndex(LogFilesConfig $config, LogFile $file, LogQueryDto $logQuery): LogIndex
5664
{
57-
return $this->logParser->parse(
58-
new SplFileInfo($file->path),
59-
new MonologLineParser($config->startOfLinePattern, $config->logMessagePattern),
60-
$logQuery
61-
);
65+
return match ($this->formatType) {
66+
self::TYPE_JSON => $this->logParser->parse(new SplFileInfo($file->path), new MonologJsonParser(), $logQuery),
67+
self::TYPE_LINE => $this->logParser->parse(
68+
new SplFileInfo($file->path),
69+
new MonologLineParser($config->startOfLinePattern, $config->logMessagePattern),
70+
$logQuery
71+
),
72+
default => throw new InvalidArgumentException('Invalid format type: ' . $this->formatType),
73+
};
6274
}
6375
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace FD\LogViewer\Service\File\Monolog;
5+
6+
use FD\LogViewer\Service\File\LogLineParserInterface;
7+
use JsonException;
8+
9+
class MonologJsonParser implements LogLineParserInterface
10+
{
11+
/**
12+
* @inheritDoc
13+
*/
14+
public function matches(string $line): int
15+
{
16+
return self::MATCH_START;
17+
}
18+
19+
/**
20+
* @inheritDoc
21+
*/
22+
public function parse(string $message): ?array
23+
{
24+
try {
25+
$json = json_decode($message, true, flags: JSON_THROW_ON_ERROR);
26+
} catch (JsonException) {
27+
return null;
28+
}
29+
30+
if (is_array($json) === false) {
31+
return null;
32+
}
33+
34+
return [
35+
'date' => $json['datetime'],
36+
'severity' => $json['level_name'],
37+
'channel' => $json['channel'],
38+
'message' => $json['message'],
39+
'context' => $json['context'] ?? [],
40+
'extra' => $json['extra'] ?? [],
41+
];
42+
}
43+
}

tests/Unit/Service/File/Monolog/MonologFileParserTest.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
use FD\LogViewer\Entity\Request\LogQueryDto;
88
use FD\LogViewer\Service\File\LogParser;
99
use FD\LogViewer\Service\File\Monolog\MonologFileParser;
10+
use FD\LogViewer\Service\File\Monolog\MonologJsonParser;
1011
use FD\LogViewer\Service\File\Monolog\MonologLineParser;
1112
use FD\LogViewer\Tests\Utility\TestEntityTrait;
13+
use InvalidArgumentException;
1214
use Monolog\Logger;
1315
use PHPUnit\Framework\Attributes\CoversClass;
1416
use PHPUnit\Framework\MockObject\MockObject;
@@ -29,7 +31,7 @@ protected function setUp(): void
2931
parent::setUp();
3032
$this->logger = $this->createMock(Logger::class);
3133
$this->logParser = $this->createMock(LogParser::class);
32-
$this->parser = new MonologFileParser([$this->logger], $this->logParser);
34+
$this->parser = new MonologFileParser(MonologFileParser::TYPE_LINE, [$this->logger], $this->logParser);
3335
}
3436

3537
public function testGetLevels(): void
@@ -53,7 +55,7 @@ public function testGetChannels(): void
5355
static::assertSame(['app' => 'app'], $this->parser->getChannels());
5456
}
5557

56-
public function testGetLogIndex(): void
58+
public function testGetLogIndexForLineParser(): void
5759
{
5860
$config = $this->createLogFileConfig();
5961
$logQuery = new LogQueryDto('identifier');
@@ -67,4 +69,32 @@ public function testGetLogIndex(): void
6769

6870
static::assertSame($index, $this->parser->getLogIndex($config, $file, $logQuery));
6971
}
72+
73+
public function testGetLogIndexForJsonParser(): void
74+
{
75+
$config = $this->createLogFileConfig();
76+
$logQuery = new LogQueryDto('identifier');
77+
$file = $this->createLogFile();
78+
$index = new LogIndex();
79+
80+
$this->logParser->expects(self::once())
81+
->method('parse')
82+
->with(new SplFileInfo('path'), new MonologJsonParser(), $logQuery)
83+
->willReturn($index);
84+
85+
$parser = new MonologFileParser(MonologFileParser::TYPE_JSON, [$this->logger], $this->logParser);
86+
static::assertSame($index, $parser->getLogIndex($config, $file, $logQuery));
87+
}
88+
89+
public function testGetLogIndexInvalidType(): void
90+
{
91+
$config = $this->createLogFileConfig();
92+
$file = $this->createLogFile();
93+
// @phpstan-ignore-next-line
94+
$parser = new MonologFileParser('foobar', [$this->logger], $this->logParser);
95+
96+
$this->expectException(InvalidArgumentException::class);
97+
$this->expectExceptionMessage('Invalid format type');
98+
$parser->getLogIndex($config, $file, new LogQueryDto('identifier'));
99+
}
70100
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace FD\LogViewer\Tests\Unit\Service\File\Monolog;
5+
6+
use FD\LogViewer\Service\File\LogLineParserInterface;
7+
use FD\LogViewer\Service\File\Monolog\MonologJsonParser;
8+
use PHPUnit\Framework\Attributes\CoversClass;
9+
use PHPUnit\Framework\TestCase;
10+
11+
#[CoversClass(MonologJsonParser::class)]
12+
class MonologJsonParserTest extends TestCase
13+
{
14+
private MonologJsonParser $parser;
15+
16+
protected function setUp(): void
17+
{
18+
parent::setUp();
19+
$this->parser = new MonologJsonParser();
20+
}
21+
22+
public function testMatches(): void
23+
{
24+
static::assertSame(LogLineParserInterface::MATCH_START, $this->parser->matches('line'));
25+
}
26+
27+
public function testParseInvalidJson(): void
28+
{
29+
static::assertNull($this->parser->parse('invalid json'));
30+
}
31+
32+
public function testParseNonArrayJson(): void
33+
{
34+
static::assertNull($this->parser->parse('"string"'));
35+
}
36+
37+
public function testParse(): void
38+
{
39+
$json = '{"datetime":"2021-01-01 00:00:00","level_name":"INFO","channel":"app",' .
40+
'"message":"message","context":["context"],"extra":["extra"]}';
41+
$expected = [
42+
'date' => '2021-01-01 00:00:00',
43+
'severity' => 'INFO',
44+
'channel' => 'app',
45+
'message' => 'message',
46+
'context' => ['context'],
47+
'extra' => ['extra'],
48+
];
49+
static::assertSame($expected, $this->parser->parse($json));
50+
}
51+
}

0 commit comments

Comments
 (0)