PageRenderTime 128ms CodeModel.GetById 60ms app.highlight 28ms RepoModel.GetById 32ms app.codeStats 1ms

/atk4/lib/Logger.php

https://github.com/intuititve/lostandfound
PHP | 702 lines | 480 code | 59 blank | 163 comment | 84 complexity | b30b03b12efdd2d161ebeff097c281cc MD5 | raw file
  1<?php
  2class Logger extends AbstractController {
  3	/**
  4	 * Logger class is implemented a more sophisticated and usable error handling.
  5	 *
  6	 * Normally all error messages are sent through the API class by using one of two
  7	 * ways:
  8	 *
  9	 * 1. throwing exception, which is caught by Api class (unless you catch it yourself)
 10	 * 2. calling $this->fatal() which would throw exception for you (useful when calling
 11	 *  from PHP4 compatible components)
 12	 *
 13	 * additionally there are way to pass info and warnings by calling:
 14	 *
 15	 * $this->warning();
 16	 * $this->info();
 17	 * $this->debug();
 18	 *
 19	 * Debug information will reach logs only if debug mode is set.
 20	 *
 21	 * ==[ Controling debug info ]===================================================
 22	 *
 23	 * AModules3 support two ways to set debug mode. Global and Local.
 24	 *
 25	 * Global mode is set by setting $api->debug to true. Local mode is set by setting
 26	 * particular object's $this->debug to true.
 27	 *
 28	 * In local mode only debug info generated by particular object will be sent
 29	 * through
 30	 *
 31	 * This class does not manage debug modes, which is a task for Api and all other
 32	 * objects.
 33	 *
 34	 * $config['debug']=true; // turns on global debug mode.
 35	 *
 36	 *
 37	 * ==[ Severity ]================================================================
 38	 *
 39	 * AModules3 have 4 severity types: debug, info, warning and fatal.
 40	 *
 41	 * Expected exceptions might be reported as warnings and uncaught exceptions
 42	 * are fatal. Forms have their own validation and errors on the form is
 43	 * completely separate system.
 44	 *
 45	 * Sample fatal errors:
 46	 *  - unable to execute query
 47	 *  - method not found
 48	 *  - mandatory data is not specified
 49	 * fatal errors will automatically terminate application execution.
 50	 *
 51	 * Warnings messages:
 52	 *  - duplicate child added to the object with same name
 53	 *  - method is called without specifying important argument
 54	 *
 55	 * Sample info messages:
 56	 *  - user tried to access restricted page
 57	 *  - monthly log rotation routine finished successfuly
 58	 *  - user information is automatically converted (from format used in previous version)
 59	 *  Info messages are passed as strings without extended information.
 60	 *
 61	 * Debug messages
 62	 *  - called some function which might contain errors or being debuged
 63	 *  - user logged in or logged out
 64	 *  - when debuging, even more messages might be sent as debug()
 65	 *  WARNING: please keep debug information to the minimum. Debug information is
 66	 *  accompanied with extended information. Debug information is always saved into
 67	 *  logs or is available on the screen.
 68	 *
 69	 * AModules3 core tries not to produce any debug or info messages to keep files
 70	 * clean. Even if it does, those calls might eventually be cleaned out.
 71	 *
 72	 *
 73	 * ==[ Configuring logger ]======================================================
 74	* Logger uses 2 output destinations, and 3 ways to restrict output information.
 75		*
 76		* Output destination: stdout (webpage), logs
 77		* Restrict options: 'full', 'light', null
 78		*
 79		* stdout is different for ApiCLI and ApiWeb classes. Web output might contact
 80		* tags or even some AJAX elements. Logs and ApiCLI uses the same output
 81		* format.
 82		*
 83		* Web output for info, warning and debug messages relies on templates
 84		* but fatal messages are template independent.
 85		*
 86		* ==[ Output restriction ]======================================================
 87		* null: this option will surpress all output. When used with logs, you won't
 88		* even need an empty directory. Web output will be clean of any messages.
 89		*
 90		* If fatal error occurs, if 'null' is used with web output, you will see
 91		* message (public_error_message), instead of the actual error.
 92		*
 93		*
 94		* light: only message is outputed. Even if debug mode is on, backtraces and
 95		* additional information is stripped off. This method is best if you are using
 96		* application for intranet and know uses or if you are doing beta testing.
 97		* This output won't contain any sensitive information such as table names, field
 98		* names, actual data)
 99		*
100		* when used with logs, each message takes one line.
101		*
102		* full: this will output all the information available including:
103		*  error message
104		*  line/file/function where error message occured (guessed)
105		*  additional information (such as last_query and error_message from DBlite)
106		*  complete backtrace.
107		*
108		* when used with logs, each message takes several lines.
109		*
110		* ==[ Activation ]==================================================================
111		* To start using this class in your applicaion you should:
112			*
113				*  $api->add('Logger');
114	*
115		* If you do not activate Logger, output will be similar to:
116		* web_output='full';
117	* log_output=null;
118	*
119		* ==[ Extending ]====================================================================
120		*
121		* You can extend this class to add additional features. Please notify me if you think
122		* something essential is missing out
123		*
124		* romans@adevel.com
125		*
126		* Debug functions were contributed my mvs@adevel.com
127		*/
128
129		// AModules3 compatibility
130		public $owner;
131	public $api;
132
133	// Configuration;
134	public $web_output='full';      // $config['logger']['web_output']
135	public $log_output=null;        // $config['logger']['log_output']
136
137	public $public_error_message=null;
138	// This message will be outputed to user in case of
139	// fatal error. When running in production mode, you
140	// shouldn't show any debug info to user, but log them
141	// instead
142
143	public $log_dir;                // Directory where logs are created. It should be
144	// used solely by AModules3. If not set, then
145	// /var/log/atk4/<realm> will be used.
146	//
147	// You can change in $config['logger']['log_dir']
148
149	protected $log_error_file;        // File we are currently logging errors to
150	protected $log_debug_file;        // File we are currently logging errors to
151	protected $log_info_file;         // File we are currently logging errors to
152	public $details=array();
153
154
155	private $html_stdout=false;
156
157	private $header_sent=0;
158
159	private $debug_log='';          // Will be outputed at the end of the page
160	private $debug_added=false;     // no debug messages added yet
161
162
163	function init(){
164		parent::init();
165		$this->debug_log=session_id()?$this->recall('debug_log',''):'';
166		if(session_id())$this->forget('debug_log');
167		$this->debug_log.="[<font color=red>Debug log from ".date("d.m.Y H:m:s")." to ".$_SERVER['QUERY_STRING']."</font>] - debug started<br>\n";
168		$this->debug_added=false;
169
170		register_shutdown_function(array($this,'showDebugInfo'));
171
172
173		$this->log_output=$this->api->getConfig('logger/log_output',null);
174		$this->web_output=$this->api->getConfig('logger/web_output','full');
175
176		if(!$this->web_output){
177			$this->public_error_message=$this->api
178				->getConfig('debug_public_error_message',
179						'We are currently having some technical difficulties. '.
180						'Please retry later.');
181		}
182
183		if($this->log_output){
184			$this->log_dir=$this->api->getConfig('logger/log_dir',
185					"/var/log/atk4/".$this->api->name);
186			$this->openLogFile('error');
187			$this->openLogFile('debug');
188			$this->openLogFile('info');
189			if(rand(1,50)==1)$this->cleanupLogDirectory();
190		}
191
192		if($this->log_output=='full'||$this->web_output=='full'){
193			// Full logging will require some preparations
194			$this->gatherDetails();
195		}
196
197		if($this->api instanceof ApiWeb){
198			$this->html_stdout=true;
199		}
200
201
202		$this->api->addHook('caught-exception',array($this,'caughtException'));
203		$this->api->addHook('output-fatal',array($this,'outputFatal'));
204		$this->api->addHook('output-warning',array($this,'outputWarning'));
205		$this->api->addHook('output-info',array($this,'outputInfo'));
206		$this->api->addHook('output-debug',array($this,'outputDebug'));
207
208		$this->api->debug('Logger is initialized');
209	}
210	function showDebugInfo(){
211		if(!$this->debug_added)return;
212		if(@$this->api->not_html){
213			// We may not output anything, because this will screw up. Save debug output to session
214			if(session_id())$this->memorize('debug_log',$this->debug_log);
215		}else{
216			echo $this->debug_log;
217		}
218	}
219	function gatherDetails(){
220		// Get IP address
221		if (isset($_SERVER['REMOTE_ADDR'])) {
222			//FIXME: generates warning - array_shift wants variable
223			//$this->details['IP Address']=(isset($_SERVER["HTTP_X_FORWARDED_FOR"]) ? array_shift(explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"])) : $_SERVER["REMOTE_ADDR"]);
224		}
225
226		if (isset($_SERVER['QUERY_STRING'])) {
227			$this->details['Query String']=$_SERVER['QUERY_STRING'];
228		}
229		if (isset($_SERVER['REDIRECT_SCRIPT_URI'])) {
230			$this->details['Original Query']=$_SERVER['REDIRECT_SCRIPT_URI'];
231		}
232		if (isset($_SERVER['HTTP_REFERER'])) {
233			$this->details['Referer']=$_SERVER['HTTP_REFERER'];
234		}
235		if (isset($_SERVER['HTTP_USER_AGENT'])) {
236			$this->details['Version']=$_SERVER['HTTP_USER_AGENT'];
237		}
238		if (isset($_SERVER['SERVER_PORT'])) {
239			$this->details['Port']=$_SERVER['SERVER_PORT'];
240		}
241	}
242
243	function findFrame($function_name,$shift=0){
244		$backtrace=debug_backtrace();
245		while($bt=array_shift($backtrace)){
246			if($bt['function']==$function_name){
247				while($shift--)$bt=array_shift($backtrace);
248				return $bt;
249			}
250		}
251		return array();
252	}
253    public $recskip=array();
254    function showRenderTree($e,$obj){
255        if(in_array($obj->name,$this->recskip)){
256            echo '..recursion('.$obj.')';
257            return;
258        };
259        $this->recskip[]=$obj;
260        if($e->owner==$obj || $e->owner->owner == $obj || $e->owner->owner->owner == $obj){
261            echo '<font color="red">'.$obj->__toString()."</font>";
262        }else echo $obj->__toString();
263        if($obj->elements){
264            echo '<ul>';
265            foreach($obj->elements as $name=>$object){
266                echo '<li>'.$name.': ';
267                $this->showRenderTree($e,$object);
268                echo '</li>';
269            }
270            echo '</ul>';
271        }
272    }
273    function logCaughtException($e){
274			if(method_exists($e,'getMyTrace'))$trace=$e->getMyTrace();
275			else $trace=$e->getTrace();
276
277			$frame=$e->my_backtrace[$e->shift];
278			$this->logLine($this->txtLine(get_class($e).": ".$e->getMessage(),$frame),2,'error',$trace);
279            if(method_exists($e,'getAdditionalMessage'))
280                $this->logLine($e->getAdditionalMessage());
281    }
282	function caughtException($caller,$e){
283		$e->shift-=1;
284		if($this->log_output){
285			//$frame=$this->findFrame('warning',$shift);
286            $this->logCaughtException($e);
287		}
288		if(!$this->web_output){
289			echo $this->public_error_message;
290			exit;
291		}
292        if($_GET[$this->name.'_debug']=='rendertree'){
293            echo '<h2>Object Tree</h2>';
294            try{
295                $this->showRenderTree($e,$this->api);
296            }catch(Exception $e){
297                echo '<h1>Exception while trying to render tree:</h1>';
298                //unset($_GET[$htis->name.'_debug']);
299                //$this->api->caughtException($e);
300            }
301        }
302
303
304		echo "<h2>".get_class($e)."</h2>\n";
305		echo '<p><font color=red>' . $e->getMessage() . '</font></p>';
306		if(method_exists($e,'getAdditionalMessage'))echo '<p><font color=red>' . $e->getAdditionalMessage() . '</font></p>';
307		if($e->more_info){
308			echo '<p>Additional information: <ul>';
309			foreach($e->more_info as $key=>$info){
310				if(is_array($info))$info=print_r($info,true);
311				echo '<li>'.$key.': '.$info.'</li>';
312			}
313			echo '</ul></p>';
314		}
315		if($e->actions){
316			echo '<p>Possible Actions: <ul>';
317			foreach($e->actions as $key=>$val){
318				echo '<li><a href="'.$this->api->getDestinationURL(null,$val).'">'.$key.'</a></li>';
319			}
320			echo '</ul></p>';
321        }
322		if(method_exists($e,'getMyFile'))echo '<p><font color=blue>' . $e->getMyFile() . ':' . $e->getMyLine() . '</font></p>';
323
324		if(method_exists($e,'getMyTrace'))echo $this->backtrace($e->shift,$e->getMyTrace());
325		else echo $this->backtrace($e->shift,$e->getTrace());
326
327		exit;
328	}
329	function outputWarning($caller,$msg,$shift=0){
330		// first, let's see if we should log this
331		$frame=$this->findFrame('warning',$shift);
332		if($this->log_output){
333			$this->logLine($this->txtLine("warning: $msg",$frame),'warning','error');
334		}
335		if(!$this->web_output){
336			return true;
337		}else{
338			echo $this->html_stdout?
339				$this->htmlLine("$msg",$frame,'warning'):
340				$this->txtLine("$msg",$frame,'warning');
341			return true;
342		}
343	}
344	function outputDebug($caller,$msg,$shift=0){
345		// first, let's see if we should log this
346		$frame=$this->findFrame('debug');
347		if($this->log_output){
348			$this->logLine($this->txtLine("info: $msg",$frame),'fatal','debug');
349		}
350		if(!$this->web_output){
351			return true;
352		}else{
353			$this->debug_added=true;
354			$this->debug_log.=$this->html_stdout?
355				$this->htmlLine("$msg",$frame,'debug'):
356				$this->txtLine("$msg",$frame,'debug');
357			return true;
358		}
359	}
360	function outputInfo($caller,$msg,$shift=0,$nohtml=false){
361		if($this->log_output){
362			$this->logLine($this->txtLine("info: $msg"),null,'info');
363		}
364		if($this->web_output && !$nohtml){
365			echo $this->html_stdout?
366				$this->htmlLine("$msg",null,'info'):
367				$this->txtLine("$msg",null,'info');
368		}
369		return true;
370	}
371	function outputFatal($caller,$msg,$shift=0){
372		// first, let's see if we should log this
373		$frame=$this->findFrame('fatal');
374		if($this->log_output){
375			$this->logLine($this->txtLine("fatal: $msg",$frame),'fatal','error');
376		}
377		if(!$this->web_output){
378			echo $this->public_error_message;
379		}else{
380			if($this->html_stdout){
381				echo "<h2>Fatal error</h2>\n";
382			}
383			echo $this->html_stdout?
384				$this->htmlLine("$msg",$frame,'fatal'):
385				$this->txtLine("$msg",$frame,'fatal');
386
387			if($this->html_stdout){
388				echo $this->backtrace('fatal');
389			}else{
390				echo "Stack trace:\n";
391				echo $this->txtBacktrace('fatal');
392			}
393
394		}
395		exit;
396	}
397	function htmlLine($msg,$frame=array(),$prefix=null){
398		if(!$frame){
399			return "<font style='font-family: verdana;  font-size:10px'><font color=blue>warning: </font> <font color=red><b>$msg</b></font></font><br>";
400		}else{
401			$errfile=dirname($frame['file']).'/<b>'.basename($frame['file']).'</b>';
402			return "<font style='font-family: verdana;  font-size:10px'><font color=blue>$errfile:".$frame['line']."</font> <font color=red>".($prefix?"$prefix: ":"")."<b>$msg</b></font></font><br>";
403		}
404	}
405	function txtLine($msg,$frame=array(),$prefix=null){
406		if(!$frame){
407			return "$prefix: $msg\n";
408		}else{
409			return basename($frame['file'])." on line ".$frame['line'].', path: '.dirname($frame['file'])."\n\n".
410				($prefix?"$prefix: ":"")."$msg\n\n";
411		}
412	}
413	function logLine($msg,$shiftfunc=null,$severity='info',$trace=null){
414		$log_file='log_'.$severity.'_file';
415        if(!isset($this->$log_file))$this->openLogFile($severity);
416		if($this->log_output==='full' && $severity=='error'){
417			if(!$this->header_sent++){
418				fputs($this->$log_file,"\n\n".
419						"============================================================\n".
420						"$msg".
421						"------------------------------------------------------------\n".
422						"Date: ".date("d-M-Y H:i:s")."\n");
423				foreach($this->details as $key=>$val){
424					fputs($this->$log_file,"$key: $val\n");
425				}
426				fputs($this->$log_file,
427						"------------------------------------------------------------\n".
428						" Stack trace\n".
429						$this->txtBacktrace($shiftfunc,$trace).
430						"\n"
431				     );
432			}else{
433				fputs($this->$log_file,$msg);
434			}
435		}elseif($this->log_output){
436			fputs($this->$log_file,"[".date("d-M-Y H:i:s")."] $msg");
437		}else{
438			return;
439		}
440		fflush($this->$log_file);
441	}
442	function logVar($var,$msg="",$shiftfunc=null,$severity='debug'){
443		//creating an $msg from variable
444		$msg.="(".gettype($var)."):";
445		if(is_array($var)||is_object($var))$msg .= print_r($var, true);
446		else$msg .= $var;
447		$this->logLine($msg."\n", $shiftfunc,$severity);
448	}
449	function logException($e){
450		// logs exception from the catch statement
451		// contains code from Logger::caughtException(), as this code won't launch
452		// if exception is caught
453		$frame=$e->my_backtrace[$e->shift-1];
454		$this->logLine($this->txtLine(get_class($e).": (".$e->getCode().") ".$e->getMessage(),$frame),2,'error');
455		return $this;
456	}
457	function openLogFile($severity='error'){
458		if(!is_dir($this->log_dir)){
459			// Directory is not writable, let's first try to create it
460			if(!mkdir($this->log_dir,0750)){
461				throw new BaseException("Unable to create $this->log_dir for log output");
462			}
463		}
464
465		$filename='am3_'.$severity.'_log';
466		$full_filename=$this->log_dir.DIRECTORY_SEPARATOR.$filename;
467		if(!is_writable($full_filename) && !is_writable($this->log_dir))throw new BaseException("Log file is not writable and seems I won't be able to create it: $full_filename");
468		if(is_link($full_filename))throw new BaseException("Log file is a symlink. Are you trying to make me overwrite somethingn?");
469
470
471		ini_set($severity.'_log',$full_filename);
472
473		//$full_filename=tempnam($this->log_dir,$filename);
474		$new_file = (file_exists($full_filename))?false:true;
475		$log_file="log_$severity"."_file";
476		$this->$log_file=fopen($full_filename,"a");
477		if(!$this->$log_file)throw new IOException("Cannot open $severity log file");
478		if ($new_file) chmod($full_filename,0777); //
479	}
480	function writeLogMessage(){
481	}
482	function backtrace($sh=null,$backtrace=null){
483		$output = "<div >\n";
484		$output .= "<b>Stack trace:</b><br /><table style='border: 1px solid black; padding: 3px; text-align: left; font-family: verdana; font-size: 10px' width=100% cellspacing=0 cellpadding=0 border=0>\n";
485		if(!isset($backtrace)) $backtrace=debug_backtrace();
486
487		$n=0;
488		foreach($backtrace as $bt){
489			$n++;
490			$args = '';
491			if(!isset($bt['args']))continue;
492			foreach($bt['args'] as $a){
493				if(!empty($args)){
494					$args .= ', ';
495				}
496				switch (gettype($a)) {
497					case 'integer':
498					case 'double':
499						$args .= $a;
500						break;
501					case 'string':
502						$a = htmlspecialchars(substr($a, 0, 128)).((strlen($a) > 128) ? '...' : '');
503						$args .= "\"$a\"";
504						break;
505					case 'array':
506						$args .= "Array(".count($a).")";
507						break;
508					case 'object':
509						$args .= "Object(".get_class($a).")";
510						break;
511					case 'resource':
512						$args .= "Resource(".strstr($a, '#').")";
513						break;
514					case 'boolean':
515						$args .= $a ? 'True' : 'False';
516						break;
517					case 'NULL':
518						$args .= 'Null';
519						break;
520					default:
521						$args .= 'Unknown';
522				}
523			}
524
525			if(($sh==null && strpos($bt['file'],'/atk4/lib/')===false) || (!is_int($sh) && $bt['function']==$sh)){
526				$sh=$n;
527			}
528
529			$output .= "<tr><td valign=top align=right><font color=".($sh==$n?'red':'blue').">".dirname($bt['file'])."/".
530				"<b>".basename($bt['file'])."</b></font></td>";
531			$output .= "<td valign=top nowrap><font color=".($sh==$n?'red':'blue').">:{$bt['line']}</font>&nbsp;</td>";
532			$name=(!isset($bt['object']->name))?get_class($bt['object']):$bt['object']->name;
533			if($bt['object'])$output .= "<td>".$name."</td>";else $output.="<td></td>";
534			$output .= "<td valign=top><font color=".($sh==$n?'red':'green').">".get_class($bt['object'])."{$bt['type']}<b>{$bt['function']}</b>($args)</font></td></tr>\n";
535		}
536		$output .= "</table></div>\n";
537		return $output;
538	}
539	function cleanupLogDirectory(){
540		// we should try to take care of our own log file cleanup
541	}
542	function txtBacktrace($sh=null,$backtrace=null){
543		if(!isset($backtrace)) $backtrace=debug_backtrace();
544		$output='';
545		$n=0;
546		foreach($backtrace as $bt){
547			$n++;
548			$args = '';
549			if(!isset($bt['args']))$bt['args']=array();
550			foreach($bt['args'] as $a){
551				if(!empty($args)){
552					$args .= ', ';
553				}
554				switch (gettype($a)) {
555					case 'integer':
556					case 'double':
557						$args .= $a;
558						break;
559					case 'string':
560						$a = (substr($a, 0, 128)).((strlen($a) > 128) ? '...' : '');
561						$args .= "\"$a\"";
562						break;
563					case 'array':
564						$args .= "Array(".count($a).")";
565						break;
566					case 'object':
567						$args .= "Object(".get_class($a).")";
568						break;
569					case 'resource':
570						$args .= "Resource(".strstr($a, '#').")";
571						break;
572					case 'boolean':
573						$args .= $a ? 'True' : 'False';
574						break;
575					case 'NULL':
576						$args .= 'Null';
577						break;
578					default:
579						$args .= 'Unknown';
580				}
581			}
582
583			if($sh){
584				if(is_int($sh)){
585					if($sh>0){
586						$sh--;
587						continue;
588					}
589				}elseif($bt['function']!=$sh){
590					$sh=null;
591					continue;
592				}
593			}
594
595			$output .= $bt['file'].":".$bt['line']." ";
596			$output .= "{$bt['class']}{$bt['type']}{$bt['function']}($args)\n";
597		}
598		return $output;
599	}
600
601	/*
602	// Debug functions
603	var $filename;
604
605	var $err_message;
606
607	var $_current_ip;
608
609	var $_prev_exec_time;
610	 */
611
612	function Debug($filename) {
613		if (is_null($filename))
614			$filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'debug.log';
615
616		$this->filename = $filename;
617
618		if (isset($_SERVER['REMOTE_ADDR'])) {
619			$this->_current_ip = (isset($_SERVER["HTTP_X_FORWARDED_FOR"]) ? array_shift(explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"])) : $_SERVER["REMOTE_ADDR"]);
620		}
621	}
622
623	function _sec2time($sec) {
624		$res = '';
625		if ($sec<0) {
626			$sec = -$sec;
627			$res = '-'.$res;
628		}
629
630		if ($sec!=floor($sec)) {
631			$msec = round(($sec - floor($sec))*1000);
632
633			$msec = '.'.str_pad($msec,3,'0', STR_PAD_LEFT);
634			$sec = floor($sec);
635		}
636
637		$hours = floor($sec/3600);
638		$min = floor(($sec - $hours*3600)/60);
639		$sec  = $sec - $hours*3600 - $min*60;
640
641		if ($hours > 0)
642			$res .= str_pad($hours,2,'0', STR_PAD_LEFT).':';
643
644		if (($hours > 0) or ($min > 0))
645			$res .= str_pad($min,2,'0', STR_PAD_LEFT).':';
646
647		$res .= str_pad($sec,2,'0', STR_PAD_LEFT).$msec;
648
649		return $res;
650	}
651
652	function _microtime_float() {
653		list($usec, $sec) = explode(" ", microtime());
654		return ((float)$usec + (float)$sec);
655	}
656
657	// print
658	function p($message, $file = null, $line = null) {
659		$res = true;
660
661		$time_diff_str = '';
662		if (!empty($this->_prev_exec_time)) {
663			$time_diff = $this->_microtime_float() - $this->_prev_exec_time;
664			if ($time_diff < 1) $time_diff_str =  $this->_sec2time($time_diff);
665		}
666
667		$details = ((empty($this->_current_ip))?'':$this->_current_ip.' - ').
668			((!empty($file))?basename($file).' (line '.$line.')':'');
669
670		if (!empty($details)) $details = ' ***** '.$details.' *****';
671
672		$message = '['.date('d-M-Y H:i:s') . '] '.$time_diff_str.$details.
673			"\n\n". $message . "\n\n";
674
675		$new_file = (file_exists($this->filename))?false:true;
676		$fh = @fopen($this->filename,'a');
677
678		if (($fh !== false) and (is_resource($fh))) {
679			@flock($fh, LOCK_EX);
680
681			if (!@fwrite($fh, $message)) {
682				$this->err_message = "Cannot write to file ($this->filename)";
683				error_log($this->err_message.' in '.__FILE__.' on line '.__LINE__,0);
684				$res = false;
685			}
686			@flock($fh, LOCK_UN);
687			@fclose($fh);
688
689			if ($new_file) chmod($this->filename,0777);
690		}
691		else {
692			$this->err_message = 'Cannot open file ('.$this->filename.')'.
693				' in '.__FILE__.' on line '.__LINE__.' for save message: '."\n".$message;
694			error_log($this->err_message,0);
695			$res = false;
696		}
697
698		$this->_prev_exec_time = $this->_microtime_float();
699
700		return $res;
701	}
702}