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