我使用 TopShelf 创建了 Windows服务,使用 Log4Net 添加了日志记录,然后构建了项目,安装了服务并开始服务。然后我的服务运行正常,但它没有记录。将显示 TopShelf 日志,但不会显示我添加到Windows服务的日志。更奇怪的是,如果我重新启动 Windows服务,则日志记录开始工作。
如果你想克隆它并自己重现问题,我已经创建了一个小项目GitHub repo来重现这个问题。
该服务应该创建两个文件,一个只是说" Hello World"和包含所有日志的另一个。如果日志文件已成功记录该行,则它将起作用:Why is this line not logged?
如果该行没有出现在log.txt
文件中,那么我的问题就无法解决。
注意:如果单击 Visual Studio 中的开始按钮,则会显示此行,但我希望在安装服务并启动服务时它能够正常工作。如果服务启动,然后重新启动它也会起作用,但这看起来更像是一个黑客而不是一个修复。
这就是我设置服务的方式。我使用 .Net Framework 4.6.1 创建了一个新的 C#控制台应用程序并安装了3个 NuGet 软件包:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.8" targetFramework="net461" />
<package id="Topshelf" version="4.0.4" targetFramework="net461" />
<package id="Topshelf.Log4Net" version="4.0.4" targetFramework="net461" />
</packages>
然后我创建了 Windows服务:
using log4net.Config;
using System.IO;
using Topshelf;
using Topshelf.HostConfigurators;
using Topshelf.Logging;
using Topshelf.ServiceConfigurators;
namespace LogIssue
{
public class Program
{
public const string Name = "LogIssue";
public static void Main(string[] args)
{
XmlConfigurator.Configure();
HostFactory.Run(ConfigureHost);
}
private static void ConfigureHost(HostConfigurator x)
{
x.UseLog4Net();
x.Service<WindowsService>(ConfigureService);
x.SetServiceName(Name);
x.SetDisplayName(Name);
x.SetDescription(Name);
x.RunAsLocalSystem();
x.StartAutomatically();
x.OnException(ex => HostLogger.Get(Name).Error(ex));
}
private static void ConfigureSystemRecovery(ServiceRecoveryConfigurator serviceRecoveryConfigurator) =>
serviceRecoveryConfigurator.RestartService(delayInMinutes: 1);
private static void ConfigureService(ServiceConfigurator<WindowsService> serviceConfigurator)
{
serviceConfigurator.ConstructUsing(() => new WindowsService(HostLogger.Get(Name)));
serviceConfigurator.WhenStarted(service => service.OnStart());
serviceConfigurator.WhenStopped(service => service.OnStop());
}
}
internal class WindowsService
{
private LogWriter _logWriter;
public WindowsService(LogWriter logWriter)
{
_logWriter = logWriter;
}
internal bool OnStart() {
new Worker(_logWriter).DoWork();
return true;
}
internal bool OnStop() => true;
}
internal class Worker
{
private LogWriter _logWriter;
public Worker(LogWriter logWriter)
{
_logWriter = logWriter;
}
public async void DoWork() {
_logWriter.Info("Why is this line not logged?");
File.WriteAllText("D:\\file.txt", "Hello, World!");
}
}
}
我在app.config中添加了 Log4Net 配置:
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="D:\log.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="100KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
<layout type="log4net.Layout.SimpleLayout" />
</appender>
<appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="FATAL" />
<foreColor value="Purple, HighIntensity" />
</mapping>
<mapping>
<level value="ERROR" />
<foreColor value="Red, HighIntensity" />
</mapping>
<mapping>
<level value="WARN" />
<foreColor value="Yellow, HighIntensity" />
</mapping>
<mapping>
<level value="INFO" />
<foreColor value="Green, HighIntensity" />
</mapping>
<mapping>
<level value="DEBUG" />
<foreColor value="Cyan, HighIntensity" />
</mapping>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message%newline" />
</layout>
</appender>
<root>
<appender-ref ref="RollingFileAppender" />
<appender-ref ref="TraceAppender" />
<appender-ref ref="ColoredConsoleAppender" />
</root>
</log4net>
除此之外,我可以运行该应用程序。
那么,什么有用?好吧,我可以通过 Visual Studio 将应用程序作为控制台应用程序运行。这样,一切正常,特别是行:_logWriter.Info("Why is this line not logged?");
正确记录 。
安装服务时:
Release
模式构建项目Path/To/Service.exe install
Path/To/Service.exe start
应用程序正确启动并创建了两个日志文件(D:\file.txt
和D:\log.txt
)但是当我查看D:\log.txt
文件时,我看到没有日志 "Why is this line not logged?"
并使其更加陌生 - 重新启动服务(服务&gt;右键单击LogIssue&gt;重新启动)会导致所有日志记录再次开始工作完美。
此外,它不像日志记录不完全正常。日志文件中充满了TopShelf日志,只是而不是我从我的应用程序中记录的内容。
我做错了什么,导致它无法正确记录?
如果您想尝试重现此操作,可以按照上述步骤操作,或者如果您愿意,可以克隆项目:https://github.com/jamietwells/log-issue.git
在进一步检查时,这比我想象的更令人困惑。我确信这个问题与XmlConfigurator.Configure()
电话在错误的地方有关,但是在测试时我找到了:
安装 Windows服务时,调用如下:
启动 Windows服务时,调用如下:
所以Main
肯定被调用(实际上它似乎被调用了两次!)。一个可能的问题是OnStart
从不同的线程调用Main
,但即使将XmlConfigurator.Configure()
调用复制到OnStart
以便从新线程调用它也会导致记录不起作用。
此时我想知道是否有人 Log4Net 使用 TopShelf ?
以下是我在安装服务时生成的日志文件示例:
2018-06-12 11:55:20,595 [1] INFO Topshelf.HostFactory [(null)] - Configuration Result:
[Success] Name LogIssue
[Success] ServiceName LogIssue
2018-06-12 11:55:20,618 [1] INFO Topshelf.HostConfigurators.HostConfiguratorImpl [(null)] - Topshelf v4.0.0.0, .NET Framework v4.0.30319.42000
2018-06-12 11:55:20,627 [1] DEBUG Topshelf.Hosts.InstallHost [(null)] - Attempting to install 'LogIssue'
2018-06-12 11:55:20,636 [1] INFO Topshelf.Runtime.Windows.HostInstaller [(null)] - Installing LogIssue service
2018-06-12 11:55:20,642 [1] DEBUG Topshelf.Runtime.Windows.HostInstaller [(null)] - Opening Registry
2018-06-12 11:55:20,642 [1] DEBUG Topshelf.Runtime.Windows.HostInstaller [(null)] - Service path: "D:\github\log-issue\LogIssue\bin\Release\LogIssue.exe"
2018-06-12 11:55:20,643 [1] DEBUG Topshelf.Runtime.Windows.HostInstaller [(null)] - Image path: "D:\github\log-issue\LogIssue\bin\Release\LogIssue.exe" -displayname "LogIssue" -servicename "LogIssue"
2018-06-12 11:55:20,644 [1] DEBUG Topshelf.Runtime.Windows.HostInstaller [(null)] - Closing Registry
2018-06-12 11:55:22,839 [1] INFO Topshelf.HostFactory [(null)] - Configuration Result:
[Success] Name LogIssue
[Success] ServiceName LogIssue
2018-06-12 11:55:22,862 [1] INFO Topshelf.HostConfigurators.HostConfiguratorImpl [(null)] - Topshelf v4.0.0.0, .NET Framework v4.0.30319.42000
2018-06-12 11:55:22,869 [1] DEBUG Topshelf.Hosts.StartHost [(null)] - Starting LogIssue
2018-06-12 11:55:23,300 [1] INFO Topshelf.Hosts.StartHost [(null)] - The LogIssue service was started.
此时在日志中,我重新启动 Windows服务,您可以看到日志记录然后开始工作。具体来说,这次记录了行Why is this line not logged?
,但不是最后一次。
2018-06-12 12:09:43,525 [6] INFO Topshelf.Runtime.Windows.WindowsServiceHost [(null)] - [Topshelf] Stopping
2018-06-12 12:09:43,542 [6] INFO Topshelf.Runtime.Windows.WindowsServiceHost [(null)] - [Topshelf] Stopped
2018-06-12 12:09:45,033 [1] INFO Topshelf.HostFactory [(null)] - Configuration Result:
[Success] Name LogIssue
[Success] ServiceName LogIssue
2018-06-12 12:09:45,055 [1] INFO Topshelf.HostConfigurators.HostConfiguratorImpl [(null)] - Topshelf v4.0.0.0, .NET Framework v4.0.30319.42000
2018-06-12 12:09:45,071 [1] DEBUG Topshelf.Runtime.Windows.WindowsHostEnvironment [(null)] - Started by the Windows services process
2018-06-12 12:09:45,071 [1] DEBUG Topshelf.Builders.RunBuilder [(null)] - Running as a service, creating service host.
2018-06-12 12:09:45,072 [1] INFO Topshelf.Runtime.Windows.WindowsServiceHost [(null)] - Starting as a Windows service
2018-06-12 12:09:45,074 [1] DEBUG Topshelf.Runtime.Windows.WindowsServiceHost [(null)] - [Topshelf] Starting up as a windows service application
2018-06-12 12:09:45,076 [5] INFO Topshelf.Runtime.Windows.WindowsServiceHost [(null)] - [Topshelf] Starting
2018-06-12 12:09:45,076 [5] DEBUG Topshelf.Runtime.Windows.WindowsServiceHost [(null)] - [Topshelf] Current Directory: D:\github\log-issue\LogIssue\bin\Release
2018-06-12 12:09:45,076 [5] DEBUG Topshelf.Runtime.Windows.WindowsServiceHost [(null)] - [Topshelf] Arguments:
2018-06-12 12:09:45,078 [5] INFO LogIssue.Worker [(null)] - Why is this line not logged?
2018-06-12 12:09:45,083 [5] INFO Topshelf.Runtime.Windows.WindowsServiceHost [(null)] - [Topshelf] Started
答案 0 :(得分:3)
为清楚起见,这里按文件名列出所有代码:
assemblyinfo.cs(将其添加到已存在的代码中):
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "Log4Net.config", Watch = true)]
app.config(将此添加到框架生成的默认配置):
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net configSource="Log4Net.config"/>
Log4Net.config(此处有更多内容,但我将其删除,因为它与此处的问题无关):
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="D:\log.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="100KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<appender-ref ref="RollingFileAppender" />
</root>
</log4net>
的Program.cs:
using Topshelf;
using Topshelf.HostConfigurators;
using Topshelf.Logging;
using Topshelf.ServiceConfigurators;
namespace LogIssue
{
public class Program
{
public const string Name = "LogIssue";
public static void Main(string[] args)
{
HostFactory.Run(ConfigureHost);
}
private static void ConfigureHost(HostConfigurator x)
{
x.Service<WindowsService>(ConfigureService);
x.SetServiceName(Name);
x.SetDisplayName(Name);
x.SetDescription(Name);
x.RunAsLocalSystem();
x.StartAutomatically();
x.OnException(ex => HostLogger.Get(Name).Error(ex));
}
private static void ConfigureSystemRecovery(ServiceRecoveryConfigurator serviceRecoveryConfigurator) =>
serviceRecoveryConfigurator.RestartService(delayInMinutes: 1);
private static void ConfigureService(ServiceConfigurator<WindowsService> serviceConfigurator)
{
serviceConfigurator.ConstructUsing(() => new WindowsService());
serviceConfigurator.WhenStarted(service => service.OnStart());
serviceConfigurator.WhenStopped(service => service.OnStop());
}
}
}
WindowsService.cs:
using log4net;
namespace LogIssue
{
internal class WindowsService
{
static ILog _log = LogManager.GetLogger(typeof(WindowsService));
internal bool OnStart() {
new Worker().DoWork();
return true;
}
internal bool OnStop() => true;
}
}
Worker.cs:
using log4net;
using System.IO;
namespace LogIssue
{
internal class Worker
{
static ILog _log = LogManager.GetLogger(typeof(Worker));
public void DoWork() {
_log.Info("Why is this line not logged?");
File.WriteAllText("D:\\file.txt", "Hello, World!");
}
}
}
编辑:
说明:
以下是我的搜索结果(点击图片放大)...
此时值得注意的是,在worker.cs中进行的日志调用可能不会立即输出到日志中,主要是因为&#34;刷新&#34;收集了一定数量的日志语句后,log4net定期执行的文件,或者日志容器超出范围并且将被解构。
这可能导致在将代码部署到服务器时似乎没有进行日志记录调用。
我们可以通过修改上面的服务来定期测试这个问题&#34;处理&#34;工人阶级,并建立一个新的... ...
using log4net;
using System.Timers;
namespace LogIssue
{
internal class WindowsService
{
static ILog _log = LogManager.GetLogger(typeof(WindowsService));
readonly Timer _timer = new Timer(1000);
public WindowsService() => _timer.Elapsed += (s, e) => new Worker().DoWork();
internal void OnStart() => _timer.Start();
internal void OnStop() => _timer.Stop();
}
}
答案 1 :(得分:2)
我已经解决了这个问题。或者更确切地说,一个名叫Kvarv的人在一年前解决了这个问题:https://github.com/Topshelf/Topshelf/issues/206#issuecomment-312581963
基本上,在命令提示符窗口中运行path/to/exe start
时,TopShelf将启动应用程序的两个实例。
第一个实例是进行一些设置和配置,第二个实例将是我们想要启动并继续运行的实际 Windows服务。
因为两者同时运行,所以为任何可以访问日志文件并首先锁定它的人引入了竞争条件。这意味着TopShelf将记录日志或您的应用程序将记录,具体取决于谁先锁定文件。
如果TopShelf首先锁定日志文件,则应用程序不会记录。
我意识到如果我在启动服务之前延迟1秒钟,我可以修复日志记录,但直到现在为止还没有意识到原因。第一个实例已完成它的配置,使用日志文件完成并且锁定到期,然后我的应用程序可以出现并配置其日志记录并写入文件。
我还意识到我们可以重新启动服务并让它突然开始工作和记录。我不知道是这种情况但是我愿意在重启被称为TopShelf时表现不同并且没有启动程序的第二个实例,它只是调用OnStop
,然后OnStart
。如果有人在重新启动服务时移动了TopShelf的行为信息,我将有兴趣知道。
它还解释了问题似乎并不适合每个人。竞争条件在不同的硬件上给出不同的结果。
有几种解决方案可以解决这个问题。
在上面链接的TopShelf问题上,第一个建议是使用 PowerShell 模块安装服务:
Start-Service <serviceName>
如果在命令提示符下而不是 PowerShell
,我们也可以使用sc start <serviceName>
这似乎不会启动多个实例并锁定文件,与其他启动服务的方法相比,可以产生更加一致和可预测的体验:
path/to/exe start
我们可以确保日志记录将文件锁定的时间尽可能短,以减少死锁的可能性。这在使用日志记录时会产生性能影响,但它可以解决问题。我们可以简单地添加:
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
到App.config
中的 RollingFileAppender 。
我们还可以向OnStart
方法添加一秒延迟,以便为第一个实例提供完成时间。
我们还可以更改Log4Net的配置方式,这样他们就不会对文件进行争夺。这是我的解决方案。在App.config文件的log4net部分中,我添加了:
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="D:\log.txt" />
但只需将其更改为:
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="D:\Logs\%processid.log" />
将导致使用当前正在运行的进程的ID来命名日志文件。这样每个实例都会获得自己的日志文件,锁定问题就不再存在。
似乎将XmlConfigurator.Configure();
作为Main()
中的第一行包含在某种程度上非常重要。我还没有完全理解为什么这很重要,但可能是因为据我所知:x.UseLog4Net();
不会调用XmlConfigurator.Configure();
,而HostLogger.Get(Name))
会调用import subprocess
subprocess.call(["rosnode", "kill", "my_node"], shell=True)
。这可以在TopShelf源(函数CreateLogWriterFactory)中看到。