在安装.Net服务期间,创建自定义事件日志和事件源的最可靠方法是什么

我在安装.Net Windows服务期间无法可靠地创建/删除事件源。

这是我的ProjectInstaller类的代码:

// Create Process Installer ServiceProcessInstaller spi = new ServiceProcessInstaller(); spi.Account = ServiceAccount.LocalSystem; // Create Service ServiceInstaller si = new ServiceInstaller(); si.ServiceName = Facade.GetServiceName(); si.Description = "Processes ..."; si.DisplayName = "Auto Checkout"; si.StartType = ServiceStartMode.Automatic; // Remove Event Source if already there if (EventLog.SourceExists("AutoCheckout")) EventLog.DeleteEventSource("AutoCheckout"); // Create Event Source and Event Log EventLogInstaller log = new EventLogInstaller(); log.Source = "AutoCheckout"; log.Log = "AutoCheckoutLog"; Installers.AddRange(new Installer[] { spi, si, log }); 

引用的facade方法只返回日志,服务等名称的字符串。

此代码大部分时间都有效,但最近安装后我开始在应用程序日志中显示日志条目而不是自定义日志。 以下错误也在日志中:

无法找到Source(AutoCheckout)中事件ID(0)的描述。 本地计算机可能没有必要的注册表信息或消息DLL文件来显示来自远程计算机的消息。 您可以使用/ AUXSOURCE =标志来检索此描述; 请参阅帮助和支持以获取详细信

由于某种原因,它在卸载期间没有正确删除源,或者在安装期间没有创建它。

任何有关最佳实践的帮助都表示赞赏。

谢谢!

另外,这里是我如何编写日志exception的示例:

 // Write to Log EventLog.WriteEntry(Facade.GetEventLogSource(), errorDetails, EventLogEntryType.Error, 99); 

关于stephbu的答案:推荐的路径是安装程序脚本和installutil,或Windows安装例程。

我正在使用安装项目,它执行服务的安装并设置日志。 无论我使用的是installutil.exe还是windows安装项目,我相信它们都会调用我上面显示的相同的ProjectInstaller类。

我看到如果在重新启动之前没有真正删除日志,我的测试机器的状态可能会导致错误。 我将进行更多实验,看看是否能解决问题。

编辑:我对在安装服务期间注册源和日志名称的确定方法感兴趣。 因此,如果先前已安装该服务,则会删除源,或在后续安装期间重用该源。

我还没有机会学习WiX来尝试这条路线。

最好的建议是不要在Visual Studio中使用安装项目。 它有非常严重的局限性。 我用WiX做了很好的结果

ServiceInstaller类自动创建EventLogInstaller并将其放在自己的Installers集合中。

试试这段代码:

 ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller(); serviceProcessInstaller.Password = null; serviceProcessInstaller.Username = null; serviceProcessInstaller.Account = ServiceAccount.LocalSystem; // serviceInstaller ServiceInstaller serviceInstaller = new ServiceInstaller(); serviceInstaller.ServiceName = "MyService"; serviceInstaller.DisplayName = "My Service"; serviceInstaller.StartType = ServiceStartMode.Automatic; serviceInstaller.Description = "My Service Description"; // kill the default event log installer serviceInstaller.Installers.Clear(); // Create Event Source and Event Log EventLogInstaller logInstaller = new EventLogInstaller(); logInstaller.Source = "MyService"; // use same as ServiceName logInstaller.Log = "MyLog"; // Add all installers this.Installers.AddRange(new Installer[] { serviceProcessInstaller, serviceInstaller, logInstaller }); 

这里有几件事

动态创建事件日志和源代码非常不受欢迎。 主要是因为执行操作所需的权利 – 你真的不想用这种力量祝福你的应用程序。

此外,如果删除事件日志或源,则只有在服务器重新启动时才会真正删除该条目,因此如果删除并重新创建条目而不弹出框,则可以进入奇怪的状态。 由于元数据存储在注册表中的方式,还有一些关于命名冲突的不成文规则。

建议的路径是安装程序脚本和installutil,或Windows安装例程。

我必须同意stephbu关于事件日志进入的“怪异状态”,我之前遇到过。 如果我猜,你的一些困难就在那里。

但是,我知道在应用程序中执行事件记录的最佳方法实际上是使用TraceListener。 您可以通过服务的app.config配置它们:

http://msdn.microsoft.com/en-us/library/system.diagnostics.eventlogtracelistener.aspx

该页面中间附近有一个部分描述了如何使用EventLog属性指定要写入的EventLog。

希望有所帮助。

我也遵循了helb的建议,除了我基本上使用标准设计器生成的类(默认对象“ServiceProcessInstaller1”和“ServiceInstaller1”)。 我决定发布这个,因为它是一个稍微简单的版本; 而且因为我在VB工作,人们有时喜欢看VB方式。

正如tartheode所说,您不应该在ProjectInstaller.Designer.vb文件中修改设计器生成的ProjectInstaller类,但是您可以修改ProjectInstaller.vb文件中的代码。 在创建一个普通的ProjectInstaller之后(使用标准的“Add Installer”机制),我所做的唯一更改是在ProjectInstaller类的New()中。 在正常的“InitializeComponent()”调用之后,我插入了这段代码:

  ' remove the default event log installer Me.ServiceInstaller1.Installers.Clear() ' Create an EventLogInstaller, and set the Event Source and Event Log Dim logInstaller As New EventLogInstaller logInstaller.Source = "MyServiceName" logInstaller.Log = "MyCustomEventLogName" ' Add the event log installer Me.ServiceInstaller1.Installers.Add(logInstaller) 

这按预期工作,因为安装程序在应用程序日志中创建事件源,而是在新的自定义日志文件中创建。

但是,我已经搞砸了,我在一台服务器上有点乱。 自定义日志的问题在于,如果事件源名称与错误的日志文件相关联(例如,“应用程序”日志而不是新的自定义日志),则必须首先删除源名称; 然后机器重新启动; 然后可以创建与正确日志关联的源。 Microsoft帮助明确指出(在EventLogInstaller类描述中 ):

如果Source属性与为计算机上的其他事件日志注册的源名称匹配,则Install方法将引发exception。

因此,我的服务中也有此function,在服务启动时调用:

  Private Function EventLogSourceNameExists() As Boolean 'ensures that the EventSource name exists, and that it is associated to the correct Log Dim EventLog_SourceName As String = Utility.RetrieveAppSetting("EventLog_SourceName") Dim EventLog_LogName As String = Utility.RetrieveAppSetting("EventLog_LogName") Dim SourceExists As Boolean = EventLog.SourceExists(EventLog_SourceName) If Not SourceExists Then ' Create the source, if it does not already exist. ' An event log source should not be created and immediately used. ' There is a latency time to enable the source, it should be created ' prior to executing the application that uses the source. 'So pass back a False to cause the service to terminate. User will have 'to re-start the application to make it work. This ought to happen only once on the 'machine on which the service is newly installed EventLog.CreateEventSource(EventLog_SourceName, EventLog_LogName) 'create as a source for the SMRT event log Else 'make sure the source is associated with the log file that we want Dim el As New EventLog el.Source = EventLog_SourceName If el.Log <> EventLog_LogName Then el.WriteEntry(String.Format("About to delete this source '{0}' from this log '{1}'. You may have to kill the service using Task Manageer. Then please reboot the computer; then restart the service two times more to ensure that this event source is created in the log {2}.", _ EventLog_SourceName, el.Log, EventLog_LogName)) EventLog.DeleteEventSource(EventLog_SourceName) SourceExists = False 'force a close of service End If End If Return SourceExists End Function 

如果函数返回False,则服务启动代码只会停止服务。 此函数几乎可确保您最终获得与正确的事件日志文件关联的正确事件源名称。 您可能需要重启机器一次; 并且您可能不得不尝试多次启动该服务。

我刚刚在MSDN论坛上发布了一个解决方案,这是我设法使用标准的安装MSI项目来解决这个问题。 我所做的是将代码添加到PreInstall和Committed事件中,这意味着我可以完全按照原样保留其他所有内容:

 SortedList eventSources = new SortedList(); private void serviceProcessInstaller_BeforeInstall(object sender, InstallEventArgs e) { RemoveServiceEventLogs(); } private void RemoveServiceEventLogs() { foreach (Installer installer in this.Installers) if (installer is ServiceInstaller) { ServiceInstaller serviceInstaller = installer as ServiceInstaller; if (EventLog.SourceExists(serviceInstaller.ServiceName)) { eventSources.Add(serviceInstaller.ServiceName, EventLog.LogNameFromSourceName(serviceInstaller.ServiceName, Environment.MachineName)); EventLog.DeleteEventSource(serviceInstaller.ServiceName); } } } private void serviceProcessInstaller_Committed(object sender, InstallEventArgs e) { RemoveServiceEventLogs(); foreach (KeyValuePair eventSource in eventSources) { if (EventLog.SourceExists(eventSource.Key)) EventLog.DeleteEventSource(eventSource.Key); EventLog.CreateEventSource(eventSource.Key, eventSource.Value); } } 

代码可以进一步修改,只删除尚未存在的事件源或创建它们(虽然logname需要存储在安装程序的某处),但由于我的应用程序代码实际上在运行时创建了事件源那对我来说没有意义。 如果已有事件,那么应该已经存在事件源。 要确保创建它们,您只需自动启动该服务即可。

我经历了一些类似的怪异行为,因为我试图注册一个与我开始的服务同名的事件源。

我注意到您还将DisplayName设置为与事件Source相同的名称。

在启动服务时,我们发现Windows在应用程序日志中记录了“Service started successfully”条目,其中source为DisplayName。 这似乎具有将应用程序名称注册到应用程序日志的效果。

在我的事件记录器类中,我后来尝试将Application Name注册为具有不同事件日志的源,但是当添加新的事件日志条目时,它们总是被添加到应用程序日志中。

我也多次获得“Source中的事件ID(0)的描述”消息。

作为一种解决方法,我只是使用与DisplayName略有不同的名称注册消息源,并且从那时起它就一直工作。 如果你还没有,那值得尝试。

我遇到了同样的问题。 在我的情况下,似乎Windows安装程序正在添加与我的服务同名的事件源,这似乎会导致问题。 您是否为Windows服务和日志源使用相同的名称? 尝试更改它,以便以不同于服务名称的方式调用事件日志源。

问题来自installutil,默认情况下,在“Application”EventLog中使用您的服务名称注册事件源。 我仍在寻找一种方法来阻止它做这个垃圾。 如果一个人可以影响installutil的行为,那将是非常好的:(

根据helb的建议解决了我的问题。 在其示例中指示的位置杀死默认事件日志安装程序会阻止安装程序在应用程序事件日志下自动注册我的Windows服务。

试图解决这个令人沮丧的怪癖的时间太多了。 太感谢了!

FWIW,我无法修改设计器生成的ProjectInstaller类中的代码,而不会导致VS关于mods。 我取消了设计器生成的代码并手动输入了类。

将空注册表项添加到HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ services \ eventlog \ Application \ MY_CUSTOM_SOURCE_NAME_HERE似乎工作正常。

更改默认行为(即,项目安装程序在应用程序日志中使用服务名称创建事件日志源)的简单方法是轻松修改项目安装程序的构造函数,如下所示:

 [RunInstaller( true )] public partial class ProjectInstaller : System.Configuration.Install.Installer { public ProjectInstaller() { InitializeComponent(); //Skip through all ServiceInstallers. foreach( ServiceInstaller ThisInstaller in Installers.OfType() ) { //Find the first default EventLogInstaller. EventLogInstaller ThisLogInstaller = ThisInstaller.Installers.OfType().FirstOrDefault(); if( ThisLogInstaller == null ) continue; //Modify the used log from "Application" to the same name as the source name. This creates a source in the "Applications and Services log" which separates your service logs from the default application log. ThisLogInstaller.Log = ThisLogInstaller.Source; } } }