<?php
/**
 * 调试日志操作类
 * DEBUG_LEVEL=0的时候不会在后端运行，
 * DEBUG_LEVEL=1的时候会记录错误、警告信息以及资源调用的耗时汇总统计，
 * DEBUG_LEVEL=2的时候，会记录全部的数据
 * 如果在参数列表中出现 __DEBUG_LEVEL ，则会强制覆盖 DEBUG_LEVEL 的值
 * 功能列表如下：
 * 1 time 性能探针，计算运行的步骤以及每一步的执行效率
 * 2 log 日志记录，把每一个日志信息记录下来
 * 3 http 接口调用的记录以及耗时的汇总统计
 * 4 redis redis调用的记录以及耗时的汇总统计
 * 5 mysql mysql调用的记录以及耗时的汇总统计
 * 6 cache memcache调用的记录以及耗时的汇总统计
 */
namespace Helpers;

define('DEBUG_LOG_ERROR', 'ERROR');
define('DEBUG_LOG_WARNING', 'WARNING');
define('DEBUG_LOG_INFO', 'INFO');

class DebugLog {

    private $logId;
    private $timeList;
    private $logList;
    private $httpList;
    private $redisList;
    private $mysqlList;
	private $cacheList;

    private static $instance = false;
    private function __construct() {}

    /**
     * 初始化调试日志操作类，没有经过初始化的后续调试代码都不会生效
     */
    public static function init() {
        if (!self::$instance) {
            self::$instance = new DebugLog();
            self::$instance->logId = microtime();
        }
    }

    /**
     * 记录时间，方便调试程序执行逻辑和每一步的执行效率
     */
    public static function time($label, $handler = false) {
        if (self::$instance === false) {
            return;
        }
        self::$instance->timeList[] = array($label, microtime(), $handler);
    }

    /**
     * 记录运行时的调试信息，分为 DEBUG_LOG_INFO 和 DEBUG_LOG_ERROR，DEBUG_LOG_INFO 只有在全量输出调试信息的时候才会输出
     */
    public static function log($label, $info, $level=DEBUG_LOG_INFO, $handler = false) {
        if (self::$instance === false || (DEBUG_LEVEL < 2 && $level == DEBUG_LOG_INFO)) {
            return;
        }
        self::$instance->logList[] = array($label, $info, $level, $handler);
    }

    /**
     * 记录运行时的http请求
     */
    public static function http($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false) {
        if (self::$instance === false) {
            return;
        }
        if (DEBUG_LEVEL === 1) {
            self::$instance->httpList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler);
        } else {
            self::$instance->httpList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler);
        }
    }

    /**
     * 记录运行时的redis请求
     */
    public static function redis($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false) {
        if (self::$instance === false) {
            return;
        }
        if (DEBUG_LEVEL === 1) {
            if ('setex' == $label) {    // 过滤掉内容块，避免日志太多
                $params[2] = null;
            }
            self::$instance->redisList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler);
        } else {
            self::$instance->redisList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler);
        }
    }

    /**
     * 记录运行时的mysql请求
     */
    public static function mysql($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false) {
        if (self::$instance === false) {
            return;
        }
        if (DEBUG_LEVEL === 1) {
            self::$instance->mysqlList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler);
        } else {
            self::$instance->mysqlList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler);
        }
    }

	/**
     * 记录运行时的memcache请求
     */
    public static function cache($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false) {
        if (self::$instance === false) {
            return;
        }
        if (DEBUG_LEVEL === 1) {
            self::$instance->cacheList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler);
        } else {
            self::$instance->cacheList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler);
        }
    }

    /**
     * 输出日志
     */
    public static function show() {
        if (self::$instance === false) {
            return;
        }
        if (isset($_SERVER['HTTP_USER_AGENT'])) {
            // 界面上可视化模式输出内容
            self::$instance->showViews();
        } else {
            self::$instance->writeLogs();
        }
    }

    /**
     * 是否有可视化界面输出，HTML代码直接返回到浏览器
     */
    public static function is_show_view() {
        if (self::$instance && isset($_SERVER['HTTP_USER_AGENT'])) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 将microtime的时间字符串转换为float型的毫秒时间
     */
    private function floatMicrotime($mt) {
        if (strpos($mt, ' ')) {
            list($ms, $m) = explode(' ', $mt);
            return ($m + $ms) * 1000;
        } else {
            return floatval($mt) * 1000;
        }
    }

    /**
     * 计算两个microtime时间的间隔时间
     * @param $m1 开始时间
     * @param $m2 结束时间
     * @param $round 保留小数位
     */
    private function intervalTime($m1, $m2, $round = 3) {
        return round(($this->floatMicrotime($m2) - $this->floatMicrotime($m1)), $round);
    }

    /**
     * 将调试信息生成可视化的HTML代码
     */
    private function showViews() {
        $showTime = microtime();
        $output = array();
        $output[] = "\n";
        $output[] = '<ul>';
        $output[] = '<li><strong style="font-size:18px;">DebugLog showViews.total process time is ' . $this->intervalTime($this->logId, $showTime) . 'ms</strong></li>';
        if ($this->timeList) {
            $total_num = count($this->timeList);
            $output[] = '<li><strong style="font-size:18px;">TimeList total count is ' . count($this->timeList) . ', log time is ' . $this->intervalTime($this->logId, $this->timeList[$total_num - 1][1]) . '</strong></li>';
            $lasttime = $this->logId;
            $output[] = '<li>0.000 : start debug log ' . $lasttime . '</li>';
            foreach ($this->timeList as $info) {
                $lasttime2 = $info[1];
                $output[] = '<li>'. $this->intervalTime($lasttime, $lasttime2) . ' : ' . implode("\t", $info) . '</li>';
                $lasttime = $lasttime2;
            }
        }
        if ($this->logList) {
            $output[] = '<li><strong style="font-size:18px;">LogList total count is ' . count($this->logList) . '</strong></li>';
            foreach ($this->logList as $info) {
                $output[] = '<li>' . implode("\t", $info) . '</li>';
            }
        }
        if ($this->httpList) {
            $current = count($output);
            $total_time = 0;
            $output[] = null;
            $max_num = array();
            $multi_num = array();
            foreach ($this->httpList as $info) {
                $intval = $this->intervalTime($info[3], $info[4]);
                $multi_flag = @json_decode($info[2],true);
                if(isset($multi_flag) && isset($multi_flag['is_multi']) && $multi_flag['is_multi']==1)
                {
                    $multi_str = strval($multi_flag['multi_num']);

                    if($intval > $max_num[$multi_str])
                    {
                        $max_num[$multi_str] = $intval;

                        if(!in_array($multi_str, $multi_num))
                        {
                            $multi_num[] = $multi_str;
                        }
                    }
                }
                else
                {
                    $total_time += $intval;
                }
                if ($info[5] && is_array($info[5])) {
                    $info[5] = json_encode($info[5]);
                }

                $output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>';
            }

            if(!empty($multi_num ))
            {
                foreach($multi_num as $val)
                {
                    $total_time += $max_num[$val];
                }
            }

            $output[$current] = '<li><strong style="font-size:18px;">HttpList total count is ' . count($this->httpList) . ', total time is ' . $total_time . '</strong></li>';

        }
        if ($this->redisList) {
            $current = count($output);
            $total_time = 0;
            $output[] = null;
            foreach ($this->redisList as $info) {
                $intval = $this->intervalTime($info[3], $info[4]);
                $total_time += $intval;
                if ($info[5] && is_array($info[5])) {
                    $info[5] = json_encode($info[5]);
                }
                $output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>';
            }
            $output[$current] = '<li><strong style="font-size:18px;">RedisList total count is ' . count($this->redisList) . ', total time is ' . $total_time . '</strong></li>';
        }
        if ($this->mysqlList) {
            $current = count($output);
            $total_time = 0;
            $output[] = null;
            foreach ($this->mysqlList as $info) {
                $intval = $this->intervalTime($info[3], $info[4]);
                $total_time += $intval;
                if ($info[5] && is_array($info[5])) {
                    $info[5] = json_encode($info[5]);
                } elseif (!$info[5]) {
                    $info[5] = '';
                }
                $output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>';
            }
            $output[$current] = '<li><strong style="font-size:18px;">MysqlList total count is ' . count($this->mysqlList) . ', total time is ' . $total_time . '</strong></li>';
        }
        if ($this->cacheList) {
            $current = count($output);
            $total_time = 0;
            $output[] = null;
            foreach ($this->cacheList as $info) {
                $intval = $this->intervalTime($info[3], $info[4]);
                $total_time += $intval;
                if ($info[5] && is_array($info[5])) {
                    $info[5] = json_encode($info[5]);
                }
                $output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>';
            }
            $output[$current] = '<li><strong style="font-size:18px;">CacheList total count is ' . count($this->cacheList) . ', total time is ' . $total_time . '</strong></li>';
        }
        $output[] =  '</ul>';
        echo implode("\n", $output);
    }

    /**
     * 将调试日志写入到本地文件中，使用JSON格式保存为一行
     */
    public function writeLogs() {
        $showTime = microtime();

         if (!defined('DEBUG_LOG_PATH')) {
            define('DEBUG_LOG_PATH', '/var/log/');
        }

        $serverList = array(
            'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'],
            'REQUEST_URI' => $_SERVER['REQUEST_URI'],
            'REMOTE_ADDR:PORT' => $_SERVER['REMOTE_ADDR'] . ':' . $_SERVER['REMOTE_PORT'],
        );
        $datalist = array(
            'logId'=>$this->logId,
            'logTime'=>$showTime,
            'timeList'=>$this->timeList,
            'logList'=>$this->logList,
            'httpList'=>$this->httpList,
            'redisList'=>$this->redisList,
            'mysqlList'=>$this->mysqlList,
            'server'=>$serverList,
            );
        $str = json_encode($datalist);
        $str = str_replace("\n", ' ', $str);
        $str .= "\n";
        $file_path = DEBUG_LOG_PATH . 'custum_debug.log';
        if($fd = @fopen($file_path, 'a')) {
            fputs($fd, $str);
            fclose($fd);
        }
    }

    /**
     * 将消息输出到指定的文件
     * 默认 define('DEBUG_LOG_PATH', '/home/worker/log/php/today/')
     * @param $msg 消息内容
     * @param string $file 日志文件名称，默认是 custum_debug.log
     */
    public static function writeDebugLog($msg, $file='custum_debug.log') {
        $dtime = date('Y-m-d H:i:s');
        if (!defined('DEBUG_LOG_PATH')) {
            $default_path = '/var/log/';
            if (file_exists($default_path)) {
                define('DEBUG_LOG_PATH', $default_path);
            } else {
                define('DEBUG_LOG_PATH', '');
            }
        }
//        $str_cookie = json_encode($_COOKIE);
		$str_cookie = 'no cookie';
        $str_server = json_encode(array($_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI']));
        $str = "[$dtime]||$msg||$str_cookie||$str_server\n";
        $file_path = DEBUG_LOG_PATH . $file;
        if($fd = @fopen($file_path, 'a')) {
            fputs($fd, $str);
            fclose($fd);
        }
    }

	/**
	 * 通过PHP的 debug_backtrace 可以详细的查看到方法调用的细节情况
	 */
	public static function writeBacktrace($deep=3, $all=false) {
		$result = array();
		$trace = debug_backtrace();
		unset($trace[0]);
		if ($deep < count($trace)) {
			for ($i = 1; $i <= $deep; $i++) {
				$info = $trace[$i];
				if (isset($info['object']) && $all === false) {
					unset($info['object']);
				}
				$result[] = $info;
			}
		} elseif ($all === false) {
			foreach ($trace as $info) {
				if (isset($info['object'])) {
					unset($info['object']);
				}
				$result[] = $info;
			}
		} else {
			$result = $trace;
		}
		self::writeDebugLog(json_encode($result), 'backtrace.log');
	}

}

