问题描述: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:WindowsSystem32inetsrv)。
对于设置应用的当前目录的示例代码,请参阅 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 } } } }