来自 Python 可执行文件的 NTEventLogHandler

Cad*_*nge 4 python logging pywin32 pyinstaller

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()
Run Code Online (Sandbox Code Playgroud)

上面的 Python (2.7.x) 脚本将“这是一条‘错误’消息”写入 Windows 事件查看器。当我将其作为脚本运行时,我得到了预期的输出。如果我通过 PyInstaller 将脚本转换为可执行文件,我会在事件日志中获得一个条目,但它会显示完全不同的内容。

无法在源(Python 日志记录测试)中找到事件 ID (1) 的描述。本地计算机可能没有必要的注册表信息或消息 DLL 文件来显示来自远程计算机的消息。您可以使用 /AUXSOURCE= 标志来检索此描述;有关详细信息,请参阅帮助和支持。以下信息是该事件的一部分: 这是一条“错误”消息。

这是我用来将脚本转换为可执行文件的命令:pyinstaller.py --onefile --noconsole my_script.py尽管命令行参数似乎对此行为没有任何影响,只需调用pyinstaller.py my_script.py.

如果您能帮助我了解发生的情况以及我如何解决此问题,我将不胜感激。

最终解决方案

我不想走资源黑客路线,因为这将是自动化的一个困难步骤。相反,我采取的方法是win32service.pyd从 c:\Python27\Lib\site-packages\win32 中获取文件并将其放在我的可执行文件旁边。然后修改脚本,将完整路径传递给文件副本win32service.pyd,这在脚本和 exe 形式中都有效。最终脚本如下:

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()
Run Code Online (Sandbox Code Playgroud)

leo*_*luk 5

通常,Windows 事件日志不会以纯文本形式存储错误消息,而是存储消息 ID 引用和插入字符串。

\n\n

它不是存储像 那样的消息Service foo crashed unexpectedly,而是存储指向 DLL 中存储的资源字符串的消息 ID。在这种情况下,资源将类似于Service %s crashed unexpectedly并将foo存储为插入字符串。写入消息的程序注册资源DLL。

\n\n

其原因在于本地化。DLL 可以存储大量不同的资源(对话框布局、字符串、图标\xe2\x80\xa6),并且一个 DLL 可以包含多种不同语言的相同资源。操作系统根据系统区域设置自动选择正确的资源。几乎所有 Microsoft 实用程序和核心实用程序都使用资源 DLL。

\n\n

旁注:如今,本地化的首选(跨平台)方式是gettext.

\n\n

这也用于消息日志 \xe2\x80\x93 理想情况下,您可以在英语 Windows 上打开德语 Windows 安装的日志,其中所有消息均为英语。

\n\n

我怀疑 pywin32 实现仅具有一个消息 ID (1)(类似于"%s". 它存储在win32service.pydpywin32 中并由 pywin32 注册。只要该文件存在于文件系统上,它就可以正常工作,但一旦它隐藏在 PyInstaller 可执行文件中,就会中断。我猜你必须将消息 ID 直接嵌入到可执行文件中。

\n\n

编辑:怀疑得到证实,消息表确实存储在里面win32service.pyd

\n\n

资源黑客显示消息表 http://media.leoluk.de/evlog_rh.png

\n\n

尝试将消息表资源复制到win32service.pydPyInstaller 可执行文件(例如使用Resource Hacker)。

\n\n

查看日志处理程序的实现,这可能有效:

\n\n
def __init__(self, appname, dllname=None, logtype="Application"):\n    logging.Handler.__init__(self)\n    try:\n        import win32evtlogutil, win32evtlog\n        self.appname = appname\n        self._welu = win32evtlogutil\n        if not dllname:\n            dllname = os.path.split(self._welu.__file__)\n            dllname = os.path.split(dllname[0])\n            dllname = os.path.join(dllname[0], r\'win32service.pyd\')\n
Run Code Online (Sandbox Code Playgroud)\n\n

你必须设置dllnameos.path.dirname(__file__). 如果您希望它继续为未冻结的脚本工作,请使用类似以下内容:

\n\n
if getattr(sys, \'frozen\', False):\n    dllname = None\nelif __file__:\n    dllname = os.path.dirname(__file__)\n\nntl = logging.handlers.NTEventLogHandler("Python Logging Test", dllname=dllname)\n
Run Code Online (Sandbox Code Playgroud)\n