TimeTrex/classes/modules/core/Debug.class.php

849 lines
30 KiB
PHP
Raw Permalink Normal View History

2022-12-13 07:10:06 +01:00
<?php /** @noinspection PhpUndefinedFunctionInspection */
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
/**
* @package Core
*/
class Debug {
static protected $enable = false; //Enable/Disable debug printing.
static protected $verbosity = 5; //Display debug info with a verbosity level equal or lesser then this.
static protected $buffer_output = true; //Enable/Disable output buffering.
static protected $debug_buffer = null; //Output buffer.
static protected $enable_display = false; //Enable/Disable displaying of debug output
static protected $enable_log = false; //Enable/Disable logging of debug output
static protected $max_line_size = 200; //Max line size in characters. This is used to break up long lines.
static protected $max_buffer_size = 1000; //Max buffer size in lines. **Syslog can't handle much more than 1000.
static protected $buffer_id = null; //Unique identifier for the debug buffer.
static protected $php_errors = 0; //Count number of PHP errors so we can automatically email the log.
static protected $email_log = false; //Determine if log needs to be emailed on shutdown.
static protected $current_pid = null; //Current PID
static protected $buffer_size = 0;//Current buffer size in lines.
/**
* @param $bool
*/
static function setEnable( $bool ) {
self::setBufferID();
self::$enable = $bool;
}
/**
* @return bool
*/
static function getEnable() {
return self::$enable;
}
/**
* @param $bool
*/
static function setBufferOutput( $bool ) {
self::$buffer_output = $bool;
}
/**
* @param $level
*/
static function setVerbosity( $level ) {
global $db;
self::$verbosity = (int)$level;
if ( is_object( $db ) && $level == 11 ) {
$db->debug = true;
}
}
/**
* @return int
*/
static function getPHPErrors() {
return self::$php_errors;
}
/**
* @return int
*/
static function getVerbosity() {
return self::$verbosity;
}
/**
* @param $bool
*/
static function setEnableDisplay( $bool ) {
self::$enable_display = $bool;
}
/**
* @return bool
*/
static function getEnableDisplay() {
return self::$enable_display;
}
/**
* @param $bool
*/
static function setEnableLog( $bool ) {
self::$enable_log = $bool;
}
/**
* @return bool
*/
static function getEnableLog() {
return self::$enable_log;
}
static function setBufferID() {
if ( self::$buffer_id == null ) {
self::$buffer_id = uniqid();
}
}
/**
* @param string $extra_ident UUID
* @param null $company_name
* @return mixed
*/
static function getSyslogIdent( $extra_ident = null, $company_name = null ) {
global $config_vars, $current_company;
if ( $company_name != '' ) {
$suffix = $company_name;
} else if ( isset( $current_company ) && is_object( $current_company ) ) {
$suffix = $current_company->getShortName();
} else {
$suffix = 'System';
}
if ( isset( $config_vars['debug']['syslog_ident'] ) && $config_vars['debug']['syslog_ident'] != '' ) {
$retval = $config_vars['debug']['syslog_ident'] . '-' . $suffix . $extra_ident;
} else {
$retval = APPLICATION_NAME . '-' . $suffix . $extra_ident;
}
return strtolower( preg_replace( '/[^a-zA-Z0-9-]/', '', escapeshellarg( $retval ) ) ); //This will remove spaces.
}
/**
* Three primary log types: $log_types = array( 0 => 'debug', 1 => 'client', 2 => 'timeclock' );
* @param int $log_type
* @return int|mixed
*/
static function getSyslogFacility( $log_type = 0 ) {
global $config_vars;
if ( isset( $config_vars['debug']['syslog_facility'] ) && $config_vars['debug']['syslog_facility'] != '' ) {
$facility_arr = explode( ',', $config_vars['debug']['syslog_facility'] );
if ( is_array( $facility_arr ) && isset( $facility_arr[(int)$log_type] ) ) {
return ( is_numeric( $facility_arr[(int)$log_type] ) ) ? $facility_arr[(int)$log_type] : constant( trim( $facility_arr[(int)$log_type] ) );
}
}
return LOG_LOCAL7; //Default
}
/**
* @param int $log_type
* @return int|mixed
*/
static function getSyslogPriority( $log_type = 0 ) {
global $config_vars;
if ( isset( $config_vars['debug']['syslog_priority'] ) && $config_vars['debug']['syslog_priority'] != '' ) {
$priority_arr = explode( ',', $config_vars['debug']['syslog_priority'] );
if ( is_array( $priority_arr ) && isset( $priority_arr[(int)$log_type] ) ) {
return ( is_numeric( $priority_arr[(int)$log_type] ) ) ? $priority_arr[(int)$log_type] : constant( trim( $priority_arr[(int)$log_type] ) );
}
}
return LOG_DEBUG; //Default
}
/**
* Used to add timing to each debug call.
* @return float
*/
static function getExecutionTime() {
return ceil( ( ( microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT'] ) * 1000 ) );
}
/**
* Splits long debug lines or array dumps to prevent syslog overflows.
* @param $text
* @param null $prefix
* @param null $suffix
* @return array
*/
static function splitInput( $text, $prefix = null, $suffix = null ) {
if ( strlen( $text ) > self::$max_line_size ) {
$retarr = [];
$lines = explode( PHP_EOL, $text ); //Split on newlines first.
foreach ( $lines as $line ) {
$split_lines = str_split( $line, self::$max_line_size ); //Split on long lines next.
foreach ( $split_lines as $split_line ) {
$retarr[] = $prefix . $split_line . $suffix;
}
}
unset( $lines, $line, $split_lines, $split_line );
} else {
$retarr = [ $prefix . $text . $suffix ]; //Always returns an array.
}
return $retarr;
}
/**
* Get PID of current process.
* @return false|int|null
*/
static function getCurrentPID() {
if ( self::$current_pid === null ) {
if ( function_exists( 'getmypid' ) == true ) {
self::$current_pid = getmypid();
} else {
self::$current_pid = 0;
}
}
return self::$current_pid;
}
/**
* @param null $text
* @param string $file
* @param int $line
* @param string $method
* @param int $verbosity
* @return bool
*/
static function Text( $text = null, $file = __FILE__, $line = __LINE__, $method = __METHOD__, $verbosity = 9 ) {
if ( $verbosity > self::getVerbosity() || self::$enable == false ) {
return false;
}
if ( empty( $method ) ) {
$method = 'GLOBAL: '; //Was: [Function]
} else {
$method = $method . '(): ';
}
//If text is too long, split it into an array.
$text_arr = self::splitInput( $text, '[P'. str_pad( self::getCurrentPID(), 7, 0, STR_PAD_LEFT ) .'] [' . str_pad( self::getExecutionTime(), 5, 0, STR_PAD_LEFT ) . 'ms] [L' . str_pad( $line, 4, 0, STR_PAD_LEFT ) . ']: ' . $method, PHP_EOL );
if ( self::$buffer_output == true ) {
foreach ( $text_arr as $text_line ) {
self::$debug_buffer[] = [ $verbosity, $text_line ];
self::$buffer_size++;
self::handleBufferSize( $line, $method );
}
} else {
if ( self::$enable_display == true ) {
foreach ( $text_arr as $text_line ) {
echo $text_line;
}
} else if ( OPERATING_SYSTEM != 'WIN' && self::$enable_log == true ) {
foreach ( $text_arr as $text_line ) {
syslog( LOG_DEBUG, $text_line );
}
}
}
return true;
}
/**
* @param object $profile_obj
* @return bool|string
*/
static function profileTimers( $profile_obj ) {
if ( !is_object( $profile_obj ) ) {
return false;
}
ob_start();
$profile_obj->printTimers();
$ob_contents = ob_get_contents();
ob_end_clean();
return $ob_contents;
}
static function showSQLProfile() {
if ( Debug::getVerbosity() >= 11 ) {
global $__tt_sql_profiler;
if ( isset( $__tt_sql_profiler ) ) {
self::Text( 'SQL Profile: Queries: ' . $__tt_sql_profiler['total_queries'] . ' ( Read: '. $__tt_sql_profiler['total_read_queries'] .' Write: '. $__tt_sql_profiler['total_write_queries'] .' ) Time: ' . $__tt_sql_profiler['total_time'] . ' ms Slowest: '. $__tt_sql_profiler['slowest_query']['time'], __FILE__, __LINE__, __METHOD__, 0 );
self::Query( $__tt_sql_profiler['slowest_query']['query'], $__tt_sql_profiler['slowest_query']['ph'], __FILE__, __LINE__, __METHOD__, 0 );
//self::Text( ' Slowest Query Backtrace: ' . $__tt_sql_profiler['slowest_query']['backtrace'], __FILE__, __LINE__, __METHOD__, 0 );
}
}
return true;
}
/**
* @return string
*/
static function backTrace() {
//ob_start();
//debug_print_backtrace();
//$ob_contents = ob_get_contents();
//ob_end_clean();
//return $ob_contents;
$retval = '';
$trace_arr = debug_backtrace();
if ( is_array( $trace_arr ) ) {
$i = 1;
foreach ( $trace_arr as $trace_line ) {
if ( isset( $trace_line['class'] ) && isset( $trace_line['type'] ) ) {
$class = $trace_line['class'] . $trace_line['type'];
} else {
$class = null;
}
if ( !isset( $trace_line['file'] ) ) {
$trace_line['file'] = 'N/A';
}
if ( !isset( $trace_line['line'] ) ) {
$trace_line['line'] = 'N/A';
}
if ( isset( $trace_line['args'] ) && is_array( $trace_line['args'] ) ) {
$args = [];
foreach ( $trace_line['args'] as $arg ) {
if ( is_array( $arg ) ) {
if ( self::getVerbosity() == 11 ) {
$args[] = self::varDump( $arg ); //NOTE: If this contains an exception object from ADODB and is triggered from a SQL error, it could cause a circular reference and exhaust all memory.
} else {
//Don't display the entire array is it polutes the log and is too large for syslog anyways.
$args[] = 'Array(' . count( $arg ) . ')';
}
} else if ( is_object( $arg ) ) {
if ( self::getVerbosity() == 11 ) {
$args[] = self::varDump( $arg ); //NOTE: If this contains an exception object from ADODB and is triggered from a SQL error, it could cause a circular reference and exhaust all memory.
} else {
//Don't display the entire array is it polutes the log and is too large for syslog anyways.
$args[] = 'Object(' . get_class( $arg ) . ')';
}
} else {
$args[] = $arg;
}
}
}
$retval .= '#' . $i . '.' . $class . $trace_line['function'] . '(' . implode( ', ', $args ) . ') ' . $trace_line['file'] . ':' . $trace_line['line'] . PHP_EOL;
$i++;
}
}
unset( $trace_arr, $trace_line, $args );
return $retval;
}
/**
* @param $array
* @return string
*/
static function varDump( $array ) {
ob_start();
var_dump( $array ); //Xdebug may interfere with this and cause it to not display all the data...
//print_r($array);
$ob_contents = ob_get_contents();
ob_end_clean();
return $ob_contents;
}
/**
* @param $array
* @param null $text
* @param string $file
* @param int $line
* @param string $method
* @param int $verbosity
* @return bool
*/
static function Arr( $array, $text = null, $file = __FILE__, $line = __LINE__, $method = __METHOD__, $verbosity = 9 ) {
if ( $verbosity > self::getVerbosity() || self::$enable == false ) {
return false;
}
if ( empty( $method ) ) {
$method = '[Function]';
}
$text_arr = [];
$text_arr[] = '[P'. str_pad( self::getCurrentPID(), 7, 0, STR_PAD_LEFT ) .'] [' . str_pad( self::getExecutionTime(), 5, 0, STR_PAD_LEFT ) . 'ms] [L' . str_pad( $line, 4, 0, STR_PAD_LEFT ) . '] Array: ' . $method . '(): ' . $text . PHP_EOL;
$text_arr = array_merge( $text_arr, self::splitInput( self::varDump( $array ), null, PHP_EOL ) );
$text_arr[] = PHP_EOL;
if ( self::$buffer_output == true ) {
foreach ( $text_arr as $text_line ) {
self::$debug_buffer[] = [ $verbosity, $text_line ];
self::$buffer_size++;
self::handleBufferSize( $line, $method );
}
} else {
if ( self::$enable_display == true ) {
foreach ( $text_arr as $text_line ) {
echo $text_line;
}
} else if ( OPERATING_SYSTEM != 'WIN' && self::$enable_log == true ) {
foreach ( $text_arr as $text_line ) {
syslog( LOG_DEBUG, $text_line );
}
}
}
return true;
}
/**
* Output SQL query with place holders inserted into the query.
* @param $query
* @param $ph
* @param string $file
* @param int $line
* @param string $method
* @param int $verbosity
* @return bool
*/
static function Query( $query, $ph, $file = __FILE__, $line = __LINE__, $method = __METHOD__, $verbosity = 9 ) {
$output_query = PHP_EOL; //Start with newline so its easier to copy&paste.
$split_query = explode( '?', $query );
foreach ( $split_query as $query_chunk ) {
$ph_value = ( !empty( $ph ) ) ? array_shift( $ph ) : false; //array_shift() returns NULL if no elements are left, but the first value can also be NULL in some cases too.
if ( is_string( $ph_value ) ) {
$ph_value = '\'' . $ph_value . '\'';
} else if ( $ph_value === null ) {
$ph_value = 'NULL';
}
$output_query .= $query_chunk . $ph_value;
}
$output_query = str_replace( "\t", ' ', $output_query );
$output_query .= ';' . PHP_EOL; //End with newline so its easier to copy&paste.
self::Arr( $output_query, 'SQL Query: ', $file, $line, $method, $verbosity );
return true;
}
/**
* @return array Replacement for apache_request_headers() as it wasn't reliably available and would sometimes cause PHP fatal errors due to it being undefined.
*
* Replacement for apache_request_headers() as it wasn't reliably available and would sometimes cause PHP fatal errors due to it being undefined.
*/
static function RequestHeaders() {
$arh = [];
$rx_http = '/\AHTTP_/';
foreach ( $_SERVER as $key => $val ) {
if ( preg_match( $rx_http, $key ) ) {
$arh_key = preg_replace( $rx_http, '', $key );
// do some nasty string manipulations to restore the original letter case
// this should work in most cases
$rx_matches = explode( '_', strtolower( $arh_key ) );
if ( count( $rx_matches ) > 0 && strlen( $arh_key ) > 2 ) {
foreach ( $rx_matches as $ak_key => $ak_val ) {
$rx_matches[$ak_key] = ucfirst( $ak_val );
}
$arh_key = implode( '-', $rx_matches );
}
$arh[$arh_key] = $val;
}
}
if ( isset( $_SERVER['CONTENT_TYPE'] ) ) {
$arh['Content-Type'] = $_SERVER['CONTENT_TYPE'];
}
if ( isset( $_SERVER['CONTENT_LENGTH'] ) ) {
$arh['Content-Length'] = $_SERVER['CONTENT_LENGTH'];
}
return $arh;
}
/**
* @param $error_number
* @param $error_str
* @param $error_file
* @param $error_line
* @return bool
*/
static function ErrorHandler( $error_number, $error_str, $error_file, $error_line ) {
//Only handle errors included in the error_reporting()
if ( ( error_reporting() & $error_number ) ) { //Bitwise operator.
// This error code is not included in error_reporting
switch ( $error_number ) {
case E_USER_ERROR:
$error_name = 'FATAL';
break;
case E_USER_WARNING:
case E_WARNING:
$error_name = 'WARNING';
break;
case E_USER_NOTICE:
case E_NOTICE:
$error_name = 'NOTICE';
break;
case E_STRICT:
$error_name = 'STRICT';
break;
case E_DEPRECATED:
$error_name = 'DEPRECATED';
break;
default:
$error_name = 'UNKNOWN';
}
$error_name .= '(' . $error_number . ')';
$text = 'PHP ERROR - ' . $error_name . ': ' . $error_str . ' File: ' . $error_file . ' Line: ' . $error_line;
//If this is the first PHP error, make sure debugging is enabled so it and any others can be captured.
if ( self::$php_errors == 0 ) {
self::setEnable( true );
self::setBufferOutput( true );
}
self::$php_errors++;
//Display these errors in the log, but don't cause them to trigger PHP errors that forces the log to be emailed.
if ( $error_number == E_USER_ERROR
|| ( DEPLOYMENT_ON_DEMAND == true
|| ( DEPLOYMENT_ON_DEMAND == false
&& (
//Database
stristr( $error_str, 'unable to connect' ) === false
&& stristr( $error_str, 'statement timeout' ) === false
&& stristr( $error_str, 'unique constraint' ) === false
&& stristr( $error_str, 'deadlock' ) === false
&& stristr( $error_str, 'server has gone away' ) === false
&& stristr( $error_str, 'software caused connection abort' ) === false
&& stristr( $error_str, 'closed the connection unexpectedly' ) === false
&& stristr( $error_str, 'execution was interrupted' ) === false
&& stristr( $error_str, 'terminating connection due to administrator command' ) === false
&& stristr( $error_str, 'could not open file' ) === false
&& stristr( $error_str, 'no such file or directory' ) === false
&& stristr( $error_str, 'no space left on device' ) === false
&& stristr( $error_str, 'unserialize' ) === false
&& stristr( $error_str, 'headers already sent by' ) === false
//SOAP
&& stristr( $error_str, 'An existing connection was forcibly closed by the remote host' ) === false
//MISC
&& stristr( $error_str, 'Unable to fork' ) === false
)
)
)
) {
self::$email_log = true;
}
if ( self::$php_errors == 1 ) { //Only trigger this on the first error, so its not repeated over and over again.
if ( PHP_SAPI != 'cli' ) { //Used to use apache_request_headers() here, but it would often fail as undefined, even though we would check function_exists() on it.
self::Arr( self::RequestHeaders(), 'Raw Request Headers: ', $error_file, $error_line, __METHOD__, 0 );
}
global $HTTP_RAW_POST_DATA;
if ( $HTTP_RAW_POST_DATA != '' ) {
self::Arr( urldecode( $HTTP_RAW_POST_DATA ), 'Raw POST Request: ', $error_file, $error_line, __METHOD__, 0 );
}
}
self::Text( '(E' . self::$php_errors . ') ' . $text, $error_file, $error_line, __METHOD__, 0 );
self::Text( self::backTrace(), $error_file, $error_line, __METHOD__, 0 );
}
return false; //Let the standard PHP error handler work as well.
}
/**
* @return string|false
*/
static function getLastPHPErrorMessage() {
$error = error_get_last();
if ( isset( $error['message'] ) ) {
return $error['message'];
}
return false;
}
/**
* @return bool
*/
static function Shutdown() {
$error = error_get_last();
if ( $error !== null && isset( $error['type'] ) && $error['type'] == 1 ) { //Only trigger fatal errors on shutdown.
self::$php_errors++;
self::$email_log = true; //On FATAL error, the error handler is not called, just shutdown is called. So we need to make sure we increment the php_errors and enable emailing the log.
self::Text( 'PHP ERROR - FATAL(' . $error['type'] . '): ' . $error['message'] . ' File: ' . $error['file'] . ' Line: ' . $error['line'], $error['file'], $error['line'], __METHOD__, 0 );
if ( defined( 'TIMETREX_API' ) && TIMETREX_API == true ) { //Only when a fatal error occurs.
global $api_message_id;
if ( $api_message_id != '' ) {
$progress_bar = new ProgressBar();
$progress_bar->error( $api_message_id, TTi18n::getText( 'ERROR: Operation cannot be completed.' ) );
unset( $progress_bar );
}
}
}
if ( self::$email_log == true ) {
//If the error log is too long, make sure we add important data to help trace it are included at the end of the log.
global $config_vars, $current_user, $current_company;
self::Text( 'URI: ' . ( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'N/A' ) . ' IP Address: ' . Misc::getRemoteIPAddress(), __FILE__, __LINE__, __METHOD__, 10 );
self::Text( 'USER-AGENT: ' . ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'N/A' ), __FILE__, __LINE__, __METHOD__, 10 );
self::Text( 'Version: ' . APPLICATION_VERSION . ' (PHP: v' . phpversion() . ' ['. PHP_INT_SIZE .']) Edition: ' . getTTProductEdition() . ' Production: ' . (int)PRODUCTION . ' Server: ' . ( isset( $_SERVER['SERVER_ADDR'] ) ? $_SERVER['SERVER_ADDR'] : 'N/A' ) . ' OS: ' . OPERATING_SYSTEM . ' Database: Type: ' . ( isset( $config_vars['database']['type'] ) ? $config_vars['database']['type'] : 'N/A' ) . ' Name: ' . ( isset( $config_vars['database']['database_name'] ) ? $config_vars['database']['database_name'] : 'N/A' ) . ' Config: ' . CONFIG_FILE . ' Demo Mode: ' . (int)DEMO_MODE, __FILE__, __LINE__, __METHOD__, 10 );
self::Text( 'Current User: ' . ( ( isset( $current_user ) && is_object( $current_user ) ) ? $current_user->getUserName() : 'N/A' ) . ' (User ID: ' . ( ( isset( $current_user ) && is_object( $current_user ) ) ? $current_user->getID() : 'N/A' ) . ') Company: ' . ( ( isset( $current_company ) && is_object( $current_company ) ) ? $current_company->getName() : 'N/A' ) . ' (Company ID: ' . ( ( isset( $current_company ) && is_object( $current_company ) ) ? $current_company->getId() : 'N/A' ) . ')', __FILE__, __LINE__, __METHOD__, 10 );
self::Text( 'Detected PHP errors (' . self::$php_errors . '), emailing log...', __FILE__, __LINE__, __METHOD__, 0 );
self::Text( '---------------[ ' . @date( 'd-M-Y G:i:s O' ) . ' [' . microtime( true ) . '] (PID: ' . getmypid() . ') ]---------------', __FILE__, __LINE__, __METHOD__, 0 );
self::emailLog();
if ( $error !== null ) { //Fatal error, write to log once more as this won't be called automatically.
self::writeToLog();
}
} else {
//Check to see if a transaction was held open, as it could be a potential problem as it was never committed.
// Essentially, a CommitTrasnaction() should be called after every FailTransaction() before the script exits. Otherwise in things like loops the entire outer transaction would be rolled back unintentionally.
global $db;
if ( is_object( $db ) ) {
$transaction_error = false;
if ( $db->transOff > 0 ) {
self::Text( 'ERROR: Detected UNCOMMITTED transaction: Count: ' . $db->transCnt . ' Off: ' . $db->transOff . ' OK: ' . (int)$db->_transOK . ', emailing log...', __FILE__, __LINE__, __METHOD__, 0 );
$transaction_error = true;
} else if ( $db->transCnt < 0 ) {
self::Text( 'ERROR: Detected DOUBLE COMMITTED transaction: Count: ' . $db->transCnt . ' Off: ' . $db->transOff . ' OK: ' . (int)$db->_transOK . ', emailing log...', __FILE__, __LINE__, __METHOD__, 0 );
$transaction_error = true;
}
if ( $transaction_error == true ) {
self::Text( '---------------[ ' . @date( 'd-M-Y G:i:s O' ) . ' [' . microtime( true ) . '] (PID: ' . getmypid() . ') ]---------------', __FILE__, __LINE__, __METHOD__, 0 );
self::emailLog();
self::writeToLog(); //write to log once more as this won't be called automatically.
}
}
}
//Must go after emailLog() and writeToLog() above, otherwise the log will get cleared out everytime this runs.
if ( PRODUCTION == false && function_exists( 'xdebug_get_gc_run_count' ) == true && xdebug_get_gc_run_count() > 0 ) {
self::Text( 'Garbage Collector Runs: ' . xdebug_get_gc_run_count() . ' Collected Roots: ' . xdebug_get_gc_total_collected_roots(), __FILE__, __LINE__, __METHOD__, 10 );
if ( file_exists( xdebug_get_gcstats_filename() ) ) {
self::Arr( file_get_contents( xdebug_get_gcstats_filename() ), 'Garbage Collection Report: ', __FILE__, __LINE__, __METHOD__, 10 );
}
self::writeToLog();
}
return true;
}
/**
* @return bool|null|string
*/
static function getOutput() {
$output = null;
if ( is_array( self::$debug_buffer ) && count( self::$debug_buffer ) > 0 ) {
foreach ( self::$debug_buffer as $arr ) {
$verbosity = $arr[0];
$text = $arr[1];
if ( $verbosity <= self::getVerbosity() ) {
$output .= $text;
}
}
return $output;
}
return false;
}
/**
* @return bool
*/
static function emailLog() {
if ( PRODUCTION === true ) {
$output = self::getOutput();
if ( strlen( $output ) > 0 ) {
global $TT_DISABLE_EMAIL_LOG;
if ( isset( $TT_DISABLE_EMAIL_LOG ) == false || $TT_DISABLE_EMAIL_LOG !== true ) { //Prevent emailLog() from triggering more errors and a emailLog infinite loop.
$TT_DISABLE_EMAIL_LOG = true;
Misc::sendSystemMail( APPLICATION_NAME . ' - Error!', $output );
$TT_DISABLE_EMAIL_LOG = false;
} else {
self::Text( 'WARNING: Skipping sendSystemMail() to avoid nested calls...', __FILE__, __LINE__, __METHOD__, 0 );
}
}
}
return true;
}
/**
* @return bool
*/
static function writeToLog() {
if ( self::$enable_log == true && self::$buffer_output == true ) {
global $config_vars;
$eol = PHP_EOL;
if ( is_array( self::$debug_buffer ) ) {
$output = $eol . '---------------[ ' . $_SERVER['REQUEST_TIME_FLOAT'] . ' {' . @date( 'd-M-Y G:i:s O' ) . '} (PID: ' . getmypid() . ') ]---------------' . $eol;
$verbosity_level = self::getVerbosity();
foreach ( self::$debug_buffer as $arr ) {
if ( $arr[0] <= $verbosity_level ) {
$output .= $arr[1];
}
}
$output .= '---------------[ ' . microtime( true ) . ' {' . @date( 'd-M-Y G:i:s O' ) . '} (PID: ' . getmypid() . ') ]---------------' . $eol;
if ( isset( $config_vars['debug']['enable_syslog'] ) && $config_vars['debug']['enable_syslog'] == true && OPERATING_SYSTEM != 'WIN' ) {
//If using rsyslog, need to set:
//$MaxMessageSize 256000 #Above ModuleLoad imtcp
openlog( self::getSyslogIdent(), 11, self::getSyslogFacility( 0 ) ); //11 = LOG_PID | LOG_NDELAY | LOG_CONS
syslog( self::getSyslogPriority( 0 ), $output ); //Used to strip_tags output, but that was likely causing problems with SQL queries with >= and <= in them.
closelog();
} else {
if ( isset( $config_vars['path']['log'] ) && is_writable( $config_vars['path']['log'] ) ) {
$file_name = $config_vars['path']['log'] . DIRECTORY_SEPARATOR . 'timetrex.log';
$fp = @fopen( $file_name, 'a' );
if ( $fp !== false ) {
@fwrite( $fp, $output ); //Used to strip_tags output, but that was likely causing problems with SQL queries with >= and <= in them.
@fclose( $fp );
unset( $output );
} else {
echo "ERROR: Unable to write to log file: " . ( isset( $config_vars['path']['log'] ) ? $config_vars['path']['log'] . '/' : 'N/A' ) . PHP_EOL;
}
} else {
echo "ERROR: Unable to write to log file in directory: " . ( isset( $config_vars['path']['log'] ) ? $config_vars['path']['log'] . '/' : 'N/A' ) . PHP_EOL;
}
}
return true;
}
}
return false;
}
/**
* @return bool
*/
static function Display() {
if ( self::$enable_display == true && self::$buffer_output == true ) {
$output = self::getOutput();
if ( function_exists( 'memory_get_usage' ) ) {
$memory_usage = memory_get_usage();
} else {
$memory_usage = 'N/A';
}
if ( strlen( $output ) > 0 ) {
echo PHP_EOL . 'Debug Buffer' . PHP_EOL;
echo '============================================================================' . PHP_EOL;
echo 'Memory Usage: ' . $memory_usage . ' Buffer Size: ' . self::$buffer_size . PHP_EOL;
echo '----------------------------------------------------------------------------' . PHP_EOL;
echo $output;
echo '============================================================================' . PHP_EOL;
}
return true;
}
return false;
}
/**
* @param null $line
* @param null $method
* @return bool
*/
static function handleBufferSize( $line = null, $method = null, $force = false ) {
//When buffer exceeds maximum size, write it to the log and clear it.
//This will affect displaying large buffers though, but otherwise we may run out of memory.
//If we detect PHP errors, buffer up to 10x the maximum size to try and capture those errors.
if ( $force == true || ( self::$php_errors == 0 && self::$buffer_size >= self::$max_buffer_size ) || ( self::$php_errors > 0 && self::$buffer_size >= ( self::$max_buffer_size * 100 ) ) ) {
self::$debug_buffer[] = [ 1, '[P'. str_pad( self::getCurrentPID(), 7, 0, STR_PAD_LEFT ) .'] [' . str_pad( self::getExecutionTime(), 5, 0, STR_PAD_LEFT ) . 'ms] [L' . str_pad( $line, 4, 0, STR_PAD_LEFT ) . ']: ' . $method . '(): Maximum debug buffer size of: ' . self::$max_buffer_size . ' reached. Writing out buffer before continuing... Buffer ID: ' . self::$buffer_id . PHP_EOL ];
self::writeToLog();
self::clearBuffer();
self::$debug_buffer[] = [ 1, '[P'. str_pad( self::getCurrentPID(), 7, 0, STR_PAD_LEFT ) .'] [' . str_pad( self::getExecutionTime(), 5, 0, STR_PAD_LEFT ) . 'ms] [L' . str_pad( $line, 4, 0, STR_PAD_LEFT ) . ']: ' . $method . '(): Continuing debug output from Buffer ID: ' . self::$buffer_id . PHP_EOL ];
return true;
}
return false;
}
/**
* @return bool
*/
static function clearBuffer() {
self::$debug_buffer = null;
self::$buffer_size = 0;
return true;
}
}
?>