1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13: class Log
14: {
15: const
16: DEVELOPMENT = FALSE,
17: PRODUCTION = TRUE;
18:
19: public static $log_level = 0;
20:
21:
22: public static $templates_dir = './templates';
23:
24:
25: public static $logs_dir = './logs';
26:
27:
28: public static $development_mode = FALSE;
29:
30:
31: public static $email;
32:
33:
34: private $tokens_classes = array(
35: 'php-keyword' => array(
36: T_ARRAY, T_NEW, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_CLASS, T_CLONE,
37: T_EXTENDS, T_VAR, T_STATIC, T_FUNCTION, T_AS, T_BREAK, T_CASE,
38: T_CATCH, T_CONTINUE, T_DECLARE, T_DEFAULT, T_ECHO, T_ELSE, T_ELSEIF,
39: T_EMPTY, T_EVAL, T_EXIT, T_EXTENDS, T_ENDDECLARE, T_ENDFOR,
40: T_ENDFOREACH, T_ENDIF, T_ENDSWITCH, T_ENDWHILE, T_END_HEREDOC,
41: T_FINAL, T_FOR, T_FOREACH, T_GLOBAL, T_GOTO, T_IF, T_IMPLEMENTS,
42: T_INCLUDE, T_INCLUDE_ONCE, T_INSTANCEOF, T_INTERFACE, T_ISSET,
43: T_LIST, T_NAMESPACE, T_PRINT, T_REQUIRE, T_REQUIRE_ONCE, T_RETURN,
44: T_SWITCH, T_THROW, T_TRY, T_UNSET, T_USE, T_VAR),
45: 'php-name' => array(T_STRING),
46: 'php-variable' => array(T_VARIABLE),
47: 'php-comment' => array(T_COMMENT),
48: 'php-strings' => array(T_CONSTANT_ENCAPSED_STRING),
49: );
50:
51:
52: private $titles = array(
53: 0 => 'Unknown Error',
54: E_ERROR => 'Fatal Error',
55: E_WARNING => 'Warning',
56: E_COMPILE_WARNING => 'Warning',
57: E_COMPILE_WARNING => 'Compile Error',
58: E_USER_WARNING => 'Warning',
59: E_NOTICE => 'Notice',
60: E_USER_NOTICE => 'Notice',
61: E_STRICT => 'Strict standards',
62: E_DEPRECATED => 'Deprecated',
63: E_USER_DEPRECATED => 'Deprecated',
64: E_USER_ERROR => 'Fatal Error',
65: );
66:
67:
68: public static function enable($mode = NULL)
69: {
70: $logger = new self;
71:
72: if ($mode === NULL) {
73: $mode = '';
74: }
75:
76: if (is_string($mode) === TRUE
77: AND isset($_SERVER['REMOTE_ADDR']) === TRUE) {
78:
79: $whitelist = explode(',', $mode . '127.0.0.1,::1');
80: $mode = in_array($_SERVER['REMOTE_ADDR'], $whitelist);
81: }
82:
83: $logger->development_mode = $mode;
84:
85: set_error_handler(array($logger, 'handle_error'));
86: set_exception_handler(array($logger, 'handle_exception'));
87: register_shutdown_function(array($logger, 'handle_fatal_error'));
88: }
89:
90:
91: 92: 93: 94: 95:
96: public function handle_error($errno, $errstr, $errfile, $errline, $errcontext)
97: {
98: if ($errno === E_DEPRECATED) {
99: return NULL;
100: }
101:
102: $backtrace = debug_backtrace();
103:
104: $this->log_error($errno, $errstr, $errfile, $errline, $backtrace, $errcontext);
105: }
106:
107:
108: 109: 110:
111: public function handle_exception($e)
112: {
113: $backtrace = $e->getTrace();
114: array_shift($backtrace);
115: $this->log_error($e->getCode(), $e->getMessage(), $e->getFile(), $e->getLine(), $backtrace);
116: }
117:
118:
119: 120: 121: 122: 123:
124: public function handle_fatal_error()
125: {
126: $last_error = error_get_last();
127: if ($last_error['type'] === E_ERROR
128: OR $last_error['type'] === E_PARSE) {
129:
130: $this->log_error(E_ERROR, $last_error['message'], $last_error['file'], $last_error['line'], array());
131: }
132: }
133:
134:
135: public function hilight_file($file, $hilight_line = NULL, $range = NULL)
136: {
137: $source = file_get_contents($file);
138: $source = preg_split('/(\n|\r)/', trim($source));
139:
140: if ($hilight_line !== NULL
141: AND $range !== NULL) {
142:
143: $offset = floor($hilight_line - ($range / 2));
144: if ($offset < 1) {
145: $offset = 1;
146: }
147:
148: $source = array_slice($source, $offset, $range);
149:
150: } elseif ($range === NULL) {
151: $offset = 1;
152: }
153:
154: return $this->hilight_source($source, $hilight_line, $offset);
155: }
156:
157:
158: public function hilight_source($source, $hilight_line = NULL, $offset)
159: {
160: if (is_array($source) === FALSE) {
161: $source = preg_split('/(\n|\r)/', trim($source));
162: }
163:
164: $hilighted_source = Html::element();
165:
166: foreach ($source as $relative_line_number => $line) {
167: $relative_line_number++;
168:
169: $line_number = $relative_line_number + $offset;
170:
171: $hilighted_line = $hilighted_source->create('span');
172: $hilighted_line->class[] = 'source-line';
173:
174: if ($hilight_line == $line_number) {
175: $hilighted_line->class[] = 'hilight-line';
176: }
177:
178: if ($relative_line_number % 2 === 0) {
179: $hilighted_line->class[] = 'even-line';
180:
181: } else {
182: $hilighted_line->class[] = 'odd-line';
183: }
184:
185: $hilighted_line->create('span')
186: ->class('line-number')
187: ->set_text(sprintf('%5s ', $line_number));
188:
189: $tmp = trim($line);
190: if (empty($tmp) === TRUE) {
191: $hilighted_line->add(chr(10));
192: continue;
193: }
194:
195: if (in_array($tmp, array('*', '/*', '*/')) === TRUE
196: OR substr($tmp, 0, 1) === '*') {
197:
198: $hilighted_line->create('span')
199: ->class('php-comment')
200: ->set_text($line);
201: continue;
202: }
203:
204: if (substr($line, 0, 2) !== '<?') {
205: $line = '<?php ' . $line;
206: }
207:
208: $tokens = @token_get_all($line);
209:
210: foreach ($tokens as $key => $token) {
211: if (is_array($token) === FALSE) {
212: $hilighted_line->add($token);
213: continue;
214: }
215:
216: if ($token[0] === T_OPEN_TAG
217: AND $key === 0) {
218:
219: continue;
220: }
221:
222: $found = FALSE;
223: foreach ($this->tokens_classes as $class => $token_codes) {
224: if (in_array($token[0], $token_codes) === TRUE) {
225: $found = TRUE;
226:
227: $hilighted_line->create('span')
228: ->set_text($token[1])
229: ->class($class);
230: }
231: }
232:
233: if ($found === FALSE) {
234: $hilighted_line->add($token[1]);
235: }
236: }
237:
238: $hilighted_line->add(chr(10));
239: }
240:
241: return $hilighted_source;
242: }
243:
244: private function log_error($errno, $errstr, $errfile, $errline, $backtrace, $errcontext = array())
245: {
246: header('HTTP/1.1 500 Internal Server Error');
247:
248: $log = $this->create_log($errno, $errstr, $errfile, $errline, $backtrace);
249: if ($this->development_mode === TRUE) {
250: die($log);
251: }
252:
253: file_put_contents(self::$logs_dir . '/errors/' . date('Y-m-d-H-i-s', time()) . '.html', $log);
254:
255: if (empty(self::$email) === FALSE) {
256: foreach ((array) self::$email as $address) {
257: $this->send_log($address, 'Error', $log);
258: }
259: }
260:
261: readfile(self::$templates_dir . '/Error.html');
262: exit;
263: }
264:
265:
266: public function create_log($errno, $errstr, $errfile, $errline, $backtrace, $errcontext = array())
267: {
268: ob_clean();
269: restore_error_handler();
270: restore_exception_handler();
271:
272: $template = self::$templates_dir . '/DevError.php';
273:
274: $data['code'] = $this->hilight_file($errfile, $errline, 30);
275: $data['title'] = $this->titles[$errno];
276: $data['error_message'] = $errstr;
277: $data['file'] = $errfile;
278: $data['context'] = $errcontext;
279: $data['current_uri'] = $this->current_uri();
280: if (isset($_SERVER['REQUEST_METHOD']) === TRUE) {
281: $data['method'] = $_SERVER['REQUEST_METHOD'];
282:
283: } else {
284: $data['method'] = '???';
285: }
286:
287: $css = '<style type="text/css">' . chr(10);
288: $css .= file_get_contents(self::$templates_dir . '/css/reset.css') . chr(10);
289: $css .= file_get_contents(self::$templates_dir . '/css/dev-error.css') . chr(10);
290: $css .= '</style>' . chr(10) . chr(10);
291:
292: $js = '<script type="text/javascript">' . chr(10);
293: $js .= file_get_contents(self::$templates_dir . '/js/jquery.min.js') . chr(10);
294: $js .= file_get_contents(self::$templates_dir . '/js/logger.js') . chr(10);
295: $js .= '</script>' . chr(10) . chr(10);
296:
297: $data['assets'] = $css . $js;
298:
299: $backtrace_tmp = $backtrace;;
300: $backtrace = array();
301: foreach($backtrace_tmp as $step_tmp) {
302: $step = array();
303:
304: if (isset($step_tmp['file']) === FALSE) {
305: continue;
306: }
307:
308: $step['file'] = $step_tmp['file'];
309:
310: $function = '';
311: if (isset($step_tmp['class']) === TRUE) {
312: if ($step_tmp['class'] === 'Log') {
313: continue;
314: }
315:
316: $function = $step_tmp['class'];
317: $function .= $step_tmp['type'];
318: }
319: $function .= $step_tmp['function'];
320: $step['function'] = $function;
321:
322: if (isset($step_tmp['args']) === FALSE) {
323: $step_tmp['args'] = array();
324: }
325:
326: $step['args'] = print_r(self::convert_bool($step_tmp['args']), TRUE);
327: $step['line'] = $step_tmp['line'];
328:
329: $step['source'] = $this->hilight_file($step_tmp['file'], $step_tmp['line'], 16);
330:
331: $backtrace[] = $step;
332: }
333: $data['backtrace'] = $backtrace;
334:
335: return $this->render($template, $data);
336: }
337:
338:
339: public function render($template, $data) {
340: extract($data);
341: ob_start();
342: include($template);
343: $contents = ob_get_contents();
344: ob_end_clean();
345: return $contents;
346: }
347:
348:
349: public static function info()
350: {
351: $data = func_get_args();
352:
353: foreach ($data as $line) {
354: self::write_log_line('Info', $line);
355: }
356: }
357:
358:
359: public static function warning()
360: {
361: $data = func_get_args();
362:
363: foreach ($data as $line) {
364: self::write_log_line('Warnnig', $line);
365: }
366: }
367:
368:
369: public static function error($message, $data = '')
370: {
371: self::write_log_line('Error', $message, $data);
372: }
373:
374:
375: public static function debug($message, $data = '')
376: {
377: self::write_log_line('Debug', $message, $data);
378: }
379:
380:
381: public static function write_log_line($level, $message, $data = '')
382: {
383: $datetime = time();
384:
385: if (is_object($data) === TRUE) {
386: ob_start();
387: var_dump($data);
388: $data = ob_get_clean();
389:
390: } elseif (is_array($data) === TRUE) {
391: $data = self::convert_bool($data);
392: $data = print_r($data, TRUE);
393: }
394:
395: $backtrace = debug_backtrace();
396: if (isset($backtrace[2]['class']) === TRUE) {
397: $from = $backtrace[2]['class'] . '::' . $backtrace[2]['function'];
398:
399: } else {
400: $from = $backtrace[2]['function'];
401: }
402:
403: $line = '"';
404: $line .= $level;
405: $line .= '","';
406: $line .= $datetime;
407: $line .= '","';
408: $line .= $from;
409: $line .= '","';
410: $line .= base64_encode($message);
411: $line .= '","';
412: $line .= base64_encode($data);
413: $line .= '"';
414: $line .= chr(10);
415:
416: $log_file = self::$logs_dir . '/' . $_SERVER['REQUEST_TIME'];
417:
418: $handle = file_put_contents($log_file, $line, FILE_APPEND | LOCK_EX);
419: }
420:
421:
422: public static function convert_bool($data) {
423: foreach ($data as &$value) {
424: if (is_array($value) === TRUE) {
425: $value = self::convert_bool($value);
426: continue;
427: }
428:
429: if ($value === TRUE) {
430: $value = 'TRUE';
431: continue;
432: }
433:
434: if ($value === FALSE) {
435: $value = 'FALSE';
436: continue;
437: }
438:
439: if ($value === NULL) {
440: $value = 'NULL';
441: continue;
442: }
443: }
444:
445: return $data;
446: }
447:
448:
449: private static function send_log($address, $message, $log)
450: {
451: $host = '?!';
452: if (isset($_SERVER['HTTP_HOST']) === TRUE) {
453: $host = $_SERVER['HTTP_HOST'];
454:
455: } elseif (isset($_SERVER['SERVER_NAME']) === TRUE) {
456: $host = $_SERVER['HTTP_HOST'];
457: }
458:
459: $attachment = chunk_split(base64_encode($log));
460:
461: $mail_message = '--PHP-mixed-onionlogger' . chr(10);
462: $mail_message .= 'Content-Type: multipart/alternative; boundary="PHP-alt-onionlogger"' . chr(10) . chr(10);
463:
464: $mail_message .= '--PHP-alt-onionlogger' . chr(10);
465: $mail_message .= 'Content-type: text/plain; charset=UTF-8' . chr(10) . chr(10);
466:
467: $mail_message .= trim($message) . chr(10) . chr(10);
468:
469: $mail_message .= '--PHP-alt-onionlogger' . chr(10);
470: $mail_message .= 'Content-type: text/html; charset=UTF-8' . chr(10) . chr(10);
471:
472: $mail_message .= trim($log) . chr(10) . chr(10);
473:
474: $mail_message .= '--PHP-alt-onionlogger--' . chr(10) . chr(10);
475:
476: $mail_message .= '--PHP-mixed-onionlogger' . chr(10);
477: $mail_message .= 'Content-Type: text/html; name="log.html"' . chr(10);
478: $mail_message .= 'Content-Transfer-Encoding: base64' . chr(10);
479: $mail_message .= 'Content-Disposition: attachment' . chr(10) . chr(10);
480:
481: $mail_message .= $attachment . chr(10) . chr(10);
482: $mail_message .= '--PHP-mixed-onionlogger--' . chr(10) . chr(10);
483:
484: $headers = array();
485: $headers[] = 'From: noreply@' . $host;
486: $headers[] = 'X-Mailer: Onion Framework';
487: $headers[] = 'Content-Type: multipart/mixed; boundary="PHP-mixed-onionlogger"';
488: $headers[] = 'MIME-Version: 1.0';
489:
490: $subject = 'PHP: An error occurred on the server ' . $host;
491:
492: mail($address, '=?UTF-8?B?'.base64_encode($subject).'?=', $mail_message, implode(chr(10) ,$headers));
493: }
494:
495:
496: private function current_uri()
497: {
498: if (empty($_SERVER['HTTPS']) === TRUE
499: OR $_SERVER['HTTPS'] === 'off') {
500:
501: $uri = 'http';
502:
503: } else {
504: $uri = 'https';
505: }
506:
507: $uri .= '://';
508:
509: if (isset($_SERVER['HTTP_HOST']) === TRUE) {
510: $uri .= $_SERVER['HTTP_HOST'];
511:
512: } else {
513: $uri .= $_SERVER['SERVER_NAME'];
514: }
515:
516: if (empty($_SERVER['REQUEST_URI']) === FALSE) {
517: $uri .= $_SERVER['REQUEST_URI'];
518: }
519:
520: return $uri;
521: }
522: }
523:
524:
525:
526: 527: 528: 529: 530: 531: 532: 533: 534:
535:
536: class LogController extends Controller
537: {
538: 539: 540:
541: public function show_log()
542: {
543: $logs = scandir(Log::$logs_dir, 1);
544: $log_file = array_shift($logs);
545: $log_file = array_shift($logs);
546: if ($_SERVER['REQUEST_TIME'] == $log_file) {
547: unlink(Log::$logs_dir . '/' . $log_file);
548: $log_file = array_shift($logs);
549: }
550:
551: $log_file = Log::$logs_dir . '/' . $log_file;
552:
553: $log = file_get_contents($log_file);
554: $log = explode(chr(10), trim($log));
555: $log = array_reverse($log);
556:
557: $out = '<h2>' . $log_file . '</h2>';
558:
559: $out .= '<table>';
560:
561: $out .= '<tr>';
562: $out .= '<th>Level</th>';
563: $out .= '<th>Time</th>';
564: $out .= '<th>From</th>';
565: $out .= '<th>Message</th>';
566: $out .= '<th>Data</th>';
567: $out .= '</tr>';
568:
569: foreach ($log as $line) {
570: if (empty($line) === TRUE) {
571: continue;
572: }
573:
574: $line = substr($line, 1, -1);
575: $line = explode('","', $line);
576:
577: $out .= '<tr>';
578: $out .= '<td>' . $line[0] . '</td>';
579: $out .= '<td>' . date('Y-m-d H:i:s', $line[1]) . '</td>';
580: $out .= '<td>' . $line[2] . '</td>';
581: $out .= '<td><code>' . base64_decode($line[3]) . '</code></td>';
582: $out .= '<td><pre>' . base64_decode($line[4]) . '</pre></td>';
583: $out .= '</tr>';
584: }
585:
586: $out .= '</table>';
587:
588: die($out);
589: }
590: }
591: