Debugging-Hilfe, die auch zur Laufzeit im System bleibt

Hi all,
ich habe mir mal ein paar Stunden Zeit genommen, eine etwas umfangreichere Debugging-Methode, di ich schon in einigenanderen Sytsemen benutzt habe, in IPS (oder besser: PHP) umzusetzen. Bin in PHP noch blutiger Anfänger, insofern wird’s bestimmt mal noch Verbesserungen geben.

Was soll das Ding tun?

Es soll die typischen „echo“ Befehle, die man beim Testen und Programmieren in Scripte ein- und wieder aus- und wieder ein- und wieder ausbaut, um sie dann bei Änderungen nach einer Woche oder so wieder ein- und auszubauen, ersetzen.

Die Idee ist simpel: Ich baue Meldungen in den Code ein, schalte die aber zur Laufzeit aus (nein: ich schalte sie zur Programmier/Testzeit ein).

Ich habe oft den Wunsch, „zu tracken“, wo mein Script gerade is, also die Zeile, um zu wissen, ob eine bestimmte „if“ schon durch ist oder nicht. Dazu gebe ich dann oft per echo aus: „jetzt dieses“ … „vor xyz“ … „nach xyz“ usw. Ist ne Sissyphusarbeit und nicht selten bleibt ein echo drin …

Seit heute mache ich das so, dass ich eine Zeile Code schreibe:

Debug( „ein Text“);

oder

Debug( „ein Text“, Wichtigkeit)

wobei ich die Wichtigkeit als Zahl ausdrücke - je kleiner die Zahl, desto wichtiger die Meldung.

Will ich später mal "tracken, was mein Script macht oder wo evtl. ein Fehler ist (es fehlt ne Variable, ich habe eine Kategorie umbenannt etc.), dann setze ich einfach eine Variable in meinem Objektbaum auf 1, 5 10, oder 1000, und erhalte im Meldungsfenster brav alle Infos abgeliefert. Von Makro bis Minutiös.

Hier ist der Code und die Doku dazu. Hoffe dass das alles einigermassen verständlich ist (ich habe ne eigene Datenbank hier, wo die Doku parallel zum Code ist und farbliche Gestaltung ermöglicht etc. da kommt das besser).



/* *************************************************************************
	function Debug()
	throws message to log depending on the DebugLevel value either set locally
	(calling script) or globally (variable in object tree)
	
	Version 2.0 // 2010-06-10
	Author: jw
	
	see separate documentation
**************************************************************************** */

// TEST VALUES FOR TESTING THE FUNCTION

//$DebugLevel = 5; // this is a local usage which is NOT recommended for wild life

//Debug();
//Debug("Message from level 1 and above");
//Debug("This message will occur from DebugLevel 5 and above", 5);
//Debug("Here with Codeline and from DebugLevel 100", 100, __LINE__);
//Debug("Message from Level 5 WITHOUT Codeline but special sender-ID", 5, 0, "Sender is Scriptcode");
//Debug("Message from Level 5 WITHOUT Codeline but scriptname as sender-ID", 5, 0, IPS_GetName($IPS_SELF);
//Debug("Message from Level 5 WITH Codeline but scriptname as sender-ID", 5, __LINE__, IPS_GetName($IPS_SELF);
//Debug("Message suppotrting [MyDebug] as an entry to the tree", 5, __LINE__, ".default.", "MyDebug:0");
//Debug(".value.", 0, 0, "", "DebuggingVariable:0"); // THIS MESSAGE READS THE ACTUAL DEBUG LEVEL


function Debug( $Msg=".default.", $Level=1, $CodeLine=0, $MsgSender=".default.", $DebugLevelObject="DebugLevel:0" ){
	// Version 2.0
	global $DebugLevel;
	if ( $Msg == ".value." ):
		$Level = 0;
		$Msg=".default.";
	endif;
	if (!@is_numeric( $DebugLevel )): // DebugLevel not set in calling script
	   $DebugLevelObjectName = @strtok($DebugLevelObject, ":"); //name of variable used
	   $DebugLevelObjectParentID = @intval(strtok(":")); // if given, use different tree structure (default is 0)
	   if ( $DebugLevelObjectName == ""): //empty parameter supplied
	      $DebugLevelObjectName = "DebugLevel";
	   endif;
   	$id = @IPS_GetObjectIDByName( $DebugLevelObjectName, $DebugLevelObjectParentID  );
  		if ($id === false):
			$DebugLevel = 0;
		else:
			$DebugLevel = floatval(GetValue( $id));
		endif;
	endif;
	if ( $DebugLevel < $Level ):
		// nothing to do because debug level set is lower than level to throw message
	   return;
	endif;
	$MsgCodeLine = "";
	if ($CodeLine > 0):
		// write line of code of calling script into message
	   $MsgCodeLine = " [L: " . $CodeLine . "]";
	endif;
	if ( $MsgSender == ".default." || $MsgSender == ""):
		// default sender info
		$MsgSender = "*** DEBUG *** " . $MsgCodeLine;
	else:
	   // special sender defined by caller
		$MsgSender = $MsgSender . $MsgCodeLine;
	endif;
	if ( $Msg == ".default." ):
	   // default message ==> publish actual debug level
	   $Msg = "DebugLevel = " . $DebugLevel;
	endif;
	IPS_LogMessage( $MsgSender , $Msg);
};



// EXTREMELY COMPRESSED VERSION OF Debug()
function DebugC( $M=".default.", $L=1, $C=0, $S=".default.", $D="DebugLevel:0" ){
	// Version 2.0.c
	global $DebugLevel;
	if ( $M == ".value." ){	$L=0;
		$M = ".default.";};
	if (!@is_numeric( $DebugLevel )){
	   $DN = @strtok($D, ":");
   	$DI = @intval(strtok(":"));
	   if ($DN == "") $DN = "DebugLevel";
	   $i = @IPS_GetObjectIDByName( $DN, $DI  );
		if ($i === false){ $DebugLevel = 0;
			}else $DebugLevel = floatval(GetValue( $i));
	};
	if ( $DebugLevel < $L ) return;
	$CO = "";
	if ($C > 0) $CO = " [L: " . $C . "]";
	if ( $S == ".default." || $S == "" ) $SS = "*** debug *** " . $CO;
	else 	$SS = $S . $CO;
	if ( $M == ".default." ) $M = "DebugLevel = " . $DebugLevel;
	IPS_LogMessage( $SS , $M);
};


/* ########################### END OF SCRIPT - DOCUMENTATION ##################

The Debug() and DebugC() (c=compressed version) supply a tiny tool for programmers
to include lines of code throwing debugging-infos into the log WITHOUT (!) the need
of removing those code lines after coding is finished.

More than this: at any time, even without touching the code, you can "switch"
the messages on and off.

And, even more: You can have different granularity of messages being produced
depending on the value yo assign to a "Debugging variable".

Therefor, even on a perfectly running system, you can easily switch on and off
runtime messages with information about what, why, when without accessing the code itself.


**** DOES IT CONSUME RESSOURCES? ****************************
The called function is simply returned with no result and almost no time
consumption (approx. 3.5 ms per run) if the according DebugLevel is set to zero.

Even when producing an output, the time of IPS_LogMessage is the by far most
"expensive" function used, but only IF a message is produced (i.e. the DebugLevel
at runtime is high).


**** FUNCTIONALITY & USAGE: *********************************
All you have to do is:

1.) Incorporate the function into your code
recommendadtion: use it by include)

2.) Write one of the following lines (see more examples at the end) into your code:

3.) Supply a DebugLevel value (read on for a few seconds to see, how)



**** CODE IN YOUR SCRIPT ***********************************

The PARAMETERS are ALL optional, so the simplest usage is

	Debug()
	
However, to get a specific result as an entry, you will rather use lines like:


	Debug( "this is breakpoint number X")

	Debug( "here we are at codeline", 1000, __LINE__)
	
	Debug( "here we are at codeline", 1000, __LINE__ , "Scriptname=SwitchAllOn")


See an in depth discussion about the usage at the end of this documentation.

REMARK: If you have not set up anything, you will see NO result.
There is one other step, step 3 as of the said above


**** HOW TO GET THE MESSAGE OUTPUT TO THE LOG: ***************
The debug function only produces any output to the logfile, if a value called

	$DebugLevel
	
has a certain value OR a variable in the object tree has the according value.

You can set this by either:

	- 	put $DebugLevel = <value> locally to your script (NOT RECOMMENDED because
		you need to modify your script each time you will see or hide the messages)

	-	having a "DebugLevel" variable of numeric type as a child to the
		main object( IPSymcon)
		(RECOMMENDED because: this allows to switch the functionality conveniently
		on and off through a variety of actions like in the tree, modify the value
		and also, potentially code used from others will also support this with no changes]
		
	-	having ANY numeric variable SOMEWHERE in the object tree AND (!!) supplying
		the name and parent object ID of that variable as a parameter to Debug
		function (see below)


**** IN DEPTH DESCRIPTION OF PARAMETERS: ************************

to the logfile, you might use parameters to steer the information returned.
CALL:

Debug( [string] Messagetext, [integer] DebugLevelThresold, [integer] Codeline, [string] Sender Name, [string] DebugLevelVariableObject)

PARAMETERS:
[string] Messagetext:
	The Message you want to be shown in the log entry
	if this parameter is ".default.", the actual set DebugLevel (if not 0) is the output
	
[integer] DebugLevelThresold
	This is the valus that will make the message visible or invisible.
	It allows for a fine granulated debugging method.
	RECOMMENDADTION:
	Use high values (i.e. 1000) for most critical messages that are supposed
	to never appear during the regular runs of your code while using lower values
	that will adddress topics that will more likely appear after your coding is
	finished i.e. access to objects taht might dissapear or value limits
	
	IF YOU OMIT this parameter, the function will produce the output at any value
	of DebugLevel from 1.0 and higher.
	
	This parameter needs to be used if the parameters following will be used!
	The number "1" is the "default" and will make sure that the message is being
	thrown on any DebugLevel from 1.0 and higher.
	
	
[integer] Codeline
	This will show the value purged to the function, appended to the sender info.
	Use the magic constant "__LINE__" of PHP to supply the actual codeline of
	the script debugged to the debug function. This allows a very fast identification
	of the lines that might produced problems, especially some weeks/months/years
	after coding.

	IF YOU OMIT this parameter, the informaion is simply not produced.

	This parameter needs to be used if the parameters following will be used!
	Use the number "0" for not using the feature.


[string] Sender Name
	This will be the value set into the "Sender" column of the log file.
	RECOMMENDATION
		Use the Scriptname and/or ID if you do not want "*** DEBUG ***" to be the
		output in the log file.

		Of course, as with IPS_LogMessage, you can supply any other string value as well.
		Be advised that the current version of IPS does not support "" (empty string),
		so, this value will be converted to "*** DEBUG ***"!

		If you omit ("") the parameter, "*** DEBUG ***" or "***debug***" (lower
		case) is used so you see that the message is a debugging value and not a
		regular output.


[string] DebugLevelVariableObject)
	IF you decide to have your own numeric variable somewhere in your IPS tree
	and dislike the place to be right below the "0" object, you can do so.
	
	Put that variable ANYWHERE you want, name it as you like.
	
	The only DISADVANTAGE in this case is, that you need to supply an additional
	parameter to the debug function, which is the very last one. And in turn,
	you need to supply all of the parameters then.
	

**** WHAT THE HACK IS DebugC()?? ***********************************
	i have written DebugC() just for fun to show how compact (and unreadable) code
	can be if the human factor would be ignored. Basically, this consums a tick
	less computation time since it is less characters to read from disk, to parse
	and to interpret at runtime.
	
	... also, with that code you can be relatively sure that no customer would
	change it - at least not functionable ...

################################# E O D #################################### */




Na wenn du noch nen „blutiger Anfänger“ bist, bin ich ne Amöbe :stuck_out_tongue:

Habe bei meinem post von gestern noch vergessen, dass man neben der „globalen Schaltung“ natürlich auch auf eine „lokale“ Schaltung je Script gehen kann.

Ich mach das so, dass ich folgende CodeZeilen in meine Script ganz am Anfang einbaue und unter dem Script eine Variable anlege, die ich dann individuell setzen kann. Der Typ sollte normalerweise Integer sein, aber da PHP ziemlich gutmütig ist, tut’s praktisch jeder Typ.



// check if local debug level is set
$DebugLevel = @GetValue(IPS_GETObjectIDByName("DebugLevel", $IPS_SELF));
if ($DebugLevel === false): // var does not exist locally --> destroy to use global one (if exist)
	unset($DebugLevel);
else:
	// convert to usable number. Note: Any crab will lead to 0 assuming no debug messages
	$DebugLevel = intval($DebugLevel);
endif;


HINWEIS:
Diese kleine Routine ist „gezielt“ etwas unsauber, um den code kurz zu halten.

Das „unset“ bei nicht ex. Variable führt dazu, dass die Debug() routine auf die globale Definition zurückgreift (sonst würde der Wert angenommen, auch wenn er 0 wäre)

jwka