2012-10-08 38 views
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將腳本轉換爲可執行文件,我會在事件日誌中獲得一個條目,但它說明了完全不同的內容。來自Python可執行文件的NTEventLogHandler

無法找到源(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() 

回答

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

你不得不設置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) 
+0

這聽起來有點合理。我該如何解決這個問題?手動註冊win32service.pyd?我如何在我的可執行文件中嵌入消息ID?是否有任何文章/教程可以證明這一點,因爲大多數關於日誌的文章只是指向標準的Python類文檔,這沒什麼幫助。 – CadentOrange

+0

看看我的編輯。 – leoluk

+0

感謝您的回答。它指出我在正確的方向。 – CadentOrange