Skip to content

Instantly share code, notes, and snippets.

@davidon
Last active January 15, 2019 23:09
Show Gist options
  • Save davidon/2952e2a2471f0c9ac7f04df74ecebcd5 to your computer and use it in GitHub Desktop.
Save davidon/2952e2a2471f0c9ac7f04df74ecebcd5 to your computer and use it in GitHub Desktop.
my good logger class with backtrace
<?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