NTEventLogHandler from Python executable

import logging, logging.handlers def main(): ntl = logging.handlers.NTEventLogHandler("Python Logging Test") logger = logging.getLogger("") logger.setLevel(logging.DEBUG) logger.addHandler(ntl) logger.error("This is a '%s' message", "Error") if __name__ == "__main__": main() 

The Python (2.7.x) script above writes "This is a" Error "message for the Windows Event Viewer. When I run it as a script, I get the expected result. If I convert the script to an executable using PyInstaller, I get an entry in the event log. but he says something completely different.

Cannot find description for event id (1) in source (Python test protocol). The local registry may not display the necessary registry data or message DLL files to display messages from a remote computer. You can use the / AUXSOURCE = flag to get this description; see Help and Support. The following information is part of the event: This is an Error message.

This is the command I use to convert the script to an executable file: pyinstaller.py --onefile --noconsole my_script.py , although the command line options do not seem to affect this behavior, and simply calling pyinstaller.py my_script.py .

I would appreciate any help in understanding what is happening and how I am fixing it.

Final decision

I did not want to follow the path of the resource hacker, as this would be a difficult step for automation. Instead, the approach I took was to grab the win32service.pyd file from c: \ Python27 \ Lib \ site-packages \ win32 and place it next to my executable. Then the script was changed, passing the full path to the copy of the win32service.pyd file, and this works both in the script and in the exe form. The final script is below:

 import logging, logging.handlers import os import sys def main(): base_dir = os.path.dirname(sys.argv[0]) dllname = os.path.join(base_dir, "win32service.pyd") ntl = logging.handlers.NTEventLogHandler("Python Logging Test", dllname=dllname) logger = logging.getLogger("") logger.setLevel(logging.DEBUG) logger.addHandler(ntl) logger.error("This is a '%s' message", "Error") if __name__ == "__main__": main() 
+4
source share
1 answer

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) 
+4
source

Source: https://habr.com/ru/post/1438431/


All Articles