Typically, the Windows event log does not save error messages in plain text, but rather links to the message identifier and the insert line.
Instead of saving a message of type Service foo crashed unexpectedly it saves a message identifier that points to a resource string stored in the DLL. In this case, the resource will look like Service %s crashed unexpectedly , and foo will be stored as an insertion string. The program that records the message registers the resource DLL.
The reason for this is localization. DLLs can store many different resources (dialog layout, lines, icons ...), and one DLL can contain the same resource in different languages. The operating system automatically selects the necessary resources depending on the system locale. Resource DLLs are used by almost all Microsoft utilities and major utilities.
Note: currently the preferred (and cross-platform) way to localize is gettext .
This is also used for the message log - ideally you can open the log from a German installation of Windows in English with all messages in English.
I suspect that the pywin32 implementation skips this mechanism with only one message identifier (1), which just looks like "%s" . It is stored in win32service.pyd and is registered by pywin32. This works great as long as this file exists on the file system, but is interrupted as soon as it is hidden inside the PyInstaller executable. I think you need to embed the message identifier in your executable file directly.
Edit: suspiciousness confirmed, the message table is actually stored inside win32service.pyd
Resource Hacker displaying a table of messages http://media.leoluk.de/evlog_rh.png
Try copying the message table resource from win32service.pyd to the win32service.pyd executable (for example, using the Resource Hacker ).
If you look at the implementation of the logging handler, this might work:
def __init__(self, appname, dllname=None, logtype="Application"): logging.Handler.__init__(self) try: import win32evtlogutil, win32evtlog self.appname = appname self._welu = win32evtlogutil if not dllname: dllname = os.path.split(self._welu.__file__) dllname = os.path.split(dllname[0]) dllname = os.path.join(dllname[0], r'win32service.pyd')
You need to set dllname in os.path.dirname(__file__) . Use something like this if you want it to continue to work for a thawed script:
if getattr(sys, 'frozen', False): dllname = None elif __file__: dllname = os.path.dirname(__file__) ntl = logging.handlers.NTEventLogHandler("Python Logging Test", dllname=dllname)