Skip to content

Commit 4402433

Browse files
authored
Merge pull request #222 from clue-labs/logstream
Refactor logging into new `LogStreamHandler`
2 parents cdef98b + 5bf9c88 commit 4402433

8 files changed

Lines changed: 352 additions & 91 deletions

src/AccessLogHandler.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace FrameworkX;
44

5-
use FrameworkX\Io\SapiHandler;
5+
use FrameworkX\Io\LogStreamHandler;
66
use Psr\Http\Message\ResponseInterface;
77
use Psr\Http\Message\ServerRequestInterface;
88
use React\Http\Message\Response;
@@ -14,15 +14,17 @@
1414
*/
1515
class AccessLogHandler
1616
{
17-
/** @var SapiHandler */
18-
private $sapi;
17+
/** @var LogStreamHandler */
18+
private $logger;
1919

2020
/** @var bool */
2121
private $hasHighResolution;
2222

23+
/** @throws void */
2324
public function __construct()
2425
{
25-
$this->sapi = new SapiHandler();
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');
2628
$this->hasHighResolution = \function_exists('hrtime'); // PHP 7.3+
2729
}
2830

@@ -85,7 +87,7 @@ private function log(ServerRequestInterface $request, ResponseInterface $respons
8587
$responseSize = 0;
8688
}
8789

88-
$this->sapi->log(
90+
$this->logger->log(
8991
($request->getAttribute('remote_addr') ?? $request->getServerParams()['REMOTE_ADDR'] ?? '-') . ' ' .
9092
'"' . $this->escape($method) . ' ' . $this->escape($request->getRequestTarget()) . ' HTTP/' . $request->getProtocolVersion() . '" ' .
9193
$status . ' ' . $responseSize . ' ' . sprintf('%.3F', $time < 0 ? 0 : $time)

src/App.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace FrameworkX;
44

55
use FrameworkX\Io\FiberHandler;
6+
use FrameworkX\Io\LogStreamHandler;
67
use FrameworkX\Io\MiddlewareHandler;
78
use FrameworkX\Io\RedirectHandler;
89
use FrameworkX\Io\RouteHandler;
@@ -24,9 +25,12 @@ class App
2425
/** @var RouteHandler */
2526
private $router;
2627

27-
/** @var SapiHandler */
28+
/** @var ?SapiHandler */
2829
private $sapi;
2930

31+
/** @var ?LogStreamHandler */
32+
private $logger;
33+
3034
/** @var Container */
3135
private $container;
3236

@@ -115,7 +119,8 @@ public function __construct(...$middleware)
115119
$this->router = new RouteHandler($this->container);
116120
$handlers[] = $this->router;
117121
$this->handler = new MiddlewareHandler($handlers);
118-
$this->sapi = new SapiHandler();
122+
$this->sapi = (\PHP_SAPI !== 'cli' ? new SapiHandler() : null);
123+
$this->logger = (\PHP_SAPI === 'cli' ? new LogStreamHandler('php://output') : null);
119124
}
120125

121126
/**
@@ -231,6 +236,9 @@ public function run(): void
231236

232237
private function runLoop(): void
233238
{
239+
$logger = $this->logger;
240+
assert($logger instanceof LogStreamHandler);
241+
234242
$http = new HttpServer(function (ServerRequestInterface $request) {
235243
return $this->handleRequest($request);
236244
});
@@ -240,31 +248,31 @@ private function runLoop(): void
240248
$socket = new SocketServer($listen);
241249
$http->listen($socket);
242250

243-
$this->sapi->log('Listening on ' . \str_replace('tcp:', 'http:', (string) $socket->getAddress()));
251+
$logger->log('Listening on ' . \str_replace('tcp:', 'http:', (string) $socket->getAddress()));
244252

245-
$http->on('error', function (\Exception $e): void {
246-
$this->sapi->log('HTTP error: ' . $e->getMessage());
253+
$http->on('error', static function (\Exception $e) use ($logger): void {
254+
$logger->log('HTTP error: ' . $e->getMessage());
247255
});
248256

249257
// @codeCoverageIgnoreStart
250258
try {
251-
Loop::addSignal(\defined('SIGINT') ? \SIGINT : 2, $f1 = function () use ($socket) {
259+
Loop::addSignal(\defined('SIGINT') ? \SIGINT : 2, $f1 = static function () use ($socket, $logger) {
252260
if (\PHP_VERSION_ID >= 70200 && \stream_isatty(\STDIN)) {
253261
echo "\r";
254262
}
255-
$this->sapi->log('Received SIGINT, stopping loop');
263+
$logger->log('Received SIGINT, stopping loop');
256264

257265
$socket->close();
258266
Loop::stop();
259267
});
260-
Loop::addSignal(\defined('SIGTERM') ? \SIGTERM : 15, $f2 = function () use ($socket) {
261-
$this->sapi->log('Received SIGTERM, stopping loop');
268+
Loop::addSignal(\defined('SIGTERM') ? \SIGTERM : 15, $f2 = static function () use ($socket, $logger) {
269+
$logger->log('Received SIGTERM, stopping loop');
262270

263271
$socket->close();
264272
Loop::stop();
265273
});
266274
} catch (\BadMethodCallException $e) {
267-
$this->sapi->log('Notice: No signal handler support, installing ext-ev or ext-pcntl recommended for production use.');
275+
$logger->log('Notice: No signal handler support, installing ext-ev or ext-pcntl recommended for production use.');
268276
}
269277
// @codeCoverageIgnoreEnd
270278

@@ -273,7 +281,7 @@ private function runLoop(): void
273281

274282
if ($socket->getAddress() !== null) {
275283
// Fiber compatibility mode for PHP < 8.1: Restart loop as long as socket is available
276-
$this->sapi->log('Warning: Loop restarted. Upgrade to react/async v4 recommended for production use.');
284+
$logger->log('Warning: Loop restarted. Upgrade to react/async v4 recommended for production use.');
277285
} else {
278286
break;
279287
}
@@ -286,6 +294,7 @@ private function runLoop(): void
286294

287295
private function runOnce(): void
288296
{
297+
assert($this->sapi instanceof SapiHandler);
289298
$request = $this->sapi->requestFromGlobals();
290299

291300
$response = $this->handleRequest($request);
@@ -294,6 +303,7 @@ private function runOnce(): void
294303
$this->sapi->sendResponse($response);
295304
} elseif ($response instanceof PromiseInterface) {
296305
$response->then(function (ResponseInterface $response) {
306+
assert($this->sapi instanceof SapiHandler);
297307
$this->sapi->sendResponse($response);
298308
});
299309
}

src/Io/LogStreamHandler.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace FrameworkX\Io;
4+
5+
/**
6+
* @internal
7+
*/
8+
class LogStreamHandler
9+
{
10+
/** @var resource */
11+
private $stream;
12+
13+
/** @throws \RuntimeException if given `$path` can not be opened in append mode */
14+
public function __construct(string $path)
15+
{
16+
$errstr = '';
17+
\set_error_handler(function (int $_, string $error) use (&$errstr): bool {
18+
// Match errstr from PHP's warning message.
19+
// fopen(/dev/not-a-valid-path): Failed to open stream: Permission denied
20+
$errstr = \preg_replace('#.*: #', '', $error);
21+
22+
return true;
23+
});
24+
25+
$stream = \fopen($path, 'ae');
26+
\restore_error_handler();
27+
28+
if ($stream === false) {
29+
throw new \RuntimeException(
30+
'Unable to open log file "' . $path . '": ' . $errstr
31+
);
32+
}
33+
34+
$this->stream = $stream;
35+
}
36+
37+
public function log(string $message): void
38+
{
39+
$time = \microtime(true);
40+
$prefix = \date('Y-m-d H:i:s', (int) $time) . \sprintf('.%03d ', (int) (($time - (int) $time) * 1e3));
41+
42+
$ret = \fwrite($this->stream, $prefix . $message . \PHP_EOL);
43+
assert(\is_int($ret));
44+
}
45+
}

src/Io/SapiHandler.php

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,6 @@
1313
*/
1414
class SapiHandler
1515
{
16-
/** @var resource */
17-
private $logStream;
18-
19-
public function __construct()
20-
{
21-
// @phpstan-ignore-next-line because `fopen()` is known to always return a `resource` for built-in wrappers
22-
$this->logStream = PHP_SAPI === 'cli' ? \fopen('php://output', 'a') : (\defined('STDERR') ? \STDERR : \fopen('php://stderr', 'a'));
23-
}
24-
2516
public function requestFromGlobals(): ServerRequestInterface
2617
{
2718
$host = null;
@@ -140,12 +131,4 @@ public function sendResponse(ResponseInterface $response): void
140131
echo $body;
141132
}
142133
}
143-
144-
public function log(string $message): void
145-
{
146-
$time = microtime(true);
147-
$log = date('Y-m-d H:i:s', (int)$time) . sprintf('.%03d ', (int)(($time - (int)$time) * 1e3)) . $message . PHP_EOL;
148-
149-
fwrite($this->logStream, $log);
150-
}
151134
}

0 commit comments

Comments
 (0)