# Buggy code
def statsAndLoggingAggregatorProcess(jobStore, stop):
    """
    The following function is used for collating stats/reporting log messages from the workers.
    Works inside of a separate process, collates as long as the stop flag is not True.
    """
    # Overall timing
    startTime = time.time()
    startClock = getTotalCpuTime()

    # Start off the stats file
    with jobStore.writeSharedFileStream("statsAndLogging.xml") as fileHandle:
        fileHandle.write('<?xml version="1.0" ?><stats>')

        # Call back function
        def statsAndLoggingCallBackFn(fileHandle2):
            node = ET.parse(fileHandle2).getroot()
            nodesNamed = node.find("messages").findall
            for message in nodesNamed("message"):
                logger.log(
                    int(message.attrib["level"]),
                    "Got message from job at time: %s : %s",
                    time.strftime("%m-%d-%Y %H:%M:%S"),
                    message.text,
                )
            for log in nodesNamed("log"):
                logger.info(
                    "%s:     %s" % tuple(log.text.split("!", 1))
                )  # the jobID is separated from log by "!"
            ET.ElementTree(node).write(fileHandle)

        # The main loop
        timeSinceOutFileLastFlushed = time.time()
        while True:
            if not stop.empty():  # This is a indirect way of getting a message to
                # the process to exit
                jobStore.readStatsAndLogging(statsAndLoggingCallBackFn)
                break
            if jobStore.readStatsAndLogging(statsAndLoggingCallBackFn) == 0:
                time.sleep(0.5)  # Avoid cycling too fast
            if time.time() - timeSinceOutFileLastFlushed > 60:  # Flush the
                # results file every minute
                fileHandle.flush()
                timeSinceOutFileLastFlushed = time.time()

        # Finish the stats file
        fileHandle.write(
            "<total_time time='%s' clock='%s'/></stats>"
            % (str(time.time() - startTime), str(getTotalCpuTime() - startClock))
        )


# Traceback (UnicodeDecodeError)
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/toil/worker.py", line 408, in <module>
main()
File "/usr/local/lib/python2.7/dist-packages/toil/worker.py", line 392, in main
jobStore.writeStatsAndLogging(ET.tostring(elementNode))
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1126, in tostring
ElementTree(element).write(file, encoding, method=method)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 820, in write
serialize(write, self._root, encoding, qnames, namespaces)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 939, in _serialize_xml
_serialize_xml(write, e, encoding, qnames, None)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 939, in _serialize_xml
_serialize_xml(write, e, encoding, qnames, None)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 937, in _serialize_xml
write(_escape_cdata(text, encoding))
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1073, in _escape_cdata
return text.encode(encoding, "xmlcharrefreplace")
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 48: ordinal not in range(128)

# Fixed code
def statsAndLoggingAggregatorProcess(jobStore, stop):
    """
    The following function is used for collating stats/reporting log messages from the workers.
    Works inside of a separate process, collates as long as the stop flag is not True.
    """
    #  Overall timing
    startTime = time.time()
    startClock = getTotalCpuTime()

    def callback(fileHandle):
        stats = json.load(fileHandle, object_hook=Expando)
        workers = stats.workers
        try:
            logs = workers.log
        except AttributeError:
            # To be expected if there were no calls to logToMaster()
            pass
        else:
            for message in logs:
                logger.log(
                    int(message.level),
                    "Got message from job at time: %s : %s",
                    time.strftime("%m-%d-%Y %H:%M:%S"),
                    message.text,
                )

        for log in stats.logs:
            logger.info("%s:     %s", log.jobStoreID, log.text)

    while True:
        # This is a indirect way of getting a message to the process to exit
        if not stop.empty():
            jobStore.readStatsAndLogging(callback)
            break
        if jobStore.readStatsAndLogging(callback) == 0:
            time.sleep(0.5)  # Avoid cycling too fast

    # Finish the stats file
    text = json.dumps(
        dict(
            total_time=str(time.time() - startTime),
            total_clock=str(getTotalCpuTime() - startClock),
        )
    )
    jobStore.writeStatsAndLogging(text)