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

时间:2020-03-06 14:32:42  来源:igfitidea点击:

在安装.Net Windows Service期间,我很难可靠地创建/删除事件源。

这是我的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方法仅返回日志,服务等名称的字符串。

这段代码在大多数情况下都有效,但是最近安装之后,我开始在应用程序日志而不是自定义日志中显示我的日志条目。日志中也包含以下错误:

The description for Event ID ( 0 ) in Source ( AutoCheckout ) cannot be found. The local computer may not have the necessary registry information or message DLL files to display messages from a remote computer. You may be able to use the /AUXSOURCE= flag to retrieve this description; see Help and Support for details.

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

感谢我们提供最佳做法的帮助。

谢谢!

此外,这是我如何将异常写入日志的示例:

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

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

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

我看到如果直到重新启动后才真正删除日志,测试机的状态将如何导致错误。我将进行更多试验,看看是否可以解决问题。

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

我还没有机会学习WiX来尝试这种方法。

解决方案

这里的几件事

即时创建事件日志和源的想法很陌生。主要是由于执行操作所需的权限,我们实际上并不想让应用程序拥有这种功能。

此外,如果删除事件日志或者源,则仅在服务器重新引导时才真正删除该条目,因此,如果删除并重新创建条目而不用跳出框,则可能会进入较麻烦的状态。由于元数据在注册表中的存储方式,还有很多关于命名冲突的不成文规则。

推荐的路径是安装程序脚本和installutil或者Windows安装程序例程。

最好的建议是不要在Visual Studio中使用安装项目。它有非常严格的局限性。
WiX取得了很好的效果

我必须就事件日志进入的"怪异状态"与stephbu达成共识,我之前已经遇到过。如果我猜到了,一些困难就躺在那儿。

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

http://msdn.microsoft.com/zh-CN/library/system.diagnostics.eventlogtracelistener.aspx

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

希望能有所帮助。

我经历了一些类似的怪异行为,因为我尝试使用与我启动的服务相同的名称注册事件源。

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

启动服务时,我们发现Windows在应用程序日志中记录了"服务已成功启动"项,其来源为DisplayName。这似乎具有在应用程序日志中注册应用程序名称的效果。

在我的事件记录器类中,我后来尝试使用其他事件日志将应用程序名称注册为源,但是在添加新事件日志条目时,总是将它们添加到应用程序日志中。

我还多次收到"源中事件ID(0)的描述"消息。

作为解决方法,我只注册了与DisplayName稍有不同的名称的消息源,此后一直有效。如果我们还没有这样做,那么值得尝试一下。

我有同样的问题。就我而言,似乎Windows安装程序会自动添加与我的服务同名的事件源,这似乎会引起问题。 Windows服务和日志源是否使用相同的名称?尝试更改它,以便事件日志源的调用方式与服务名称的调用方式不同。

问题来自installutil,默认情况下,installutil在"应用程序" EventLog中使用服务名称注册事件源。我仍在寻找一种阻止这种垃圾的方法。如果可以影响installutil的行为,那就太好了:(

" 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
});

遵循赫尔伯的建议,为我解决了这个问题。在示例中指出的情况下,终止默认事件日志安装程序将阻止安装程序在"应用程序事件"日志下自动注册我的Windows服务。

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

FWIW,我不能修改设计器生成的ProjectInstaller类中的代码,而不会导致VS对mod提出质疑。我取消了设计师生成的代码,并手动输入了该类。

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

正如tartheode所说,我们不应在ProjectInstaller.Designer.vb文件中修改设计者生成的ProjectInstaller类,而可以在ProjectInstaller.vb文件中修改代码。创建普通的ProjectInstaller(使用标准的"添加安装程序"机制)后,我所做的唯一更改是在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类描述中):

The Install method throws an exception
  if the Source property matches a
  source name that is registered for a
  different event log on the computer.

因此,我的服务中也有此功能,该服务在启动时会被调用:

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,则服务启动代码将仅停止服务。此函数几乎可以确保我们最终将获得与正确的事件日志文件关联的正确的事件源名称。我们可能必须重新启动计算机一次;否则,可能需要重新启动计算机。并且我们可能不得不尝试多次启动该服务。