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.