問題描述:Log4Net,本地測試一切正常,發布后,無法自動創建文件夾和日志文件,無法寫入文件。
一、在項目中配置Log4Net
請參考我的上一篇博客 《aspnetcore配置log4net並添加全局異常處理》,常規做法。
二、Log4Net不寫日志常規解決步驟
一般講來,Log4Net是非常成熟的框架,很難出現問題,出現不寫日志這種情況,首先要做的是檢查我們的代碼、配置是否正確。
- 檢查目錄中是否包含 log4net.config ,如果文件不存在,手動復制一份即可。
- 檢查 log4net.config 文件,主要是日志文件輸出路徑,一般日志保存目錄是這樣的 根目錄/Logs/Errors/xxxx.log ,其他資料里一般寫法為 <file value="Logs\\Errors\\" /> ,在這里個人推薦這樣來寫 <file value="Logs/Errors/" /> ,原因是曾經在 ubuntu > nginx 環境下部署時,第一種寫法無法正常工作,改為第二種方法后正常。
- 檢查文件夾權限,確保具有寫權限。
三、絕望的排查過程
因為在本地測試完全正常,所以代碼和配置文件應該沒有問題,但保險起見,還是將網上常用的幾種寫法都嘗試了一遍,然而,並沒有什么卵用。
接下來,查看文件夾權限,講真,對於Server的文件系統我是真的了解很少,大部分時間遇到權限問題,就簡單的給Everyone一個讀寫權限了事。不出意外的,折騰許久,還是失敗了。
重復以上步驟兩個小時后...
頭昏腦漲中,想起之前的項目中使用Log4Net是正常的,寫法和部署方式一樣,為什么這次不行呢?
於是對比之前的項目,把IIS中站點和應用程序池的配置項一條一條拿出來看,一個小時后...
完全一樣啊,啥情況,要不換NLog???雖然為了項目進度換成其他框架也說的過去,但作為程序員,明知道有bug卻解決不了,總歸過不去心里這一關不是。不甘心啊~
四、靈光乍現
就在准備把問題先放放,修改項目使用Nlog的時候,鬼使神差的,去看了眼任務管理器,咦~~還真發現了問題,之前的項目都是開了三個進程(w3wp.exe \ conhost.exe \ dotnet.exe),而新項目只有兩個(w3wp.exe \ conhost.exe),精神立馬振奮了一下,看來問題可能就在這里了。打開項目的 web.config 文件,發現多了點東西:
有問題找度娘,搜索 InProcess ,大部分內容都一樣,沒看出啥問題,難道是我想錯了,問題不在這里?說臟話會不會發布失敗?咱也不知道咱也不敢問,還是憋回去吧!
抱着最后一絲希望,跑到官方文檔 《ASP.NET Core 模塊》,不得不說啊,微軟自動翻譯的文檔真的很考驗中文水平,但是再爛也得看不是,這塊內容之前也沒說關注過,不管能不能解決問題,看看也好,當補課了。
細細看下來,有這么一段
在 ASP.NET Core 2.2.1 或早期版本中,GetCurrentDirectory 會返回 IIS 啟動的進程的工作目錄而非應用目錄(例如,對於 w3wp.exe,是 C:\Windows\System32\inetsrv)。
對於設置應用的當前目錄的示例代碼,請參閱 CurrentDirectoryHelpers 類。 調用
SetCurrentDirectory
方法。 后續 GetCurrentDirectory 調用提供應用的目錄。
醍醐灌頂,雖然外面已經是傍晚時分,但我眼前仿佛一道曙光升起,恍惚間似乎看到天女降臨,嘿,嘿嘿~~
五、填坑
接下來就簡單了,github上把CurrentDirectoryHelpers類搞下來,在 startup.cs 的構造函數中添加 CurrentDirectoryHelpers.SetCurrentDirectory(); ,重新發布、部署、啟動站點,文件出現了。
下面貼一下 CurrentDirectoryHelpers 的實現,不要謝我,我只是代碼的搬運工

using System; namespace SampleApp { internal class CurrentDirectoryHelpers { internal const string AspNetCoreModuleDll = "aspnetcorev2_inprocess.dll"; [System.Runtime.InteropServices.DllImport("kernel32.dll")] private static extern IntPtr GetModuleHandle(string lpModuleName); [System.Runtime.InteropServices.DllImport(AspNetCoreModuleDll)] private static extern int http_get_application_properties(ref IISConfigurationData iiConfigData); [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] private struct IISConfigurationData { public IntPtr pNativeApplication; [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.BStr)] public string pwzFullApplicationPath; [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.BStr)] public string pwzVirtualApplicationPath; public bool fWindowsAuthEnabled; public bool fBasicAuthEnabled; public bool fAnonymousAuthEnable; } public static void SetCurrentDirectory() { try { // Check if physical path was provided by ANCM var sitePhysicalPath = Environment.GetEnvironmentVariable("ASPNETCORE_IIS_PHYSICAL_PATH"); if (string.IsNullOrEmpty(sitePhysicalPath)) { // Skip if not running ANCM InProcess if (GetModuleHandle(AspNetCoreModuleDll) == IntPtr.Zero) { return; } IISConfigurationData configurationData = default(IISConfigurationData); if (http_get_application_properties(ref configurationData) != 0) { return; } sitePhysicalPath = configurationData.pwzFullApplicationPath; } Environment.CurrentDirectory = sitePhysicalPath; } catch { // ignore } } } }