In this topic, @rob outlines a way to use a textDAT to display text and error log instead of the “Textport and DATs” Dialog. Following those instructions works fine for sys.stdout, but trying to do the same with sys.stderr causes TD to crash. Here is my exact process:
I make a textDAT called ‘text1’. On the same level, I make an ExecuteDat, in which I put:
n = op('text1')
def onStart():
sys.stderr = n
return
I press the pulse button next to Start in the executeDAT’s parameters.
To test this, I make a constantCHOP and a chopExecDAT and put the following syntax error in the chopExecDAT:
Thanks for the report. I can reproduce the problem and am looking into it now.
Update: The issue is that there is an internal error when it’s trying to print the error message to the DAT and this is creating an infinite loop. I’m looking into potential solutions and will let you know when something is available.
I’ve fixed the issue with the text dats and that will be included in our next release.
In the meantime, there is a workaround if you’re interested. Rather than send the errors directly to the DAT, you can use a helper class that has the required interface for stderr and override that to direct the output to the DAT.
Using your example, if you put this code in the ExecuteDAT it should work:
class MyCatcher(tdutils.outputcatcher.StdoutCatcher):
def write(self, stuff):
op('text1').write(stuff)
return
mc = MyCatcher()
def onStart():
sys.stderr = mc
return
Hey, I’m having a peculiar bug when using the workaround. It works fine printing and error-logging as can be tested with the chopExecDAT. However, if you right-click on the base1 below it, select “customize component” to open the Component Editor, and then close the Component Editor, a stream of never-ending errors floods the saveableLog.
AttributeError: 'NoneType' object has no attribute 'par'
Traceback (most recent call last):
File "", line 1, in <module>
I’ve attached the toe. Note that, in the executeDAT, I added a 60f delay because otherwise I get a similar endless error stream.
I can avoid the endless stream by using the native Python open(), write(), close() methods, but this won’t let me easily view the log in TD without opening the file (fileInDAT needs to be refreshed).
I discussed the issue with the team here and the verdict seems to be that there might not be a great solution for this.
The original post about redirecting Python’s output stream was a bit of a hack and this actually bypasses some of TouchDesigner’s internal error handling which is why you’re suddenly get a flood of error messages that shouldn’t be visible. The messages are also missing important context information that is normally inserted by TouchDesigner, which is why the file, line and module information are not correct.
Have you looked at the Error DAT and its callbacks? That might be a cleaner way to collect error data depending on what you’re trying to do.
@robmc Thanks for the reply. Since my goal is to save both the print/debug output and the error log continuously to a file (in case of crashes), I’ve made a Frankenstein hybrid of the sys.stdout catcher and the errorDAT’s output. It seems to work pretty well. Wish I could figure out how to get the Base’s display to auto-scroll-down, but that’s not a big deal:
Here’s a newer version. I set it to do the following:
Filter out certain internal errors that the errorDAT reports, but which would not be normally shown in the text/DAT dialog.
Filter out repeated errors by comparing with the last five error calls. This is to avoid flooding the error log with calls and tanking your FPS in the event that you have five errors that are continuously called every frame. Eg. if I have 5 invalid parameter values on an audioOscillatorCHOP, the text/DAT dialog would not report this, but the errorDAT would every frame. My “fix” has them only reported once, but if you have 6+ such errors at once, the FPS tanks again. I’m just doing simple string comparison in a for loop. @robmc How does the actual text/DAT dialog go about filtering out such errors?
yeah, we’re not doing any kind of string comparison to filter errors. For python errors, we generally redirect the error output to a buffer specific to the node that was running the code and then we choose based on the node where to display that error information. So, errors that occur on internal nodes generally just stay on those nodes’ separate buffers.
Does that answer your question? We have some longer term ideas on how to make the logging more customizable, but its not on our immediate todo list unfortunately.