Skip to content

Commit 62058e2

Browse files
committed
logger refactoring
1 parent 6b9675e commit 62058e2

6 files changed

Lines changed: 318 additions & 77 deletions

File tree

src/Tracy/BlueScreenLogger.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Tracy (https://tracy.nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
namespace Tracy;
9+
10+
11+
/**
12+
* Logger which generates BlueScreen file.
13+
*/
14+
class BlueScreenLogger implements ILogger
15+
{
16+
/** @var string|null name of the directory where errors should be logged */
17+
public $directory;
18+
19+
/** @var BlueScreen */
20+
private $blueScreen;
21+
22+
23+
public function __construct(?string $directory, ?BlueScreen $blueScreen = null)
24+
{
25+
$this->directory = $directory;
26+
$this->blueScreen = $blueScreen;
27+
}
28+
29+
30+
public function log($message, string $priority = self::INFO): ?string
31+
{
32+
if (!$this->directory) {
33+
throw new \LogicException('Logging directory is not specified.');
34+
35+
} elseif (!is_dir($this->directory)) {
36+
throw new \RuntimeException("Logging directory '$this->directory' is not found or is not directory.");
37+
}
38+
39+
if ($message instanceof \Throwable) {
40+
return $this->logException($message);
41+
}
42+
43+
return null;
44+
}
45+
46+
47+
public function getExceptionFile(\Throwable $exception): string
48+
{
49+
while ($exception) {
50+
$data[] = [
51+
get_class($exception), $exception->getMessage(), $exception->getCode(), $exception->getFile(), $exception->getLine(),
52+
array_map(function ($item) { unset($item['args']); return $item; }, $exception->getTrace()),
53+
];
54+
$exception = $exception->getPrevious();
55+
}
56+
$hash = substr(md5(serialize($data)), 0, 10);
57+
$dir = strtr($this->directory . '/', '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR);
58+
foreach (new \DirectoryIterator($this->directory) as $file) {
59+
if (strpos($file->getBasename(), $hash)) {
60+
return $dir . $file;
61+
}
62+
}
63+
return $dir . 'exception--' . @date('Y-m-d--H-i') . "--$hash.html"; // @ timezone may not be set
64+
}
65+
66+
67+
/**
68+
* Logs exception to the file if file doesn't exist.
69+
* @return string logged error filename
70+
*/
71+
protected function logException(\Throwable $exception, ?string $file = null): string
72+
{
73+
$file = $file ?? $this->getExceptionFile($exception);
74+
$bs = $this->blueScreen ?? new BlueScreen;
75+
$bs->renderToFile($exception, $file);
76+
return $file;
77+
}
78+
}

src/Tracy/Debugger.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace Tracy;
1111

1212
use ErrorException;
13+
use Tracy;
1314

1415

1516
/**
@@ -139,11 +140,11 @@ final public function __construct()
139140

140141
/**
141142
* Enables displaying or logging errors and exceptions.
142-
* @param mixed $mode production, development mode, autodetection or IP address(es) whitelist.
143-
* @param string $logDirectory error log directory
144-
* @param string $email administrator email; enables email sending in production mode
143+
* @param mixed $mode production, development mode, autodetection or IP address(es) whitelist.
144+
* @param string|ILogger $logDirectoryOrLogger error log directory or logger instance
145+
* @param string $email administrator email; enables email sending in production mode
145146
*/
146-
public static function enable($mode = null, string $logDirectory = null, string $email = null): void
147+
public static function enable($mode = null, $logDirectoryOrLogger = null, string $email = null)
147148
{
148149
if ($mode !== null || self::$productionMode === null) {
149150
self::$productionMode = is_bool($mode) ? $mode : !self::detectDebugMode($mode);
@@ -158,8 +159,10 @@ public static function enable($mode = null, string $logDirectory = null, string
158159
if ($email !== null) {
159160
self::$email = $email;
160161
}
161-
if ($logDirectory !== null) {
162-
self::$logDirectory = $logDirectory;
162+
if (is_string($logDirectoryOrLogger)) {
163+
self::$logDirectory = $logDirectoryOrLogger;
164+
} elseif ($logDirectoryOrLogger instanceof ILogger) {
165+
self::$logger = $logDirectoryOrLogger;
163166
}
164167
if (self::$logDirectory) {
165168
if (!preg_match('#([a-z]+:)?[/\\\\]#Ai', self::$logDirectory)) {
@@ -466,8 +469,8 @@ public static function getLogger(): ILogger
466469
{
467470
if (!self::$logger) {
468471
self::$logger = new Logger(self::$logDirectory, self::$email, self::getBlueScreen());
469-
self::$logger->directory = &self::$logDirectory; // back compatiblity
470-
self::$logger->email = &self::$email;
472+
self::$logDirectory = &self::$logger->directory; // back compatibility
473+
self::$email = &self::$logger->email;
471474
}
472475
return self::$logger;
473476
}

src/Tracy/Helpers.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,29 @@ public static function getNonce(): ?string
262262
? $m[1]
263263
: null;
264264
}
265+
266+
267+
/**
268+
* @param string|\Exception|\Throwable
269+
* @return string
270+
* @internal
271+
*/
272+
public static function formatMessage($message)
273+
{
274+
if ($message instanceof \Exception || $message instanceof \Throwable) {
275+
while ($message) {
276+
$tmp[] = ($message instanceof \ErrorException
277+
? Helpers::errorTypeToString($message->getSeverity()) . ': ' . $message->getMessage()
278+
: Helpers::getClass($message) . ': ' . $message->getMessage() . ($message->getCode() ? ' #' . $message->getCode() : '')
279+
) . ' in ' . $message->getFile() . ':' . $message->getLine();
280+
$message = $message->getPrevious();
281+
}
282+
$message = implode("\ncaused by ", $tmp);
283+
284+
} elseif (!is_string($message)) {
285+
$message = Dumper::toText($message);
286+
}
287+
288+
return trim($message);
289+
}
265290
}

src/Tracy/Logger.php

Lines changed: 59 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ class Logger implements ILogger
3333
/** @var BlueScreen|null */
3434
private $blueScreen;
3535

36+
/** @var StreamLogger[] */
37+
private $streamLoggers;
38+
39+
/** @var BlueScreenLogger */
40+
private $blueScreenLogger;
41+
42+
/** @var MailLogger */
43+
private $mailLogger;
44+
3645

3746
/**
3847
* @param string|array|null $email
@@ -43,6 +52,9 @@ public function __construct(?string $directory, $email = null, BlueScreen $blueS
4352
$this->email = $email;
4453
$this->blueScreen = $blueScreen;
4554
$this->mailer = [$this, 'defaultMailer'];
55+
56+
$this->blueScreenLogger = $this->createBlueScreenLogger();
57+
$this->mailLogger = $this->createMailLogger();
4658
}
4759

4860

@@ -53,32 +65,49 @@ public function __construct(?string $directory, $email = null, BlueScreen $blueS
5365
* @return string|null logged error filename
5466
*/
5567
public function log($message, string $priority = self::INFO): ?string
68+
{
69+
$exceptionFile = $this->getStreamLogger($priority)->log($message, $priority);
70+
$this->mailLogger->log($message, $priority);
71+
return $exceptionFile;
72+
}
73+
74+
75+
protected function getStreamLogger($priority)
5676
{
5777
if (!$this->directory) {
5878
throw new \LogicException('Logging directory is not specified.');
5979
} elseif (!is_dir($this->directory)) {
6080
throw new \RuntimeException("Logging directory '$this->directory' is not found or is not directory.");
6181
}
6282

63-
$exceptionFile = $message instanceof \Throwable
64-
? $this->getExceptionFile($message)
65-
: null;
66-
$line = static::formatLogLine($message, $exceptionFile);
67-
$file = $this->directory . '/' . strtolower($priority ?: self::INFO) . '.log';
68-
69-
if (!@file_put_contents($file, $line . PHP_EOL, FILE_APPEND | LOCK_EX)) { // @ is escalated to exception
70-
throw new \RuntimeException("Unable to write to log file '$file'. Is directory writable?");
83+
$path = $this->directory . '/' . strtolower($priority ?: self::INFO) . '.log';
84+
if (!isset($this->streamLoggers[$path])) {
85+
$this->streamLoggers[$path] = new StreamLogger($path, $this->blueScreenLogger);
7186
}
7287

73-
if ($exceptionFile) {
74-
$this->logException($message, $exceptionFile);
75-
}
88+
return $this->streamLoggers[$path];
89+
}
7690

77-
if (in_array($priority, [self::ERROR, self::EXCEPTION, self::CRITICAL], true)) {
78-
$this->sendEmail($message);
79-
}
8091

81-
return $exceptionFile;
92+
protected function createBlueScreenLogger()
93+
{
94+
$blueScreenLogger = new BlueScreenLogger($this->directory, $this->blueScreen);
95+
$blueScreenLogger->directory = &$this->directory;
96+
97+
return $blueScreenLogger;
98+
}
99+
100+
101+
protected function createMailLogger()
102+
{
103+
$mailLogger = new MailLogger($this->directory, $this->email);
104+
$mailLogger->directory = &$this->directory;
105+
$mailLogger->email = &$this->email;
106+
$mailLogger->fromEmail = &$this->fromEmail;
107+
$mailLogger->emailSnooze = &$this->emailSnooze;
108+
$mailLogger->mailer = &$this->mailer;
109+
110+
return $mailLogger;
82111
}
83112

84113

@@ -119,82 +148,43 @@ public static function formatLogLine($message, string $exceptionFile = null): st
119148
}
120149

121150

151+
/**
152+
* @deprecated
153+
*/
122154
public function getExceptionFile(\Throwable $exception): string
123155
{
124-
while ($exception) {
125-
$data[] = [
126-
get_class($exception), $exception->getMessage(), $exception->getCode(), $exception->getFile(), $exception->getLine(),
127-
array_map(function ($item) { unset($item['args']); return $item; }, $exception->getTrace()),
128-
];
129-
$exception = $exception->getPrevious();
130-
}
131-
$hash = substr(md5(serialize($data)), 0, 10);
132-
$dir = strtr($this->directory . '/', '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR);
133-
foreach (new \DirectoryIterator($this->directory) as $file) {
134-
if (strpos($file->getBasename(), $hash)) {
135-
return $dir . $file;
136-
}
137-
}
138-
return $dir . 'exception--' . @date('Y-m-d--H-i') . "--$hash.html"; // @ timezone may not be set
156+
return $this->blueScreenLogger->getExceptionFile($exception);
139157
}
140158

141159

142160
/**
143-
* Logs exception to the file if file doesn't exist.
144-
* @return string logged error filename
161+
* @deprecated
145162
*/
146-
protected function logException(\Throwable $exception, string $file = null): string
163+
protected function logException(\Throwable $exception, ?string $file = null): string
147164
{
148-
$file = $file ?: $this->getExceptionFile($exception);
149-
$bs = $this->blueScreen ?: new BlueScreen;
150-
$bs->renderToFile($exception, $file);
151-
return $file;
165+
$reflection = new \ReflectionMethod($this->blueScreenLogger, 'logException');
166+
$reflection->setAccessible(true);
167+
return $reflection->invoke($this->blueScreenLogger, $exception, $file);
152168
}
153169

154170

155171
/**
156-
* @param mixed $message
172+
* @deprecated
157173
*/
158174
protected function sendEmail($message): void
159175
{
160-
$snooze = is_numeric($this->emailSnooze)
161-
? $this->emailSnooze
162-
: @strtotime($this->emailSnooze) - time(); // @ timezone may not be set
163-
164-
if (
165-
$this->email
166-
&& $this->mailer
167-
&& @filemtime($this->directory . '/email-sent') + $snooze < time() // @ file may not exist
168-
&& @file_put_contents($this->directory . '/email-sent', 'sent') // @ file may not be writable
169-
) {
170-
($this->mailer)($message, implode(', ', (array) $this->email));
171-
}
176+
$reflection = new \ReflectionMethod($this->mailLogger, 'sendEmail');
177+
$reflection->setAccessible(true);
178+
$reflection->invoke($this->mailLogger, $message);
172179
}
173180

174181

175182
/**
176-
* Default mailer.
177-
* @param mixed $message
183+
* @deprecated
178184
* @internal
179185
*/
180186
public function defaultMailer($message, string $email): void
181187
{
182-
$host = preg_replace('#[^\w.-]+#', '', $_SERVER['HTTP_HOST'] ?? php_uname('n'));
183-
$parts = str_replace(
184-
["\r\n", "\n"],
185-
["\n", PHP_EOL],
186-
[
187-
'headers' => implode("\n", [
188-
'From: ' . ($this->fromEmail ?: "noreply@$host"),
189-
'X-Mailer: Tracy',
190-
'Content-Type: text/plain; charset=UTF-8',
191-
'Content-Transfer-Encoding: 8bit',
192-
]) . "\n",
193-
'subject' => "PHP: An error occurred on the server $host",
194-
'body' => static::formatMessage($message) . "\n\nsource: " . Helpers::getSource(),
195-
]
196-
);
197-
198-
mail($email, $parts['subject'], $parts['body'], $parts['headers']);
188+
$this->mailLogger->defaultMailer($message, $email);
199189
}
200190
}

0 commit comments

Comments
 (0)