来自Python可执行文件的NTEventLogHandler

时间:2012-10-08 14:42:03

标签: 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()

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

  

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

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

我很感激任何帮助,以了解正在发生的事情以及如何解决这个问题。

最终解决方案

我不想走资源黑客路线,因为这将是一个自动化的艰难步骤。相反,我采用的方法是从c:\ Python27 \ Lib \ site-packages \ win32中获取win32service.pyd文件,并将其放在我的可执行文件旁边。然后修改脚本将完整路径传递给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()

1 个答案:

答案 0 :(得分:4)

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

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

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

附注:现在,本地化的首选(和跨平台)方式是gettext

这也用于消息日志 - 理想情况下,您可以使用英语中的所有消息从英语Windows安装中打开日志。

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

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

Resource Hacker showing the message table 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')

您必须将dllname设置为os.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)