In the process of converting a lot of spaghetti code to organized script modules and multiple functions. One drawback I’ve noticed to this is that sometimes the error messages are truncated and not as helpful or specific as if the script was on the button onAction script. I will just get errors that show a Key I tried access in a dictionary that doesn’t exist, or a type error, but no line number or even what function this occured in. I am trying to mediate that.
What I am looking for is some function I can call in the beginning of other functions so that it will print out the current function and what function called it if possible.
I tried doing it with using the inspect module per this and doing this in a function called from another function
import inspect
print "running " + str(inspect.stack()[1][3]) + " called by " + str(inspect.stack()[2][3])
which just throws me a error like so
I assume this has to do with the odd runtime environment Ignition is, java calling python scripts etc and so I’m not sure if there is a python stack to really access like there would be in a traditional all python application.
Is there another way to do this? I would really like a function I could call as the first line of other functions that will just print “Running function (function name), called by (function name)”
#------------
# Java wrapper for Jython exceptions to preserve the python
# stack trace.
#
class PythonAsJavaException(java.lang.Exception):
@staticmethod
def fullClassName(cls):
if cls.__bases__:
return fullClassName(cls.__bases__[0])+"."+cls.__name__
return cls.__name__
def __init__(self, pyexc, tb=None):
super(PythonAsJavaException, self).__init__(str(pyexc), None, True, True)
traceElements = []
if tb is None:
tb = sys.exc_info()[2]
while tb:
code = tb.tb_frame.f_code
vnames = code.co_varnames
if vnames and vnames[0] in ('cls', 'self'):
ref = tb.tb_frame.f_locals[vnames[0]]
if vnames[0] == 'self':
className = fullClassName(ref.__class__)
else:
className = fullClassName(ref)
else:
className = '<global>'
traceElements.append(StackTraceElement(className, code.co_name, code.co_filename, tb.tb_lineno))
tb = tb.tb_next
self.setStackTrace(traceElements)
It’s a bit over my head - how could I use implement say in a situation like this for a module lets call barcodes?
def create(data):
import barcodes
# something here to print the module.create is running, called by GUI
barcodes.foo()
def foo():
# something here to print barcode.foo is runnig, called by create
x = 1
Now say I ran barcode.create(data) from my GUI, how would I use your class to have printed to the console
running barcode.create called from ?
running barcode.foo called from barcode.create
You wouldn’t use my class. I said inspiration. I would use python/jython’s exception machinery to generate a traceback. With raise inside a try block, if not directly. The traceback has the information you want. That class is an example of traversing it.
There might be ways to make it more accurate (get the module’s name, things like that…) but I have no idea how all this interacts with Ignition’s components. You might need to call upon Java for this, I don’t know.
No, I gave up and just hand traced how the code was being run, now I just judiciously use a logger where needed, logger.trace(“Running function x”) is the first line of any function I care to know and use logger.info()'s at important junctions. I also restructured my code so it’s not really a mystery who’s calling what anymore so I didn’t need this anymore. Sorry can’t be more helpful
I imagine <module> refers to the scripting module you have your functions in but I am not an expert here.
If you create a java exception instance you can get the java call-chain that ran the jython script, but you won’t get a specific component instance. It does generally reveal a component type, which can narrow down your search. Most of Ignition’s event machinery yields <module> when the source is an anonymous script.
You have to address this in your design pattern:
events should have single-line scripts that delegate to a project library function,
event or event.source should always be an argument passed in,
the library function should wrap the entire body in a trycatchcatch block–java and jython flavors,
and the handler should create a new exception that identifies the component, including the original exception as the cause.
Are there any specific reasons to do this other than organization, code reuse, exception handling etc...? I'm rolling through a large library of components and moving the logic as much as I can into a project library function for basically just organization. I was just curious if there were other benefits on the java/jython/Ignition side of things that we might see as a side benefit.
The biggest reason is to avoid legacy scoping rules. If you search the forum you can find tons of examples of the different ways in which legacy scoping creates issues.
We’ve never run into that exact issue so we’ve been running along w/o issues on event scripts. We’ve been going through fixing up our standards and just wanted to have a bit more of an explanation for future users.