PHP ErrorHandling 0.9

Meglévő minimalista hibakezelést „turbóztam fel” emailküldéssel. Nekem ez a verzió jobban bejön, egyébként meg ízlés dolga. Van egy „bootloader” területünk (az az include, amit mindig, minden körülmények között meg KELL hívni), ebbe helyeztem el egy require_once("ErrorHandler.php"); , ezzel el is intéztem az egészet.

A kódnak két (három) feladata van:

  • Error esetén emailt küldeni stackTrace-szel, változók értékével, hibaüzenettel.
  • Exception esetén ugyanezt megcsinálni.
  • Meglévő error.log file-t továbbra is úgy írni, mintha semmi se történt volna.

A harmadik ponttal úgy voltam, hogy a lehető legkevesebbet akartam azon a kódon változtatni, ezért egy kicsit öszvér a végeredmény, ezért is neveztem el 0.9-es verziónak. Ha időm engedi, akkor tisztességgel integrálom a „printDebugBacktrace3” metódust is.

Ha van valami jó ötleted, hogy amitől még ütősebb lehetne, akkor azt sok szeretettel látom a kommentben (és beépítem a köv. verzióba).

Disclaimer: a printDebugBacktrace3 metódust nem én írtam, ezért van németül kommentelve és ezért érhető tetten, hogy teljesen más a hibaüzenet formázása, valamint az, hogy milyen értékekre kíváncsi.

 'Error'
		,E_WARNING => 'Warning'
		,E_PARSE => 'Parsing Error'
		,E_NOTICE => 'Notice'
		,E_CORE_ERROR => 'Core Error'
		,E_CORE_WARNING => 'Core Warning'
		,E_COMPILE_ERROR => 'Compile Error'
		,E_COMPILE_WARNING => 'Compile Warning'
		,E_USER_ERROR => 'User Error'
		,E_USER_WARNING => 'User Warning'
		,E_USER_NOTICE => 'User Notice'
		,E_STRICT => 'Runtime Notice'
		,E_RECOVERABLE_ERROR => 'Catchable Fatal Error'
		,E_DEPRECATED => "Deprecated"
		,E_USER_DEPRECATED => "User deprecated"
	);
		
	/**
	 * Set of errors for which a var trace will be saved.
	 * @var Array $userErrors
	 */
	private static $userErrors = array( E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE );

	/**
	 * Set of errors for which an e-mail will be sent.
	 * @var Array $mailErrors
	 */
	private static $mailErrors = array( E_ERROR, E_WARNING, E_PARSE );

	/**
	 * Formating the exception message and calls "sendMail".
	 *
	 * @param Exception e thrown Exception
	 */
	public static function handleException($e) {
		$emsg = $e->getMessage();
		$err  = "EXCEPTION TRACE as string".PHP_EOL;
		$err .= $e->getTraceAsString().PHP_EOL;
		$err .= "PRINT_R EXCEPTION".PHP_EOL;
		$err .= print_r($e, true).PHP_EOL;

		self::sendMail($emsg, $err);
	}

	/**
	 * Formating the error message for PHPLOG and for email. Calls "sendMail".
	 *
	 * @param int errno 		error number, refering to variable errorTypers
	 * @param string errmsg 	error message
	 * @param string filename 	the error occured in the $filename
	 * @param int linenum		the error occured at the $linenum line
	 * @param mixed vars		variables 
	 */
	public static function handleError( $errno, $errmsg, $filename, $linenum, $vars ) {

		if(!(error_reporting() & $errno)) {
			return;
		}

		$dbgTrace = debug_backtrace();
		printDebugBacktrace3($errno, $errmsg, $filename, $linenum, $dbgTrace);

		// SPAM Prevention: Keine Warnungen wegen NOWAIT Locks aus dem AccessProtectionSystem
		// and because we have an old PHP
		if(strpos( $errmsg, 'deprecated') !== false) {
			return;
		}

		/* Der '@'-Operator in PHP wird verwendet, um Warnungen zu unterdrücken.
		 * Eigene Error Handler werden jedoch trotzdem aufgerufen, allerdings wird error_reporting 
		 * temporär auf 0 gesetzt. 
		 * Uns interessieren diese Meldungen hier nicht, da der Entwickler sich ja explitzit dazu
		 * entschlossen hat, diese Warnungen zu unterdrücken.    
		 */
		if(error_reporting() === 0) {
			return;
		}
		$err = self::$errorType[$errno]."($errno), ".date( "Y-m-d H:i:s (T)" ).":\n";

		if( in_array( $errno, self::$userErrors ) ) {
			$err .= "\t" . wddx_serialize_value( $vars, "Variables" ) . "\n";
		}

		foreach( $dbgTrace as $l_idx => $l_trace ) {
			if( $l_idx == 0 ) // Der letzte Aufruf ist diese Handler-Funktion: Hier also nur den Fehler ausgeben!
			{
				$err .= '#'.$l_idx.' '.$filename.'('.$linenum.'): '.$errmsg."\n";
			}
			else
			{
				if ( !isset( $l_trace['file'] ) )
					continue;
				
				$err .= '#'.$l_idx.' '.$l_trace['file'].'('.$l_trace['line'].'): ';
				$args = array();
				if( isset( $l_trace['args'] ) && is_array( $l_trace['args'] ) )
				foreach( $l_trace['args'] as $ll_arg )
				{
					if( is_object( $ll_arg ) )
					{
						$args[] = 'Object('.get_class( $ll_arg ).')';
					}
					elseif( is_resource( $ll_arg ) )
					{
						$args[] = get_resource_type( $ll_arg );
					}
					elseif( is_array( $ll_arg ) )
					{
						$args[] = 'array('.count($ll_arg).')';
					}
					else
					{
						$ll_s = (string)$ll_arg;
						if( strlen( $ll_s )>30 )
						{
							$ll_s = substr( $ll_s, 0, 15 ).'...'.substr( $ll_s, -15 );
						}
						$args[] = is_string($ll_arg) ? '"'.$ll_s.'"' : $ll_s;
					}
				}
				if( isset( $l_trace['object'] ) )
				{
					$err .= get_class($l_trace['object']).'->';
				}
				$err .= $l_trace['function'].'( '.implode(', ', $args).' )'."\n";
			}
		} // foreach stacktrace


		// only critical errors
		if( in_array( $errno, self::$mailErrors ) )
		{
				self::sendMail(self::$errorType[$errno].' - '.$errmsg, $err);
			
		}
	} // handleError()


	/**
	 * Creates and send an email including the error message (created by the methods handleError and handleException).
	 * Also includes session variables and defined variables.
	 * 
	 * @param string emsg	Error message for the header
	 * @param string err	Long error text including callStack.
	 */
        static function sendMail ($emsg, $err) {
		$mail = new Mail();
		$mail->setSubject('Error in CoConUt: '.$emsg);

		$arguments = php_sapi_name() == 'cli' ? $_SERVER['argv'] : $_REQUEST;

                $msg = $err.PHP_EOL;
		$msg .= PHP_EOL."SESSION VARIABLES: ".PHP_EOL;
                $msg .= print_r( $_SESSION, true).PHP_EOL;
		$msg .= PHP_EOL."DEFINED VARIABLES: ".PHP_EOL;
                $msg .= print_r( get_defined_vars (), true).PHP_EOL;
		$msg .= PHP_EOL."ARGUMENTS: ".PHP_EOL;
		$msg .= print_r( $arguments, true).PHP_EOL;

		$mail->setBodyText( $msg );
		try 	{
			$mail->send();
		}
		catch( Exception $e )
		{
			print( 'Error when sending exception e-mail: '.$e->getMessage().PHP_EOL.$e->getTraceAsString().PHP_EOL );
		}

	
	}
}

/**
 * Class Mail is a simple class to send emails. It uses the PHP method mail().
 * The sender will be "Apache" if the error occured during a web session or 
 * the name assigned to the shell script user if the error occured during the execution of a shell script.
 * 
 */
class Mail {
	/** 
	 * @var string subject the subject of the email
	 */
	var $subject = "Coconut Error ";
	/**
	 * @var string body the email body
	 */
	var $body;

	/**
	 * Sets the subject of the email
	 *
	 * @param string s subject
	 */
	function setSubject($s) {
		$this->subject = $s;
	}

	/**
	 * Sets the body of the email
	 *
	 * @param string s body
	 */
	function setBodyText($s) {
		$this->body = $s;
	}

	/**
	 * Executes the mail() method of PHP with the correct parameters.
	 */
	function send() { 
		mail (EMAILTO, $this->subject, $this->body);
/	}

} // end of class Mail


/**
 * Dieses Include-File stellt eine Fehlermeldungs- und Logging-Funktion
 * per set_error_handler() ein.
 * Dadurch wird bei Fehlern der Call-Stack in das error_log geschrieben
 *
 */


if(!function_exists("printDebugBacktrace3")) {

  /**
   * Das Haupt-Konfigurations-Include-File.
   */
//  @include_once "config.inc.php";

  /** Funktion, die von PHP selbst aufgerufen wird, wenn ein Fehler
   *  auftritt (da sie mit set_error_handler() als Error-Handler
   *  eingestellt wird).
   */
  function printDebugBacktrace3($errno, $errstr, $errfile, $errline, $dbgTrace) {

    // Bei manchen Funktionen möchte ich die Args nicht mitloggen.
    // Da möchte ich dann auch bei den aufrufenden Funktionen keine Args mitloggen.
    $dont_log_args_array = array( // alles hier klein schreiben!
                                 "ocilogon","oci_connect",
                                 "ocinlogon","oci_new_connect",
                                 "ociplogon","oci_pconnect",
                                 "ftp_login", 
                                 "ssh2_auth_password", 
                                 "ssh2_auth_pubkey_file",
                                 "cocouser::authenticate");

    // Bei manchen Funktionen sollen Arrays komplett ausgegeben werden.
    $expand_arrays_array = array( // alles hier klein schreiben!
                                 "db::parsebindexecute",
                                 );

    if(php_sapi_name() == "cli")
      $NL = PHP_EOL;
    else
      $NL = "
\n"; $dbgTrace = debug_backtrace(); $msg = ErrorHandler::$errorType[$errno].": ".$errstr." in ".$errfile." on line ".$errline."."; $short_msg = $msg . $NL; $NL = PHP_EOL; // nur $short_msg wird angezeigt, der Rest geht ins error_log, // da möchte ich kein
sehen $msg .= $NL." calling stack:".$NL; foreach($dbgTrace as $dbgIndex => $dbgInfo) { if ($dbgInfo["file"]==__FILE__) continue; if($dbgInfo["function"] == 'handleError') continue; if(isset($dbgInfo["class"])) $class_info = $dbgInfo["class"] . "::"; else $class_info = ""; $dbgInfo["function"] = $class_info . $dbgInfo["function"]; if(!isset($dont_log_args) || !$dont_log_args) $dont_log_args = in_array(strtolower($dbgInfo["function"]), $dont_log_args_array); $args = ''; if(is_array($dbgInfo["args"]) && !$dont_log_args ) { foreach($dbgInfo["args"] as $arg) { if(is_object($arg)) $args .= ', ' . '(object)'; elseif(is_array($arg) && in_array(strtolower($dbgInfo["function"]), $expand_arrays_array)) $args .= ', ' . print_r($arg, true); elseif(is_array($arg)) $args .= ', (array)'; else $args .= ', ' . $arg; } $args = substr($args,1); if(strlen($args) > 1000) $args = "(".$args."...) [truncated, too long]"; else $args = "(".$args.")"; } $msg .= " ".$dbgInfo["file"].", line ".$dbgInfo["line"].", ". $dbgInfo["function"].$args.$NL; } if(ini_get("log_errors")) { $msg = ( php_sapi_name() == "cli"? $_SERVER["SCRIPT_FILENAME"] : $_SERVER["REQUEST_URI"] ) . ( isset($_SERVER["HTTP_REFERER"])? (" referer: ".$_SERVER["HTTP_REFERER"]) : "" ) . $NL . $msg; error_log($msg, 0); } if(ini_get("display_errors")) { if(php_sapi_name() == "cli") echo "\n"; else if(!count(ob_list_handlers())) // nicht, wenn ich nach ob_start() aufgerufen wurde echo "
\n"; echo ini_get("error_prepend_string"); echo $short_msg; echo " The calling stack can be seen in the log file ", ini_get("error_log")," .",$NL; echo ini_get("error_append_string"); if((php_sapi_name() != "cli") && !count(ob_list_handlers())) echo "
\n"; } } } // if(!function_exists("printDebugBacktrace")) ?>