1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Tracy;
9:
10:
11: 12: 13:
14: class Logger implements ILogger
15: {
16:
17: public $directory;
18:
19:
20: public $email;
21:
22:
23: public $fromEmail;
24:
25:
26: public $emailSnooze = '2 days';
27:
28:
29: public $mailer;
30:
31:
32: private $blueScreen;
33:
34:
35: public function __construct($directory, $email = NULL, BlueScreen $blueScreen = NULL)
36: {
37: $this->directory = $directory;
38: $this->email = $email;
39: $this->blueScreen = $blueScreen;
40: $this->mailer = array($this, 'defaultMailer');
41: }
42:
43:
44: 45: 46: 47: 48: 49:
50: public function log($message, $priority = self::INFO)
51: {
52: if (!$this->directory) {
53: throw new \LogicException('Directory is not specified.');
54: } elseif (!is_dir($this->directory)) {
55: throw new \RuntimeException("Directory '$this->directory' is not found or is not directory.");
56: }
57:
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)) {
65: throw new \RuntimeException("Unable to write to log file '$file'. Is directory writable?");
66: }
67:
68: if ($exceptionFile) {
69: $this->logException($message, $exceptionFile);
70: }
71:
72: if (in_array($priority, array(self::ERROR, self::EXCEPTION, self::CRITICAL), TRUE)) {
73: $this->sendEmail($message);
74: }
75:
76: return $exceptionFile;
77: }
78:
79:
80: 81: 82: 83:
84: protected function formatMessage($message)
85: {
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()
91: ) . ' in ' . $message->getFile() . ':' . $message->getLine();
92: $message = $message->getPrevious();
93: }
94: $message = implode($tmp, "\ncaused by ");
95:
96: } elseif (!is_string($message)) {
97: $message = Dumper::toText($message);
98: }
99:
100: return trim($message);
101: }
102:
103:
104: 105: 106: 107:
108: protected function formatLogLine($message, $exceptionFile = NULL)
109: {
110: return implode(' ', array(
111: @date('[Y-m-d H-i-s]'),
112: preg_replace('#\s*\r?\n\s*#', ' ', $this->formatMessage($message)),
113: ' @ ' . Helpers::getSource(),
114: $exceptionFile ? ' @@ ' . basename($exceptionFile) : NULL,
115: ));
116: }
117:
118:
119: 120: 121: 122:
123: public function getExceptionFile($exception)
124: {
125: $dir = strtr($this->directory . '/', '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR);
126: $hash = substr(md5(preg_replace('~(Resource id #)\d+~', '$1', $exception)), 0, 10);
127: foreach (new \DirectoryIterator($this->directory) as $file) {
128: if (strpos($file, $hash)) {
129: return $dir . $file;
130: }
131: }
132: return $dir . 'exception--' . @date('Y-m-d--H-i') . "--$hash.html";
133: }
134:
135:
136: 137: 138: 139: 140:
141: protected function logException($exception, $file = NULL)
142: {
143: $file = $file ?: $this->getExceptionFile($exception);
144: if ($handle = @fopen($file, 'x')) {
145: ob_start();
146: ob_start(function ($buffer) use ($handle) { fwrite($handle, $buffer); }, 4096);
147: $bs = $this->blueScreen ?: new BlueScreen;
148: $bs->render($exception);
149: ob_end_flush();
150: ob_end_clean();
151: fclose($handle);
152: }
153: return $file;
154: }
155:
156:
157: 158: 159: 160:
161: protected function sendEmail($message)
162: {
163: $snooze = is_numeric($this->emailSnooze)
164: ? $this->emailSnooze
165: : @strtotime($this->emailSnooze) - time();
166:
167: if ($this->email && $this->mailer
168: && @filemtime($this->directory . '/email-sent') + $snooze < time()
169: && @file_put_contents($this->directory . '/email-sent', 'sent')
170: ) {
171: call_user_func($this->mailer, $message, implode(', ', (array) $this->email));
172: }
173: }
174:
175:
176: 177: 178: 179: 180: 181: 182:
183: public function defaultMailer($message, $email)
184: {
185: $host = preg_replace('#[^\w.-]+#', '', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n'));
186: $parts = str_replace(
187: array("\r\n", "\n"),
188: array("\n", PHP_EOL),
189: array(
190: 'headers' => implode("\n", array(
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']);
202: }
203:
204: }
205: