4
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()

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

找不到 Source ( Python Logging Test ) 中事件 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()
4

1 回答 1

4

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

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

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

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

这也用于消息日志 - 理想情况下,您可以在英语版本上打开来自德语 Windows 安装的日志,其中所有消息均为英语。

我怀疑 pywin32 实现通过只有一个消息 ID (1) 来跳过该机制,就像"%s". 它存储在win32service.pydpywin32中并由其注册。只要此文件存在于文件系统上,它就可以正常工作,但一旦它隐藏在 PyInstaller 可执行文件中就会中断。我想您必须将消息 ID 直接嵌入到您的可执行文件中。

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

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

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

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

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')

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

if getattr(sys, 'frozen', False):
    dllname = None
elif __file__:
    dllname = os.path.dirname(__file__)

ntl = logging.handlers.NTEventLogHandler("Python Logging Test", dllname=dllname)
于 2012-10-08T17:05:54.837 回答