Skip to content

Commit 6784642

Browse files
committed
logger refactoring
1 parent 4298109 commit 6784642

7 files changed

Lines changed: 366 additions & 95 deletions

File tree

src/Tracy/BlueScreenLogger.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 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($directory, BlueScreen $blueScreen = null)
24+
{
25+
$this->directory = $directory;
26+
$this->blueScreen = $blueScreen;
27+
}
28+
29+
30+
/**
31+
* Logs message or exception to file and sends email notification.
32+
* @param string|\Exception|\Throwable
33+
* @param int one of constant ILogger::INFO, WARNING, ERROR (sends email), EXCEPTION (sends email), CRITICAL (sends email)
34+
* @return string logged error filename
35+
*/
36+
public function log($message, $priority = self::INFO)
37+
{
38+
if (!$this->directory) {
39+
throw new \LogicException('Logging directory is not specified.');
40+
41+
} elseif (!is_dir($this->directory)) {
42+
throw new \RuntimeException("Logging directory '$this->directory' is not found or is not directory.");
43+
}
44+
45+
if ($message instanceof \Exception || $message instanceof \Throwable) {
46+
$exceptionFile = $this->getExceptionFile($message);
47+
$this->logException($message, $exceptionFile);
48+
return $exceptionFile;
49+
}
50+
51+
return null;
52+
}
53+
54+
55+
/**
56+
* @param \Exception|\Throwable
57+
* @return string
58+
*/
59+
public function getExceptionFile($exception)
60+
{
61+
while ($exception) {
62+
$data[] = [
63+
get_class($exception), $exception->getMessage(), $exception->getCode(), $exception->getFile(), $exception->getLine(),
64+
array_map(function ($item) { unset($item['args']); return $item; }, $exception->getTrace()),
65+
];
66+
$exception = $exception->getPrevious();
67+
}
68+
$hash = substr(md5(serialize($data)), 0, 10);
69+
$dir = strtr($this->directory . '/', '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR);
70+
foreach (new \DirectoryIterator($this->directory) as $file) {
71+
if (strpos($file->getBasename(), $hash)) {
72+
return $dir . $file;
73+
}
74+
}
75+
return $dir . 'exception--' . @date('Y-m-d--H-i') . "--$hash.html"; // @ timezone may not be set
76+
}
77+
78+
79+
/**
80+
* Logs exception to the file if file doesn't exist.
81+
* @param \Exception|\Throwable
82+
* @return string logged error filename
83+
*/
84+
protected function logException($exception, $file = null)
85+
{
86+
$file = $file ?: $this->getExceptionFile($exception);
87+
$bs = $this->blueScreen ?: new BlueScreen;
88+
$bs->renderToFile($exception, $file);
89+
return $file;
90+
}
91+
}

src/Tracy/Debugger.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,12 @@ final public function __construct()
138138

139139
/**
140140
* Enables displaying or logging errors and exceptions.
141-
* @param mixed production, development mode, autodetection or IP address(es) whitelist.
142-
* @param string error log directory
143-
* @param string administrator email; enables email sending in production mode
141+
* @param mixed production, development mode, autodetection or IP address(es) whitelist.
142+
* @param string|ILogger error log directory or logger instance
143+
* @param string administrator email; enables email sending in production mode
144144
* @return void
145145
*/
146-
public static function enable($mode = null, $logDirectory = null, $email = null)
146+
public static function enable($mode = null, $logDirectoryOrLogger = null, $email = null)
147147
{
148148
if ($mode !== null || self::$productionMode === null) {
149149
self::$productionMode = is_bool($mode) ? $mode : !self::detectDebugMode($mode);
@@ -159,8 +159,10 @@ public static function enable($mode = null, $logDirectory = null, $email = null)
159159
if ($email !== null) {
160160
self::$email = $email;
161161
}
162-
if ($logDirectory !== null) {
163-
self::$logDirectory = $logDirectory;
162+
if (is_string($logDirectoryOrLogger)) {
163+
self::$logDirectory = $logDirectoryOrLogger;
164+
} elseif ($logDirectoryOrLogger instanceof ILogger) {
165+
self::$logger = $logDirectoryOrLogger;
164166
}
165167
if (self::$logDirectory) {
166168
if (!preg_match('#([a-z]+:)?[/\\\\]#Ai', self::$logDirectory)) {
@@ -498,8 +500,8 @@ public static function getLogger()
498500
{
499501
if (!self::$logger) {
500502
self::$logger = new Logger(self::$logDirectory, self::$email, self::getBlueScreen());
501-
self::$logger->directory = &self::$logDirectory; // back compatiblity
502-
self::$logger->email = &self::$email;
503+
self::$logDirectory = &self::$logger->directory; // back compatiblity
504+
self::$email = &self::$logger->email;
503505
}
504506
return self::$logger;
505507
}

src/Tracy/Helpers.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,29 @@ public static function getNonce()
246246
? $m[1]
247247
: null;
248248
}
249+
250+
251+
/**
252+
* @param string|\Exception|\Throwable
253+
* @return string
254+
* @internal
255+
*/
256+
public static function formatMessage($message)
257+
{
258+
if ($message instanceof \Exception || $message instanceof \Throwable) {
259+
while ($message) {
260+
$tmp[] = ($message instanceof \ErrorException
261+
? Helpers::errorTypeToString($message->getSeverity()) . ': ' . $message->getMessage()
262+
: Helpers::getClass($message) . ': ' . $message->getMessage() . ($message->getCode() ? ' #' . $message->getCode() : '')
263+
) . ' in ' . $message->getFile() . ':' . $message->getLine();
264+
$message = $message->getPrevious();
265+
}
266+
$message = implode("\ncaused by ", $tmp);
267+
268+
} elseif (!is_string($message)) {
269+
$message = Dumper::toText($message);
270+
}
271+
272+
return trim($message);
273+
}
249274
}

src/Tracy/ILogger.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,11 @@ interface ILogger
2121
EXCEPTION = 'exception',
2222
CRITICAL = 'critical';
2323

24+
/**
25+
* Logs message or exception to file and sends email notification.
26+
* @param string|\Exception|\Throwable
27+
* @param int one of constant ILogger::INFO, WARNING, ERROR, EXCEPTION, CRITICAL
28+
* @return mixed
29+
*/
2430
function log($value, $priority = self::INFO);
2531
}

src/Tracy/Logger.php

Lines changed: 57 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,25 @@ class Logger implements ILogger
3131
/** @var BlueScreen */
3232
private $blueScreen;
3333

34+
/** @var StreamLogger[] */
35+
private $streamLoggers;
36+
37+
/** @var BlueScreenLogger */
38+
private $blueScreenLogger;
39+
40+
/** @var MailLogger */
41+
private $mailLogger;
42+
3443

3544
public function __construct($directory, $email = null, BlueScreen $blueScreen = null)
3645
{
3746
$this->directory = $directory;
3847
$this->email = $email;
3948
$this->blueScreen = $blueScreen;
4049
$this->mailer = [$this, 'defaultMailer'];
50+
51+
$this->blueScreenLogger = $this->createBlueScreenLogger();
52+
$this->mailLogger = $this->createMailLogger();
4153
}
4254

4355

@@ -48,32 +60,49 @@ public function __construct($directory, $email = null, BlueScreen $blueScreen =
4860
* @return string logged error filename
4961
*/
5062
public function log($message, $priority = self::INFO)
63+
{
64+
$exceptionFile = $this->getStreamLogger($priority)->log($message, $priority);
65+
$this->mailLogger->log($message, $priority);
66+
return $exceptionFile;
67+
}
68+
69+
70+
protected function getStreamLogger($priority)
5171
{
5272
if (!$this->directory) {
5373
throw new \LogicException('Logging directory is not specified.');
5474
} elseif (!is_dir($this->directory)) {
5575
throw new \RuntimeException("Logging directory '$this->directory' is not found or is not directory.");
5676
}
5777

58-
$exceptionFile = $message instanceof \Exception || $message instanceof \Throwable
59-
? $this->getExceptionFile($message)
60-
: null;
61-
$line = $this->formatLogLine($message, $exceptionFile);
62-
$file = $this->directory . '/' . strtolower($priority ?: self::INFO) . '.log';
63-
64-
if (!@file_put_contents($file, $line . PHP_EOL, FILE_APPEND | LOCK_EX)) { // @ is escalated to exception
65-
throw new \RuntimeException("Unable to write to log file '$file'. Is directory writable?");
78+
$path = $this->directory . '/' . strtolower($priority ?: self::INFO) . '.log';
79+
if (!isset($this->streamLoggers[$path])) {
80+
$this->streamLoggers[$path] = new StreamLogger($path, $this->blueScreenLogger);
6681
}
6782

68-
if ($exceptionFile) {
69-
$this->logException($message, $exceptionFile);
70-
}
83+
return $this->streamLoggers[$path];
84+
}
7185

72-
if (in_array($priority, [self::ERROR, self::EXCEPTION, self::CRITICAL], true)) {
73-
$this->sendEmail($message);
74-
}
7586

76-
return $exceptionFile;
87+
protected function createBlueScreenLogger()
88+
{
89+
$blueScreenLogger = new BlueScreenLogger($this->directory, $this->blueScreen);
90+
$blueScreenLogger->directory = &$this->directory;
91+
92+
return $blueScreenLogger;
93+
}
94+
95+
96+
protected function createMailLogger()
97+
{
98+
$mailLogger = new MailLogger($this->directory, $this->email);
99+
$mailLogger->directory = &$this->directory;
100+
$mailLogger->email = &$this->email;
101+
$mailLogger->fromEmail = &$this->fromEmail;
102+
$mailLogger->emailSnooze = &$this->emailSnooze;
103+
$mailLogger->mailer = &$this->mailer;
104+
105+
return $mailLogger;
77106
}
78107

79108

@@ -83,21 +112,7 @@ public function log($message, $priority = self::INFO)
83112
*/
84113
protected function formatMessage($message)
85114
{
86-
if ($message instanceof \Exception || $message instanceof \Throwable) {
87-
while ($message) {
88-
$tmp[] = ($message instanceof \ErrorException
89-
? Helpers::errorTypeToString($message->getSeverity()) . ': ' . $message->getMessage()
90-
: Helpers::getClass($message) . ': ' . $message->getMessage() . ($message->getCode() ? ' #' . $message->getCode() : '')
91-
) . ' in ' . $message->getFile() . ':' . $message->getLine();
92-
$message = $message->getPrevious();
93-
}
94-
$message = implode("\ncaused by ", $tmp);
95-
96-
} elseif (!is_string($message)) {
97-
$message = Dumper::toText($message);
98-
}
99-
100-
return trim($message);
115+
return Helpers::formatMessage($message);
101116
}
102117

103118

@@ -117,87 +132,42 @@ protected function formatLogLine($message, $exceptionFile = null)
117132

118133

119134
/**
120-
* @param \Exception|\Throwable
121-
* @return string
135+
* @deprecated
122136
*/
123137
public function getExceptionFile($exception)
124138
{
125-
while ($exception) {
126-
$data[] = [
127-
get_class($exception), $exception->getMessage(), $exception->getCode(), $exception->getFile(), $exception->getLine(),
128-
array_map(function ($item) { unset($item['args']); return $item; }, $exception->getTrace()),
129-
];
130-
$exception = $exception->getPrevious();
131-
}
132-
$hash = substr(md5(serialize($data)), 0, 10);
133-
$dir = strtr($this->directory . '/', '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR);
134-
foreach (new \DirectoryIterator($this->directory) as $file) {
135-
if (strpos($file->getBasename(), $hash)) {
136-
return $dir . $file;
137-
}
138-
}
139-
return $dir . 'exception--' . @date('Y-m-d--H-i') . "--$hash.html"; // @ timezone may not be set
139+
return $this->blueScreenLogger->getExceptionFile($exception);
140140
}
141141

142142

143143
/**
144-
* Logs exception to the file if file doesn't exist.
145-
* @param \Exception|\Throwable
146-
* @return string logged error filename
144+
* @deprecated
147145
*/
148146
protected function logException($exception, $file = null)
149147
{
150-
$file = $file ?: $this->getExceptionFile($exception);
151-
$bs = $this->blueScreen ?: new BlueScreen;
152-
$bs->renderToFile($exception, $file);
153-
return $file;
148+
$reflection = new \ReflectionMethod($this->blueScreenLogger, 'logException');
149+
$reflection->setAccessible(true);
150+
return $reflection->invoke($this->blueScreenLogger, $exception, $file);
154151
}
155152

156153

157154
/**
158-
* @param string|\Exception|\Throwable
159-
* @return void
155+
* @deprecated
160156
*/
161157
protected function sendEmail($message)
162158
{
163-
$snooze = is_numeric($this->emailSnooze)
164-
? $this->emailSnooze
165-
: @strtotime($this->emailSnooze) - time(); // @ timezone may not be set
166-
167-
if ($this->email && $this->mailer
168-
&& @filemtime($this->directory . '/email-sent') + $snooze < time() // @ file may not exist
169-
&& @file_put_contents($this->directory . '/email-sent', 'sent') // @ file may not be writable
170-
) {
171-
call_user_func($this->mailer, $message, implode(', ', (array) $this->email));
172-
}
159+
$reflection = new \ReflectionMethod($this->mailLogger, 'sendEmail');
160+
$reflection->setAccessible(true);
161+
return $reflection->invoke($this->mailLogger, $message);
173162
}
174163

175164

176165
/**
177-
* Default mailer.
178-
* @param string|\Exception|\Throwable
179-
* @param string
180-
* @return void
166+
* @deprecated
181167
* @internal
182168
*/
183169
public function defaultMailer($message, $email)
184170
{
185-
$host = preg_replace('#[^\w.-]+#', '', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n'));
186-
$parts = str_replace(
187-
["\r\n", "\n"],
188-
["\n", PHP_EOL],
189-
[
190-
'headers' => implode("\n", [
191-
'From: ' . ($this->fromEmail ?: "noreply@$host"),
192-
'X-Mailer: Tracy',
193-
'Content-Type: text/plain; charset=UTF-8',
194-
'Content-Transfer-Encoding: 8bit',
195-
]) . "\n",
196-
'subject' => "PHP: An error occurred on the server $host",
197-
'body' => $this->formatMessage($message) . "\n\nsource: " . Helpers::getSource(),
198-
]
199-
);
200-
201-
mail($email, $parts['subject'], $parts['body'], $parts['headers']);
171+
return $this->mailLogger->defaultMailer($message, $email);
202172
}
203173
}

0 commit comments

Comments
 (0)