Skip to content

Commit 37630e3

Browse files
committed
Add optional $path argument for AccessLogHandler
1 parent fe0df4e commit 37630e3

5 files changed

Lines changed: 177 additions & 45 deletions

File tree

docs/api/app.md

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,62 @@ $app = new FrameworkX\App($container);
362362
// …
363363
```
364364

365+
If you do not want to log to the console, you can configure an absolute log file
366+
path by passing an argument to the [`AccessLogHandler`](middleware.md#accessloghandler)
367+
like this:
368+
369+
=== "Using DI container"
370+
371+
```php title="public/index.php"
372+
<?php
373+
374+
require __DIR__ . '/../vendor/autoload.php';
375+
376+
$container = new FrameworkX\Container([
377+
'accesslog' => __DIR__ . '/../logs/access.log',
378+
FrameworkX\AccessLogHandler::class => fn(string $accesslog) => new FrameworkX\AccessLogHandler($accesslog)
379+
]);
380+
381+
$app = new FrameworkX\App($container);
382+
383+
// …
384+
```
385+
386+
=== "Using middleware instances"
387+
388+
```php title="public/index.php"
389+
<?php
390+
391+
require __DIR__ . '/../vendor/autoload.php';
392+
393+
$app = new FrameworkX\App(
394+
new FrameworkX\AccessLogHandler(__DIR__ . '/../logs/access.log'),
395+
new FrameworkX\ErrorHandler()
396+
);
397+
398+
399+
400+
// …
401+
```
402+
403+
Likewise, you can disable writing an access log by passing an absolute path to
404+
`/dev/null` (Unix) or `nul` (Windows) like this:
405+
406+
```php title="public/index.php"
407+
<?php
408+
409+
require __DIR__ . '/../vendor/autoload.php';
410+
411+
$container = new FrameworkX\Container([
412+
'accesslog' => DIRECTORY_SEPARATOR !== '\\' ? '/dev/null' : __DIR__ . '\\nul'
413+
FrameworkX\AccessLogHandler::class => fn(string $accesslog) => new FrameworkX\AccessLogHandler($accesslog),
414+
]);
415+
416+
$app = new FrameworkX\App($container);
417+
418+
// …
419+
```
420+
365421
X supports running behind reverse proxies just fine. However, by default it will
366422
see the IP address of the last proxy server as the client IP address (this will
367423
often be `127.0.0.1`). You can get the original client IP address if you configure
@@ -385,8 +441,6 @@ it to the [`AccessLogHandler`](middleware.md#accessloghandler) like this:
385441
new FrameworkX\ErrorHandler()
386442
);
387443

388-
$app = new FrameworkX\App($container);
389-
390444
// …
391445
```
392446

@@ -405,8 +459,6 @@ it to the [`AccessLogHandler`](middleware.md#accessloghandler) like this:
405459
FrameworkX\ErrorHandler::class
406460
);
407461

408-
$app = new FrameworkX\App($container);
409-
410462
// …
411463
```
412464

src/AccessLogHandler.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,18 @@ class AccessLogHandler
2020
/** @var bool */
2121
private $hasHighResolution;
2222

23-
/** @throws void */
24-
public function __construct()
23+
/**
24+
* @param ?string $path (optional) absolute log file path or will log to console output by default
25+
* @throws \InvalidArgumentException if given `$path` is not an absolute file path
26+
* @throws \RuntimeException if given `$path` can not be opened in append mode
27+
*/
28+
public function __construct(?string $path = null)
2529
{
26-
/** @throws void because `fopen()` is known to always return a `resource` for built-in wrappers */
27-
$this->logger = new LogStreamHandler(\PHP_SAPI === 'cli' ? 'php://output' : 'php://stderr');
30+
if ($path === null) {
31+
$path = \PHP_SAPI === 'cli' ? 'php://output' : 'php://stderr';
32+
}
33+
34+
$this->logger = new LogStreamHandler($path);
2835
$this->hasHighResolution = \function_exists('hrtime'); // PHP 7.3+
2936
}
3037

tests/AccessLogHandlerTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,105 @@
1212

1313
class AccessLogHandlerTest extends TestCase
1414
{
15+
public function testCtorWithRelativePathThrows(): void
16+
{
17+
$this->expectException(\InvalidArgumentException::class);
18+
new AccessLogHandler('../access.log');
19+
}
20+
21+
public function testCtorWithPathToDirectoryThrows(): void
22+
{
23+
$this->expectException(\RuntimeException::class);
24+
new AccessLogHandler(__DIR__);
25+
}
26+
27+
public function testCtorWithPathToNewFileWillCreateNewFile(): void
28+
{
29+
$path = tempnam(sys_get_temp_dir(), 'log');
30+
assert(is_string($path));
31+
unlink($path);
32+
33+
new AccessLogHandler($path);
34+
35+
$this->assertFileExists($path);
36+
unlink($path);
37+
}
38+
39+
public function testInvokeWithDefaultPathWillLogMessageToConsole(): void
40+
{
41+
$handler = new AccessLogHandler();
42+
43+
$request = new ServerRequest('GET', 'http://localhost:8080/users', [], '', '1.1', ['REMOTE_ADDR' => '127.0.0.1']);
44+
$response = new Response(200, [], "Hello\n");
45+
46+
$this->expectOutputRegex('#^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d 127\.0\.0\.1 "GET /users HTTP/1\.1" 200 6 0\.0\d\d' . PHP_EOL . '$#');
47+
$handler($request, function () use ($response) { return $response; });
48+
}
49+
50+
public function testInvokeWithPathToNewFileWillCreateNewFileWithLogMessage(): void
51+
{
52+
$path = tempnam(sys_get_temp_dir(), 'log');
53+
assert(is_string($path));
54+
unlink($path);
55+
56+
$handler = new AccessLogHandler($path);
57+
58+
$request = new ServerRequest('GET', 'http://localhost:8080/users', [], '', '1.1', ['REMOTE_ADDR' => '127.0.0.1']);
59+
$response = new Response(200, [], "Hello\n");
60+
$handler($request, function () use ($response) { return $response; });
61+
62+
$log = file_get_contents($path);
63+
assert(is_string($log));
64+
65+
if (method_exists($this, 'assertMatchesRegularExpression')) {
66+
$this->assertMatchesRegularExpression('#^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d 127\.0\.0\.1 "GET /users HTTP/1\.1" 200 6 0\.0\d\d' . PHP_EOL . '$#', $log);
67+
} else {
68+
// legacy PHPUnit < 9.1
69+
$this->assertRegExp('#^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d 127\.0\.0\.1 "GET /users HTTP/1\.1" 200 6 0\.0\d\d' . PHP_EOL . '$#', $log);
70+
}
71+
72+
unset($handler);
73+
unlink($path);
74+
}
75+
76+
public function testInvokeWithPathToExistingFileWillAppendLogMessage(): void
77+
{
78+
$path = tempnam(sys_get_temp_dir(), 'log');
79+
assert(is_string($path));
80+
file_put_contents($path, 'first' . PHP_EOL);
81+
82+
$handler = new AccessLogHandler($path);
83+
84+
$request = new ServerRequest('GET', 'http://localhost:8080/users', [], '', '1.1', ['REMOTE_ADDR' => '127.0.0.1']);
85+
$response = new Response(200, [], "Hello\n");
86+
$handler($request, function () use ($response) { return $response; });
87+
88+
$log = file_get_contents($path);
89+
assert(is_string($log));
90+
91+
if (method_exists($this, 'assertMatchesRegularExpression')) {
92+
$this->assertMatchesRegularExpression('#^first' . PHP_EOL . '\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d 127\.0\.0\.1 "GET /users HTTP/1\.1" 200 6 0\.0\d\d' . PHP_EOL . '$#', $log);
93+
} else {
94+
// legacy PHPUnit < 9.1
95+
$this->assertRegExp('#^first' . PHP_EOL . '\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d 127\.0\.0\.1 "GET /users HTTP/1\.1" 200 6 0\.0\d\d' . PHP_EOL . '$#', $log);
96+
}
97+
98+
unset($handler);
99+
unlink($path);
100+
}
101+
102+
/**
103+
* @doesNotPerformAssertions
104+
*/
105+
public function testInvokeWithDevNullWritesNothing(): void
106+
{
107+
$handler = new AccessLogHandler(DIRECTORY_SEPARATOR !== '\\' ? '/dev/null' : __DIR__ . '\\nul');
108+
109+
$request = new ServerRequest('GET', 'http://localhost:8080/users', [], '', '1.1', ['REMOTE_ADDR' => '127.0.0.1']);
110+
$response = new Response(200, [], "Hello\n");
111+
$handler($request, function () use ($response) { return $response; });
112+
}
113+
15114
public function testInvokeLogsRequest(): void
16115
{
17116
$handler = new AccessLogHandler();

tests/AppMiddlewareTest.php

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use FrameworkX\AccessLogHandler;
66
use FrameworkX\App;
7-
use FrameworkX\Io\MiddlewareHandler;
7+
use FrameworkX\ErrorHandler;
88
use FrameworkX\Io\RouteHandler;
99
use PHPUnit\Framework\TestCase;
1010
use Psr\Http\Message\ResponseInterface;
@@ -674,23 +674,10 @@ public function testInvokeWithGlobalMiddlewareReturnsResponseWhenGlobalMiddlewar
674674
/** @param callable|class-string ...$middleware */
675675
private function createAppWithoutLogger(...$middleware): App
676676
{
677-
$app = new App(...$middleware);
678-
679-
$ref = new \ReflectionProperty($app, 'handler');
680-
$ref->setAccessible(true);
681-
$middleware = $ref->getValue($app);
682-
assert($middleware instanceof MiddlewareHandler);
683-
684-
$ref = new \ReflectionProperty($middleware, 'handlers');
685-
$ref->setAccessible(true);
686-
$handlers = $ref->getValue($middleware);
687-
assert(is_array($handlers));
688-
689-
$first = array_shift($handlers);
690-
$this->assertInstanceOf(AccessLogHandler::class, $first);
691-
692-
$ref->setValue($middleware, $handlers);
693-
694-
return $app;
677+
return new App(
678+
new AccessLogHandler(DIRECTORY_SEPARATOR !== '\\' ? '/dev/null' : __DIR__ . '\\nul'),
679+
new ErrorHandler(),
680+
...$middleware
681+
);
695682
}
696683
}

tests/AppTest.php

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,23 +1651,10 @@ public function testInvokeWithMatchingRouteReturnsInternalServerErrorResponseWhe
16511651

16521652
private function createAppWithoutLogger(callable ...$middleware): App
16531653
{
1654-
$app = new App(...$middleware);
1655-
1656-
$ref = new \ReflectionProperty($app, 'handler');
1657-
$ref->setAccessible(true);
1658-
$middleware = $ref->getValue($app);
1659-
assert($middleware instanceof MiddlewareHandler);
1660-
1661-
$ref = new \ReflectionProperty($middleware, 'handlers');
1662-
$ref->setAccessible(true);
1663-
$handlers = $ref->getValue($middleware);
1664-
assert(is_array($handlers));
1665-
1666-
$first = array_shift($handlers);
1667-
$this->assertInstanceOf(AccessLogHandler::class, $first);
1668-
1669-
$ref->setValue($middleware, $handlers);
1670-
1671-
return $app;
1654+
return new App(
1655+
new AccessLogHandler(DIRECTORY_SEPARATOR !== '\\' ? '/dev/null' : __DIR__ . '\\nul'),
1656+
new ErrorHandler(),
1657+
...$middleware
1658+
);
16721659
}
16731660
}

0 commit comments

Comments
 (0)