Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
"fossar/htmlawed": "^1.3.3",
"guzzlehttp/psr7": "^2.0",
"j0k3r/graby-site-config": "^1.0.181",
"j0k3r/httplug-ssrf-plugin": "^2.0|^3.0",
"j0k3r/php-readability": "^1.2.9",
"monolog/monolog": "^1.18.0|^2.3",
"j0k3r/httplug-ssrf-plugin": "^3.0",
"j0k3r/php-readability": "^1.3",
"monolog/monolog": "^3.0",
"php-http/client-common": "^2.7",
"php-http/discovery": "^1.19",
"php-http/httplug": "^2.4",
Expand Down
12 changes: 11 additions & 1 deletion phpstan.dist.neon
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ parameters:
-
message: '#\$innerHTML#'
path: %currentWorkingDirectory%
- identifier: property.uninitializedReadonly
-
identifier: property.uninitializedReadonly
-
identifier: offsetAccess.nonOffsetAccessible
path: %currentWorkingDirectory%/tests
count: 13
-
identifier: argument.type
paths:
- %currentWorkingDirectory%/tests/Extractor/HttpClientTest.php
- %currentWorkingDirectory%/tests/SiteConfig/ConfigBuilderTest.php

inferPrivatePropertyTypeFromConstructor: true
7 changes: 4 additions & 3 deletions src/Graby.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Http\Discovery\Psr18ClientDiscovery;
use Http\Message\CookieJar;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseFactoryInterface;
Expand Down Expand Up @@ -98,18 +99,18 @@ public function __construct(array $config = [], ?ClientInterface $client = null,
if ($this->config->getDebug()) {
$this->logger = new Logger('graby');

// This statement has to be before Logger::INFO to catch all DEBUG messages
// This statement has to be before Level::Info to catch all DEBUG messages
if ('debug' === $this->config->getLogLevel()) {
$fp = fopen(__DIR__ . '/../log/html.log', 'w');
if (false !== $fp) {
// Emptying of the HTML logfile to avoid gigantic logs
fclose($fp);
}

$this->logger->pushHandler(new StreamHandler(__DIR__ . '/../log/html.log', Logger::DEBUG));
$this->logger->pushHandler(new StreamHandler(__DIR__ . '/../log/html.log', Level::Debug));
}

$this->logger->pushHandler(new StreamHandler(__DIR__ . '/../log/graby.log', Logger::INFO, false));
$this->logger->pushHandler(new StreamHandler(__DIR__ . '/../log/graby.log', Level::Info, false));
}

if (null === $configBuilder) {
Expand Down
46 changes: 16 additions & 30 deletions src/Monolog/Formatter/GrabyFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Graby\Monolog\Formatter;

use Monolog\Formatter\HtmlFormatter;
use Monolog\Level;
use Monolog\LogRecord;

/**
* Formats incoming records into an HTML table.
Expand All @@ -15,44 +17,28 @@
*/
class GrabyFormatter extends HtmlFormatter
{
/**
* Formats a log record.
*
* @param array{
* channel: string,
* level: int,
* level_name: string,
* datetime: \DateTimeInterface,
* message: string,
* formatted?: string,
* context: array<string, mixed>,
* extra: array<string, mixed>,
* } $record A record to format
*
* @return string The formatted record
*/
public function format(array $record): string
public function format(LogRecord $record): string
{
$output = '<table cellspacing="1" width="100%" class="monolog-output">';

$output .= $this->addRowWithLevel($record['level'], 'Time', $record['datetime']->format($this->dateFormat));
$output .= $this->addRowWithLevel($record['level'], 'Message', (string) $record['message']);
$output .= $this->addRowWithLevel($record->level, 'Time', $record->datetime->format($this->dateFormat));
$output .= $this->addRowWithLevel($record->level, 'Message', (string) $record->message);

if ($record['context']) {
if ($record->context) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['context'] as $key => $value) {
$embeddedTable .= $this->addRowWithLevel($record['level'], (string) $key, $this->convertToString($value));
foreach ($record->context as $key => $value) {
$embeddedTable .= $this->addRowWithLevel($record->level, (string) $key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRowWithLevel($record['level'], 'Context', $embeddedTable, false);
$output .= $this->addRowWithLevel($record->level, 'Context', $embeddedTable, false);
}
if ($record['extra']) {
if ($record->extra) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['extra'] as $key => $value) {
$embeddedTable .= $this->addRowWithLevel($record['level'], (string) $key, $this->convertToString($value));
foreach ($record->extra as $key => $value) {
$embeddedTable .= $this->addRowWithLevel($record->level, (string) $key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRowWithLevel($record['level'], 'Extra', $embeddedTable, false);
$output .= $this->addRowWithLevel($record->level, 'Extra', $embeddedTable, false);
}

return $output . '</table>';
Expand All @@ -73,18 +59,18 @@ protected function convertToString($data): string
/**
* Creates an HTML table row with background cellon title cell.
*
* @param int $level Error level
* @param Level $level Error level
* @param string $th Row header content
* @param string $td Row standard cell content
* @param bool $escapeTd false if td content must not be html escaped
*/
private function addRowWithLevel(int $level, string $th, string $td = ' ', bool $escapeTd = true): string
private function addRowWithLevel(Level $level, string $th, string $td = ' ', bool $escapeTd = true): string
{
$th = htmlspecialchars($th, \ENT_NOQUOTES, 'UTF-8');
if ($escapeTd) {
$td = '<pre>' . htmlspecialchars($td, \ENT_NOQUOTES, 'UTF-8') . '</pre>';
}

return "<tr style=\"padding: 4px;spacing: 0;text-align: left;\">\n<th style=\"background:" . $this->logLevels[$level] . "\" width=\"100px\">$th:</th>\n<td style=\"padding: 4px;spacing: 0;text-align: left;background: #eeeeee\">" . $td . "</td>\n</tr>";
return "<tr style=\"padding: 4px;spacing: 0;text-align: left;\">\n<th style=\"background:" . $this->getLevelColor($level) . "\" width=\"100px\">$th:</th>\n<td style=\"padding: 4px;spacing: 0;text-align: left;background: #eeeeee\">" . $td . "</td>\n</tr>";
}
}
66 changes: 14 additions & 52 deletions src/Monolog/Handler/GrabyHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,24 @@

use Graby\Monolog\Formatter\GrabyFormatter;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Level;
use Monolog\Logger;
use Monolog\LogRecord;
use Monolog\Processor\PsrLogMessageProcessor;
use Psr\Log\LogLevel;

/**
* Custom handler to keep all related log
* and be able to display them in the test page.
*/
class GrabyHandler extends AbstractProcessingHandler
{
/**
* @var array<array{
* channel: string,
* level: int,
* level_name: string,
* datetime: \DateTimeInterface,
* message: string,
* formatted?: string,
* context: array<string, mixed>,
* extra: array<string, mixed>,
* }>
*/
/** @var LogRecord[] */
protected array $records = [];
/**
* @var array<int, array{
* channel: string,
* level: int,
* level_name: string,
* datetime: \DateTimeInterface,
* message: string,
* formatted?: string,
* context: array<string, mixed>,
* extra: array<string, mixed>,
* }>
*/
/** @phpstan-var array<value-of<Level::VALUES>, LogRecord[]> */
protected array $recordsByLevel = [];

public function __construct($level = Logger::DEBUG, $bubble = true)
public function __construct($level = Level::Debug, $bubble = true)
{
parent::__construct($level, $bubble);

Expand All @@ -51,16 +32,7 @@ public function __construct($level = Logger::DEBUG, $bubble = true)
}

/**
* @return array<array{
* channel: string,
* level: int,
* level_name: string,
* datetime: \DateTimeInterface,
* message: string,
* formatted?: string,
* context: array<string, mixed>,
* extra: array<string, mixed>,
* }>
* @return array<LogRecord>
*/
public function getRecords(): array
{
Expand All @@ -74,28 +46,18 @@ public function clear(): void
}

/**
* @param string|int $level Logging level value or name
* @param int|string|Level|LogLevel::* $level Logging level value or name
*
* @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level
*/
public function hasRecords($level): bool
public function hasRecords(int|string|Level $level): bool
{
return isset($this->recordsByLevel[$level]);
return isset($this->recordsByLevel[Logger::toMonologLevel($level)->value]);
}

/**
* @param array{
* channel: string,
* level: int,
* level_name: string,
* datetime: \DateTimeInterface,
* message: string,
* formatted?: string,
* context: array<string, mixed>,
* extra: array<string, mixed>,
* } $record
*/
protected function write(array $record): void
protected function write(LogRecord $record): void
{
$this->recordsByLevel[$record['level']][] = $record;
$this->recordsByLevel[$record->level->value][] = $record;
$this->records[] = $record;
}
}
2 changes: 1 addition & 1 deletion tests/Extractor/ContentExtractorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ public function testIframeEmbeddedContent(): void
public function testLogMessage(): void
{
$logger = new Logger('foo');
$handler = new TestHandler($level = Logger::INFO);
$handler = new TestHandler($level = \Monolog\Level::Info);
$logger->pushHandler($handler);

$contentExtractor = new ContentExtractor(self::CONTENT_EXTRACTOR_CONFIG);
Expand Down
2 changes: 1 addition & 1 deletion tests/GrabyFunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class GrabyFunctionalTest extends TestCase
public function testRealFetchContent(): void
{
$logger = new Logger('foo');
$handler = new TestHandler($level = Logger::INFO);
$handler = new TestHandler($level = \Monolog\Level::Info);
$logger->pushHandler($handler);
$httpMockClient = new HttpMockClient();
$httpMockClient->addResponse(new Response(200, ['Connection' => ['keep-alive'], 'Content-Type' => ['text/html; charset=UTF-8'], 'X-Protected-By' => ['Sqreen'], 'Set-Cookie' => ['critical-article-free-desktop=f63cb50f6847971760d55dff34849007; expires=Thu, 10-Feb-2022 19:11:31 GMT; Max-Age=2592000; path=/; secure'], 'X-Frame-Options' => ['SAMEORIGIN'], 'X-XSS-Protection' => ['1; mode=block'], 'Via' => ['1.1 google, 1.1 varnish, 1.1 varnish'], 'Cache-Control' => ['private, max-age=0'], 'Accept-Ranges' => ['bytes'], 'Date' => ['Tue, 11 Jan 2022 19:11:31 GMT'], 'X-Served-By' => ['cache-cdg20730-CDG, cache-fra19144-FRA'], 'X-Cache' => ['MISS, MISS'], 'X-Cache-Hits' => ['0, 0'], 'X-Timer' => ['S1641928291.389187,VS0,VE95'], 'Vary' => ['Accept-Encoding'], 'Strict-Transport-Security' => ['max-age=31557600'], 'transfer-encoding' => ['chunked']], (string) file_get_contents(__DIR__ . '/fixtures/content/https___www.lemonde.fr_actualite-medias_article_2015_04_12_radio-france-vers-une-sortie-du-conflit_4614610_3236.html')));
Expand Down
19 changes: 10 additions & 9 deletions tests/Monolog/Formatter/GrabyFormatterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,31 @@
namespace Tests\Graby\Monolog\Formatter;

use Graby\Monolog\Formatter\GrabyFormatter;
use Monolog\Logger;
use Monolog\LogRecord;
use PHPUnit\Framework\TestCase;

class GrabyFormatterTest extends TestCase
{
public function testFormat(): void
{
$formatter = new GrabyFormatter();
$res = $formatter->formatBatch([[
'channel' => 'graby',
'level' => 100,
'level_name' => 'ALERT',
'datetime' => new \DateTimeImmutable(),
'message' => 'This is a log message',
'context' => [
$res = $formatter->formatBatch([new LogRecord(
message: 'This is a log message',
context: [
'cursor' => 'here',
'success' => true,
],
'extra' => [
level: Logger::toMonologLevel(100),
channel: 'graby',
datetime: new \DateTimeImmutable(),
extra: [
'complex' => [
'interesting' => 'ok',
],
'success' => true,
],
]]);
)]);

$this->assertStringContainsString('<pre>This is a log message</pre>', $res);
$this->assertStringContainsString('<pre>(bool) true</pre>', $res);
Expand Down
32 changes: 21 additions & 11 deletions tests/Monolog/Handler/GrabyHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,41 @@
namespace Tests\Graby\Monolog\Handler;

use Graby\Monolog\Handler\GrabyHandler;
use Monolog\Level;
use Monolog\Logger;
use Monolog\LogRecord;
use PHPUnit\Framework\TestCase;

class GrabyHandlerTest extends TestCase
{
public function testFormat(): void
{
$handler = new GrabyHandler();
$handler->handle([
'channel' => 'graby',
'level' => 100,
'level_name' => 'ALERT',
'datetime' => new \DateTimeImmutable(),
'message' => 'message',
'context' => [],
'extra' => [],
]);
$handler->handle(new LogRecord(
message: 'This is a log message',
context: [
'cursor' => 'here',
'success' => true,
],
level: Logger::toMonologLevel(100),
channel: 'graby',
datetime: new \DateTimeImmutable(),
extra: [
'complex' => [
'interesting' => 'ok',
],
'success' => true,
],
)
);

$this->assertCount(1, $handler->getRecords());
$this->assertArrayHasKey('formatted', $handler->getRecords()[0]);
$this->assertTrue($handler->hasRecords(Logger::DEBUG));
$this->assertTrue($handler->hasRecords(Level::Debug));

$handler->clear();

$this->assertCount(0, $handler->getRecords());
$this->assertFalse($handler->hasRecords(Logger::DEBUG));
$this->assertFalse($handler->hasRecords(Level::Debug));
}
}