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.
I've had reason to dig into this as of late for similar reasons to the OP. had started using logger.trace(traceback.format_stack(limit=1)) and was wondering what better options might exist. My research led me to these ways of programmatically obtaining the current function name and the name of the function that called it. I
# Options to get name of current function
fname = traceback.extract_stack(limit=1)[0][2]
fname = sys._getframe(0).f_code.co_name
fname = inspect.stack()[0][3]
fname = inspect.currentframe().f_code.co_name
# Get name of calling function
fname = traceback.extract_stack(limit=2)[0][2]
fname = sys._getframe(1).f_code.co_name
fname = inspect.stack()[1][3]
fname = inspect.currentframe().f_back.f_code.co_name
I attempted to benchmark the performance of the different options (my first time trying this), these results were representative.
Method
Loops
Milliseconds
traceback.extract_stack(limit=1)[0][2]
100
1.846
sys._getframe(0).f_code.co_name
100
0.082
inspect.currentframe().f_code.co_name
100
0.053
inspect.stack()[0][3]
100
8,355.998
I removed inspect.stack()[0][3] from further testing as it was sooo slow.
Method
Loops
Milliseconds
traceback.extract_stack(limit=1)[0][2]
100,000
637.639
sys._getframe(0).f_code.co_name
100,000
23.272
inspect.currentframe().f_code.co_name
100,000
17.674
Method
Loops
Milliseconds
traceback.extract_stack(limit=1)[0][2]
1,000,000
6,399.593
sys._getframe(0).f_code.co_name
1,000,000
268.101
inspect.currentframe().f_code.co_name
1,000,000
226.340
inspect.currentframe().f_code.co_name does appear to be fastest for getting current function name. It does require importing inspect for each module where I want to use it and when toying with that in script console the import does appear to add a notable delay.
An argument against using sys._getframe(0).f_code.co_name is that the leading underscore in the function name indicates that it is private, intended for internal use and should not be directly called by users outside the module or class.
If speed is important to you, you should use system.reflect.asThrowable() to convert jython exceptions into java-compatible exceptions. Then you can use the logger method variants that take a throwable object and expand it into the logs for you, efficiently.
If you cannot use my Integration Toolkit, similar functionality is available in later.py.
I'm late to this post, but I like this approach from an efficiency standpoint. I can put all my expensive imports at top level and only do them once, as opposed to something like a custom method or an event handler that would have to do them every time they run. Also, there's the obvious benefit of being able to use the functions in multiple places while only having to maintain them in one place.
I can illustrate the efficiency with a simple test script that imports Color and returns red. If I do this in a library script with the top level import, it runs more than 5 times faster than if I execute the same script from a custom method:
Example:
'''
Compare library script efficiency vs custom method efficiency
Loop each approach a million time to magnify the efficiency differences
'''
# Each script simply imports java.awt.Color and returns Color.RED
# Run a function that requires imports from the script library
startTime = system.date.now()
for index in xrange(1000000):
red = testScript.getRed()
print 'Library Script:', '{}ms'.format(system.date.millisBetween(startTime, system.date.now()))
# Run a script that requires imports from a custom method on the source component
startTime = system.date.now()
for index in xrange(1000000):
red = event.source.getRed()
print 'Custom Method:', '{}ms'.format(system.date.millisBetween(startTime, system.date.now()))