通常,Windows 事件日志不会以纯文本形式存储错误消息,而是存储消息 ID 引用和插入字符串。
而不是存储像这样的消息Service foo crashed unexpectedly
,它存储一个消息 ID,该 ID 指向存储在 DLL 中的资源字符串。在这种情况下,资源将类似于Service %s crashed unexpectedly
and foo
将存储为插入字符串。写入消息的程序注册资源DLL。
其原因在于本地化。 DLL 可以存储大量不同的资源(对话框布局、字符串、图标……),并且一个 DLL 可以包含多种不同语言的相同资源。操作系统根据系统区域设置自动选择正确的资源。几乎所有 Microsoft 实用程序和核心实用程序都使用资源 DLL。
Side note: Nowadays, the preferred (and cross-platform) way for localization is gettext
.
这也用于消息日志 - 理想情况下,您可以在英语 Windows 上打开德语 Windows 安装的日志,其中所有消息均为英语。
我怀疑 pywin32 实现通过只有一个消息 ID (1) 来跳过该机制,就像这样"%s"
。它存储在win32service.pyd
并由 pywin32 注册。只要该文件存在于文件系统上,它就可以正常工作,但一旦它隐藏在 PyInstaller 可执行文件中,就会中断。我猜你必须将消息 ID 直接嵌入到可执行文件中。
Edit:怀疑得到证实,消息表确实存放在里面win32service.pyd
资源黑客显示消息表 http://media.leoluk.de/evlog_rh.png http://media.leoluk.de/evlog_rh.png
尝试从复制消息表资源win32service.pyd
到您的 PyInstaller 可执行文件(例如使用资源黑客 http://media.leoluk.de/evlog_rh.png).
查看日志处理程序的实现,这可能有效:
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
to 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)