Last active
January 15, 2019 23:09
-
-
Save davidon/2952e2a2471f0c9ac7f04df74ecebcd5 to your computer and use it in GitHub Desktop.
my good logger class with backtrace
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Debugger; | |
/** | |
* usage example: $log = new MyLogger(); $logger->log($logger::ARGTRACE_NONE . $logger::BACKTRACE_NONE . $logger::SELF_ARGUMENTS_LOW, .......) | |
*/ | |
//for Symfony project, this is put under src/Debugger/ folder | |
//for Lumen and Laravel, this is put under app/Debugger/ folder | |
//create logs directory under PROJECT_HOME | |
define('PROJECT_HOME', dirname(dirname(__DIR__))); | |
class MyLogger { | |
//how to trace arguments of called function (the function where MyLogger->log() is put inside) | |
const ARGTRACE_DEEP = 'S'; | |
const ARGTRACE_LOW = 'T'; | |
const ARGTRACE_NONE = 'N'; | |
//how to log arguments of mylogger() itself | |
const SELF_ARGUMENTS_LOW ='M'; | |
//whether log backtrace path | |
const BACKTRACE_NONE = 'B'; | |
const LOG_DIR = PROJECT_HOME . '/logs/'; | |
const QUERY_REL_PATH = '/PATH/TO/MYSQL/QUERY/IS/CALLED/db_query.php'; | |
const INVALID_SQLS = array('SELECT PUT-INVALID-SQL-INTO-THIS-ARRAY'); | |
//for docker | |
//define('VMROOT', '/PATH/TO/ROOT/ON/VM'); | |
//not static | |
private $nesting_count = 0; | |
/** | |
* variable number parameters; | |
* If the first parameter is one or two characters and contains the following: | |
* | |
* (The following is for staacktrace arguments, that is, the arguments of the called function (the function from which mylogger is called, not mylogger function itself);) | |
* 'N': not to log the stacktrace arguments; | |
* 'S' log stacktrace arguments and log deeply; | |
* 'T' log stacktrace arguments but NOT log deeply, i.e., only log class name of object | |
* | |
* 'M: for parameters of mylogger function: only log class name of object; | |
* | |
* If last parameter starts with "filename:", then use the string after it as the filename | |
*/ | |
function log() { | |
set_error_handler(array($this, 'logger_error_handler')); | |
if (array_key_exists('MYLOGGER', $GLOBALS) && $GLOBALS['MYLOGGER'] === false) { | |
trigger_error('MyLogger->log() is disabled', E_USER_WARNING); | |
} | |
$backtrace = true; | |
date_default_timezone_set('Australia/Melbourne'); | |
$self_args_msg = ''; | |
$trace_args = false; //Don't log stacktrace arguments by default | |
$trace_args_deep = false;//for stack trace arguments, only log instance class name by default | |
$self_args_deep = true; //for arguments of this function itself, log recursively of instance by default | |
$logger_filename = ''; | |
$args_num = func_num_args(); | |
if ($args_num >= 1){ | |
$args = func_get_args(); | |
foreach($args as $i => $arg){ | |
if ($i === 0 && (strlen($arg) == 1 || strlen($arg) == 2)){ //first arg, and it's only one or two letters | |
if (strstr($arg, self::SELF_ARGUMENTS_LOW)) { //arguments of mylogger() function | |
$self_args_deep = false; | |
//'M' can be together with 'T' or 'N', so no continue; | |
} | |
if (strstr($arg, self::BACKTRACE_NONE)) { | |
$backtrace = false; | |
} | |
//default arg trace none, i.e. undefined just same as ARGTRACE_NONE | |
$trace_args = false; | |
$trace_args_deep = false; | |
if (strstr($arg, self::ARGTRACE_DEEP)) { //log stacktrace arguments and deep | |
$trace_args = true; | |
$trace_args_deep = true; | |
continue; | |
} else if (strstr($arg, self::ARGTRACE_LOW)) { //log stacktrace arguments but not deep | |
$trace_args = true; | |
$trace_args_deep = false; | |
continue; | |
} else if (strstr($arg, self::ARGTRACE_NONE)) { //not log stacktrace arguments | |
$trace_args = false; | |
$trace_args_deep = false; | |
continue; | |
} | |
} else if ($i === 1 && $arg === 'X') { | |
//if the 2nd param is 'X', don't log it | |
continue; | |
} else if ($i == $args_num - 1 && stripos($arg, 'filename:') === 0) { //the last arg and it starts with "filename:" | |
$logger_filename = substr($arg, strlen('filename:')); | |
continue; | |
} | |
$self_args_msg .= $this->arg2text($arg, $self_args_deep); | |
} //end foreach | |
} | |
//this file is put under shared/config/ | |
$logger_filename = self::LOG_DIR . ($logger_filename ?: ($this->is_php_useful() ? 'debug_super' : 'debug_phantom')) . '.log'; | |
$i = 0; | |
$opened = false; | |
//sometimes large content can't be written in time | |
/* while (!$opened && $i < 3) { | |
unset($fs); | |
if(!$fs = fopen($logger_filename,'a+')){ | |
sleep(1); | |
$i++; | |
$opened = false; | |
} else { | |
$opened = true; | |
} | |
}*/ | |
if (!$fs = fopen($logger_filename, 'a+')) { | |
exit('unable to open mylogger file ' . $logger_filename); | |
//throw new Exception('Exception: unable to open log file ' . $logger_filename); | |
} | |
//trace | |
$stack = debug_backtrace(); | |
$traces = array(); | |
$trace_args_str = ''; | |
$from_query = false; | |
foreach($stack as $i => $item) { | |
if ($i == 1 && $trace_args) { | |
$args = $this->index_get($item, 'args'); | |
if (count($args)) { | |
foreach ($args as $j => $arg) { | |
//included file path will go to stacktrace args, don't log them | |
if (is_string($arg) && stristr($arg, PROJECT_HOME) == $arg && file_exists($arg)) { | |
continue; | |
} | |
$trace_args_str .= 'Arg(' . $j . '): ' . $this->arg2text($arg, $trace_args_deep); | |
} | |
} | |
} | |
//display file path of local desktop rather than in vagrant | |
//$file = str_replace(VMROOT, PROJECT_HOME, $this->var_get($item['file'])); | |
$file = $this->var_get($item['file']); | |
$line = $this->var_get($item['line']); | |
//make it recognizable for PHPStorm stacktrace tool | |
$per_trace = ''; | |
//$per_trace = "\t0.000000\t888888\t"; | |
$per_trace .= $this->var_get($item['class'], '') . $this->var_get($item['type']) . $this->var_get($item['function']) . "()\t#" . (count($stack) - $i) . ' ' . $file . ':' . $line . PHP_EOL; | |
//for no backtrace, still log the last trace | |
if ($i === 0 && !$backtrace) { | |
$traces_str = $per_trace; | |
break; | |
} | |
$per_trace_html = "<a href='javascript:var rq = new XMLHttpRequest(); rq.open(\"GET\", \"http://localhost:8091?message=$file:$line\", true); rq.send(null);'>$per_trace</a><br/>"; | |
$traces[] = $per_trace; | |
$traces_html[] = $per_trace_html; | |
if ($i === 0 && stristr($item['file'], self::QUERY_REL_PATH)) { //MyLogger is called from DB query method | |
//only true when the last trace is connection.php | |
$from_query = true; | |
} | |
} //end foreach | |
if ($backtrace) { | |
$traces = array_reverse($traces); | |
$traces_html = array_reverse($traces_html); | |
$traces_str = ''; | |
foreach ($traces as $i => $trace) { | |
$traces_str .= $trace; | |
} | |
$traces_str_html = ''; | |
foreach ($traces_html as $i => $trace_html) { | |
$traces_str_html .= $trace_html; | |
} | |
} | |
//----------------- start writing ------------------- | |
$traces_str = $traces_str ? $traces_str . PHP_EOL : ''; | |
$log = PHP_EOL . PHP_EOL . "Log time:" . date('Y-m-d H:i:s') . PHP_EOL . $traces_str; | |
if ($trace_args_str && !$from_query) { | |
//fwrite($fs, 'Stack trace arguments:' . PHP_EOL . $trace_args_str . PHP_EOL); | |
$log .= 'Stack trace arguments:' . PHP_EOL . $trace_args_str . PHP_EOL; | |
} | |
if (!empty($self_args_msg)) { | |
$log .= 'MYLOGGER arguments:' . PHP_EOL . $self_args_msg; | |
} | |
fwrite($fs, $log); | |
fclose($fs); | |
$log_html = str_replace(PHP_EOL, '<br/>', $log); | |
return $log_html; | |
} | |
//only for william local use | |
/** | |
* To determine if there's object or resource in an array | |
* | |
* @param $arg must be an array | |
* @return boolean | |
*/ | |
function having_object($arg) { | |
if ($this->nesting_count++ < 100 - 5) { //to avoid: Fatal error: Maximum function nesting level of '100' reached, aborting! | |
return false; | |
} | |
$has_object = false; | |
if (is_object($arg)) { | |
return true; | |
} else if (is_array($arg)) { | |
foreach($arg as $part_arg) { | |
if (is_object($part_arg) || is_resource($part_arg)) { | |
$has_object = true; | |
break; | |
} elseif (is_array($part_arg)) { | |
$has_object = $this->having_object($part_arg); | |
if ($has_object) { | |
break; | |
} | |
} | |
} | |
} | |
return $has_object; | |
} | |
function get_uri() { | |
if (!array_key_exists('REQUEST_URI',$_SERVER )) { | |
return ''; | |
} | |
$uri = strrchr($_SERVER['REQUEST_URI'], '/'); | |
$uri = str_replace('/', '', $uri); | |
$uri = str_replace (stristr($uri, '?'), '', $uri); | |
return $uri; | |
} | |
function is_uri_useful() { | |
$uri = $this->get_uri(); | |
if ($uri && !in_array($uri,array( | |
//when logging interception panel...., comment it | |
'interceptor.php', | |
'frame_ticker_market.php', | |
'bet_ticker_ajax.php', | |
'alerts_ticker.php', //intercept panel needed? | |
//when logging password rotation, ...., comment it | |
'status.php', | |
'frame_top.php', | |
'customer_status.php', | |
'frames.php', | |
//when logging admin top drop down category list,..., comment it | |
'cs_get_categories.php', | |
'accounts_main.php', | |
'remote_server.php', | |
))) { | |
return true; | |
} | |
return false; | |
} | |
function is_cli() { | |
return in_array(PHP_SAPI, array('cli', 'cgi', 'cgi-fcgi')); | |
} | |
function is_cli_useful() { | |
if (!in_array(PHP_SAPI, array('cli', 'cgi', 'cgi-fcgi'))) { | |
return false; | |
} | |
if (in_array($GLOBALS['argv'][0], array( | |
'daily_cron.php', | |
//betshop cron and pregenerate files, filter betshop/pregenerate/ and betshop/cron/ could be better | |
'leftnavigation.php', | |
'homepageblocks.php', | |
'promo_block_markets.php', | |
'next_three_races.php', | |
'allracing.php', | |
'navdropdowns.php', | |
'market_dispatcher.php', | |
'single_liability_dispatcher.php', | |
'multi_liability_dispatcher.php', | |
))) { | |
return false; | |
} | |
return true; | |
} | |
function is_php_useful() { | |
return false | |
|| $this->is_uri_useful() | |
|| $this->is_cli_useful() | |
; | |
} | |
//for issue 16941 | |
function is_modify_sql($sql) { | |
$sqlt = trim($sql); | |
//if (stristr($sqlt, 'UPDATE ') == $sqlt || stristr($sqlt, 'INSERT ') == $sqlt || stristr($sqlt, 'REPLACE ') == $sqlt || stristr($sqlt, 'DELETE ') == $sqlt) { | |
if (stripos($sqlt, 'UPDATE') === 0 || stripos($sqlt, 'INSERT') === 0 || stripos($sqlt, 'REPLACE') === 0 || stripos($sqlt, 'DELETE') === 0) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function is_select_sql($sql) { | |
$sqlt = trim($sql); | |
if (stripos($sqlt, 'SELECT ') === 0) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function is_sql_valid($sql) { | |
$sql = trim($sql); | |
$sql = str_replace(array("\n","\r", "\t"), array(' '), $sql); | |
foreach (self::INVALID_SQLS as $isql) | |
if (stripos($sql, $isql) === 0) { | |
return false; | |
} | |
return true; | |
} | |
function select_all($sql, $columns = null) { | |
$sqlt = trim($sql); | |
if (!stristr($sql, 'SELECT ')) { | |
return false; | |
} | |
if (is_null($columns)) { | |
return true; | |
} | |
if (!is_array($columns)) { | |
$columns = array($columns); | |
} | |
foreach ($columns as $column) { | |
if (!stristr($sqlt, $column)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
function select_any($sql, $columns = null) { | |
$sqlt = trim($sql); | |
if (!stristr($sql, 'SELECT ')) { | |
return false; | |
} | |
if (is_null($columns)) { | |
return true; | |
} | |
if (!is_array($columns)) { | |
$columns = array($columns); | |
} | |
foreach ($columns as $column) { | |
if (stristr($sqlt, $column)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function contain_all($sql, $columns) { | |
$sqlt = trim($sql); | |
if (!is_array($columns)) { | |
$columns = array($columns); | |
} | |
foreach ($columns as $column) { | |
if (!stristr($sqlt, $column)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
function contain_any($sql, $columns) { | |
$sqlt = trim($sql); | |
if (!is_array($columns)) { | |
$columns = array($columns); | |
} | |
foreach ($columns as $column) { | |
if (stristr($sqlt, $column)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function var_get(&$var, $default = false) { | |
if (!isset($var)) { | |
return $default; | |
} else { | |
return (!empty($var) || (isset($var) && is_numeric($var) && $var == 0)) ? $var : $default; | |
} | |
} | |
function index_get(&$array, $index, $default = false) { | |
if (is_array($array)) { | |
return isset($array[$index]) ? $array[$index] : $default; | |
} | |
return $default; | |
} | |
/** | |
* To test: MyLogger->log('test false', false, 'test 0', 0, 'test empty', '', 'test null', null, 'test spaces', ' ', 'test space', ' ', 'test true', true, 'test number 0.00',0.00, 'test string 0.00', '0.00'); | |
* @param $arg | |
* @param bool $deep | |
* @return string | |
*/ | |
function arg2text($arg, $deep = true) { | |
$msg = ''; | |
if (empty($arg)) { | |
if (is_null($arg)) { | |
$msg .= '#NULL#' . PHP_EOL; | |
} else if ($arg === false) { | |
$msg .= '#FALSE#' . PHP_EOL; | |
} else if ($arg === '') { | |
$msg .= '#EMPTY STRING#' . PHP_EOL; | |
} else if ($arg === 0) { | |
$msg .= '#INT 0#' . PHP_EOL; | |
} else if ($arg == 0) { | |
$msg .= '#NUMERICAL 0#' . PHP_EOL; | |
} | |
} else if ($arg === true) { | |
$msg .= '#TRUE#' . PHP_EOL; | |
} else if ( $arg === ' ') { | |
$msg .= '#ONE SPACE#' . PHP_EOL; | |
} else if (is_string($arg)) { | |
if (trim($arg) === '' && $arg[0] === ' ') { | |
$msg .= '#' . strlen($arg) . ' SPACES#' . PHP_EOL; | |
} else { | |
$msg .= $arg . PHP_EOL; | |
} | |
} else { | |
if ($this->having_object($arg)) { | |
if ($deep) { | |
$msg .= print_r($arg,true) . PHP_EOL; | |
} else if (is_array($arg)) { | |
foreach ($arg as $per_arg) { | |
$msg .= $this->arg2text($per_arg, $deep); | |
} | |
} else { | |
$msg .= 'Instance of class '. get_class($arg) . PHP_EOL; | |
} | |
} else { | |
$msg .= print_r($arg,true) . PHP_EOL; | |
} | |
} | |
return $msg; | |
} | |
/** | |
* only when $var equals $value, mylogger() is called | |
* (called externally) | |
* @param $mixed $var when this is called, $var is passed in with a variable | |
*@param mixed $value when this is called, $value is passed in with a literal (e.g. an known string or a number) | |
*/ | |
function mylogger_on_equal($var = null, $value = null) { | |
if (!isset($var)) { | |
unset($GLOBALS['MYLOGGER']); | |
} else if ($var == $value) { | |
$GLOBALS['MYLOGGER'] = true; | |
} else { | |
$GLOBALS['MYLOGGER'] = false; | |
} | |
} | |
function is_ajax() { | |
if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/** | |
* Is the script name same as one of the arguments | |
* @param array|string $target_script_name | |
* multiple arguments separated by comma, or an array containing all valid script names | |
* | |
* @return bool | |
*/ | |
function script_is($target_script_name) { | |
if (!is_array($target_script_name)) { | |
$names = func_get_args(); | |
} else { | |
$names = $target_script_name; | |
} | |
if (empty($names)) { | |
return false; | |
} | |
foreach ($names as $name) { | |
if (strstr($_SERVER['SCRIPT_FILENAME'], $name) == $name) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* if script is same as the followings, return false | |
* @return bool | |
*/ | |
function is_script_valid() { | |
$scripts = array('cs_get_events.php'); //put more useless ajax scripts here | |
if (($this->script_is($scripts))) { | |
return false; | |
} else { | |
return true; | |
} | |
} | |
function friendly_error_type($type) | |
{ | |
$return =""; | |
if($type & E_ERROR) // 1 // | |
$return.='& E_ERROR '; | |
if($type & E_WARNING) // 2 // | |
$return.='& E_WARNING '; | |
if($type & E_PARSE) // 4 // | |
$return.='& E_PARSE '; | |
if($type & E_NOTICE) // 8 // | |
$return.='& E_NOTICE '; | |
if($type & E_CORE_ERROR) // 16 // | |
$return.='& E_CORE_ERROR '; | |
if($type & E_CORE_WARNING) // 32 // | |
$return.='& E_CORE_WARNING '; | |
if($type & E_COMPILE_ERROR) // 64 // | |
$return.='& E_COMPILE_ERROR '; | |
if($type & E_COMPILE_WARNING) // 128 // | |
$return.='& E_COMPILE_WARNING '; | |
if($type & E_USER_ERROR) // 256 // | |
$return.='& E_USER_ERROR '; | |
if($type & E_USER_WARNING) // 512 // | |
$return.='& E_USER_WARNING '; | |
if($type & E_USER_NOTICE) // 1024 // | |
$return.='& E_USER_NOTICE '; | |
if($type & E_STRICT) // 2048 // | |
$return.='& E_STRICT '; | |
if($type & E_RECOVERABLE_ERROR) // 4096 // | |
$return.='& E_RECOVERABLE_ERROR '; | |
if($type & E_DEPRECATED) // 8192 // | |
$return.='& E_DEPRECATED '; | |
if($type & E_USER_DEPRECATED) // 16384 // | |
$return.='& E_USER_DEPRECATED '; | |
return substr($return,2); | |
} | |
function logger_error_handler($errno, $errstr) { | |
error_log($errstr); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment