[IGN-7976]Path to Component Event

I am trying to make a hold your hand script for everything script errors:

I managed to get figure out how to get module/function_name/line number and was able to create a traceback path which is nice. For example:

def func(t, *args, **kwargs):
	print t
	0/0
	return

def func1(t, *args, **kwargs):
	func(t)

def func2(t, *args, **kwargs):
	func1(t)

def func3(t, *args, **kwargs):
	func2(t)

try:
	func3("test", 1, 2, 3, trial = "testing kwargs")
except:
	print trace.debug()

Yields the following output:

ZeroDivisionError @ <admin@script_console>:<module>:17 -> <admin@script_console>:func3:14 -> <admin@script_console>:func2:11 -> <admin@script_console>:func1:8 -> <admin@script_console>:func:4 ->  integer division or modulo by zero

We have used this on site track down errors in complicated business logic.

However, now I am trying to take it to the next level. One pet peeve of mine was value change scripts do not identify which tag or component they are coming from. They simply say something like function:valueChanged.

My questions from this are:

  • Which function?
  • Which component?
  • Which event?

This works with script modules, but not with components. Ideally I would want to have something like <view_path>/<component_name>/<event_name>/

I was able to solve this for tag Events, but I can’t figure it out for component events.

Here is my current code:

class debug:
	"""Creates debug object with metadata on the specific error."""
	
	error_path = None
	error_name = None
	error_message = None
	error_string = None
	
	def __init__(self):
		self.get_error_details()
		gwlog(self.error_string)
	
	def __str__(self):
		return self.error_string
		
	@classmethod
	def get_error_path(self, traceback = None, path = ""):
		if traceback is None:
			return path
		else:
			frame = traceback.tb_frame
			frame_locals = frame.f_locals
			file_name = frame.f_code.co_filename
			function_name = frame.f_code.co_name
			line_number = traceback.tb_lineno
			
			gwlog(frame_locals)
			
			if file_name == "<tagevent:valueChanged>":
				tag_path = frame_locals.get("tagPath")
				file_name = "<tagevent:valueChanged>" if tag_path is None else tag_path
				
			elif file_name == "<input>":
				file_name = "<%s@script_console>" % system.vision.getUsername()
			
			elif file_name == "<function:runAction>":
				component = frame_locals.get("self")
				if component is not None:
					component_meta = getattr(component, "meta")
					component_name = getattr(component_meta, "name")
					file_name = str(component_name)
					
					## NEED HELP IDENTIFYING THIS COMPONENT. IDK WHERE function.runAction is
					## Looking to get <view_path>/<component_name>/<event_name>/<I already have the rest from here>
			
			path +=  "%s:%s:%s -> " % (file_name, function_name, line_number)
			
			final_path = self.get_error_path(traceback.tb_next, path)
			if final_path is None:
				return
			else:
				return final_path
	
	def get_error_details(self):
		
		TYPE, MSG_OBJ, TRACEBACK = sys.exc_info()
		
		self.error_name = TYPE.__name__ if TYPE else "Unknown Error"
		self.error_message = MSG_OBJ.message if MSG_OBJ.message else MSG_OBJ
		self.error_path = self.get_error_path(TRACEBACK)
		
		self.error_string = "%s @ %s %s" % (self.error_name, self.error_path, self.error_message)

Does anyone know how to get reference to a view path or a component path from the calling component event itself? Or perhaps there is another way to absolutely define the script location of a perspective component?

Constraints: Passing a path into my class constructor is not acceptable as it is impossible to reliably force a user to “remember” to add this in.

Also, I am aware that business logic should not be written in components themselves, we avoid this as much as possible. However, things like a simple change script may not be classified as “business logic”. However, on screens with many components, especially on SCADAs with tonnes of embedded views and components with the same names, it is useful to know which one made the mistake. The goal is this: If I have 1000 buttons on the screen, I shouldn’t have to open each one to find out which one caused a traceback on function.onValueChanged.

I think you are looking for component.view.id to get the view name and you can remove

component_meta = getattr(component, "meta")
component_name = getattr(component_meta, "name")

and can just component.meta.name because you already have the reference to the component.

This will be the great script for base debug script for every project for sure. Thanks for sharing up the code bud.

Agreed but that does not mean they shouldn’t be in somewhere in your script library. I often have a gui scripting library under the appropriate package for things like change scripts that run on the front end. Worth it for a number of reasons not related to your post, but what is related s in your stack trace you should see what function was throwing the issue, so you could potentially fix the issue without even finding or caring about what specific component threw it.

Making the valueChange script like

def valueChanged(self, previousValue, currentValue, origin, missedEvents):
	test.valueChangedFunction(self, previousValue, currentValue, origin, missedEvents)

where my script is

def valueChangedFunction(self, previousValue, currentValue, origin, missedEvents):
	x = 1 / 0

Now in my logs I see

Error running property change script on TextField.props.text: Traceback (most recent call last): File "<function:valueChanged>", line 2, in valueChanged File "<module:test>", line 2, in valueChangedFunction ZeroDivisionError: integer division or modulo by zero

I don’t know what component threw it but I see exactly where I would fix it - test.valueChangedFunction and I can fix it without doing any run down on the GUI.

I understand this can have issues if one of your many 1000 components has a slight difference to the others and you need to inspect the gui anyways but I do think doing things in this style will help you avoid that in a many scenarios – in the event it is strictly a jython/java errror and not caused by a GUI input being a type mismatch or something of that nature.

I don’t have an answer to your initial query but I do think doing things in the manner I described above removes a lot of the pain points that made you go down this path in the first place.

This is basically the same request as this other post. There's an existing ticket (that I was unfortunately able to get done in 8.3.0) to add this kind of meta information, which is very easy for us to do, and very awkward for you to obtain) to scripts invoked as extension functions, via the __name__ or __file__ builtin keys.