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.