Каков наиболее надежный способ создания настраиваемого журнала событий и источника событий при установке .Net Service? - PullRequest
25 голосов
/ 22 сентября 2008

У меня проблемы с надежным созданием / удалением источников событий во время установки моей .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 });

Указанные методы фасадов просто возвращают строки для имени журнала, службы и т. Д.

Этот код работает большую часть времени, но недавно после установки я начал получать свои записи в журнале приложений, а не в пользовательском журнале. И в журнале тоже есть следующие ошибки:

Описание для идентификатора события (0) в источнике (AutoCheckout) не может быть найдено. Локальный компьютер может не иметь необходимой информации реестра или файлов DLL сообщений для отображения сообщений с удаленного компьютера. Вы можете использовать флаг / AUXSOURCE =, чтобы получить это описание; см. Помощь и Поддержка для деталей.

По какой-то причине он либо неправильно удаляет источник во время удаления, либо не создает его во время установки.

Любая помощь с лучшими практиками здесь приветствуется.

Спасибо!

Кроме того, вот пример того, как я пишу исключения в журнал:

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

Относительно ответа stephbu: Рекомендуемый путь - это скрипт установщика и installutil или подпрограмма установки Windows.

Я использую проект установки, который выполняет установку службы и настраивает журнал. Использую ли я installutil.exe или проект установки Windows, я считаю, что они оба вызывают один и тот же класс ProjectInstaller, который я показал выше.

Я вижу, как состояние моего тестового компьютера могло вызывать ошибку, если журнал действительно не был удален до перезагрузки. Я буду экспериментировать больше, чтобы увидеть, решит ли это проблему.

Edit: Меня интересует надежный способ регистрации источника и имени журнала при установке сервиса. Поэтому, если служба была ранее установлена, она удалила бы источник или использовала бы его повторно при последующих установках.

У меня еще не было возможности изучить WiX, чтобы попробовать этот маршрут.

Ответы [ 12 ]

25 голосов
/ 17 августа 2009

Класс ServiceInstaller автоматически создает EventLogInstaller и помещает его в собственную коллекцию установщиков.

Попробуйте этот код:

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
});
7 голосов
/ 22 сентября 2008

Пара вещей здесь

Создание журналов событий и источников на лету довольно неодобрительно. в первую очередь из-за прав, необходимых для выполнения действия - вы на самом деле не хотите благословлять свои приложения этой силой.

Более того, если вы удаляете журнал событий или источник, запись только верно удаляется при перезагрузке сервера, поэтому вы можете войти в странное состояние, если удаляете и воссоздаете записи, не отсекая поле. Существует также множество неписаных правил о конфликтах имен из-за способа хранения метаданных в реестре.

Рекомендуемый путь - это скрипт установщика и installutil или подпрограмма установки Windows.

5 голосов
/ 01 октября 2008

Лучшая рекомендация - не использовать проект установки в Visual Studio. Это имеет очень серьезные ограничения. У меня были очень хорошие результаты с WiX

2 голосов
/ 09 сентября 2010

Я также следовал предложению helb , за исключением того, что я в основном использовал классы, сгенерированные стандартным конструктором (объекты по умолчанию "ServiceProcessInstaller1" и "ServiceInstaller1"). Я решил опубликовать это, так как это немного более простая версия; а также потому, что я работаю в VB, и людям иногда нравится видеть VB-путь.

Как сказал tartheode , вам не следует изменять сгенерированный дизайнером класс ProjectInstaller в файле ProjectInstaller.Designer.vb , но вы можете изменить код в файле ProjectInstaller.vb . После создания обычного ProjectInstaller (с использованием стандартного механизма «Add Installer») единственное изменение, которое я сделал, было в New () класса ProjectInstaller. После обычного вызова 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 ):

Метод Install выдает исключение если свойство Source соответствует имя источника, зарегистрированное для другой журнал событий на компьютере.

Поэтому у меня также есть эта функция в моей службе, которая вызывается при запуске службы:

   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, код запуска службы просто останавливает службу. Эта функция в значительной степени гарантирует, что вы в конечном итоге получите правильное имя источника события, связанное с правильным файлом журнала событий. Возможно, вам придется перезагрузить машину один раз; и вам, возможно, придется попробовать запустить службу более одного раза.

2 голосов
/ 01 октября 2008

Я должен согласиться со stephbu относительно «странных состояний», в которые попадает журнал событий, я сталкивался с этим раньше. Если бы я догадался, некоторые из ваших трудностей лежат там.

Однако лучший из известных мне способов ведения журнала событий в приложении - это использование TraceListener. Вы можете настроить их через app.config службы:

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

В середине этой страницы есть раздел, в котором описано, как использовать свойство EventLog для указания EventLog, в который вы хотите записать.

Надеюсь, это поможет.

1 голос
/ 10 апреля 2011

Я только что опубликовал решение этой проблемы на форумах MSDN, и мне удалось обойти это с помощью стандартного проекта MSI. Что я сделал, так это добавил код к событиям PreInstall и Committed, что означало, что я могу сохранить все остальное в точности так, как было:

SortedList<string, string> eventSources = new SortedList<string, string>();
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<string, string> eventSource in eventSources)
  {
    if (EventLog.SourceExists(eventSource.Key))
      EventLog.DeleteEventSource(eventSource.Key);

    EventLog.CreateEventSource(eventSource.Key, eventSource.Value);
  }
}

Код можно изменить немного дальше, чтобы удалить только источники событий, которые еще не существовали, или создать их (хотя логин должен был бы быть сохранен где-то напротив установщика), но так как мой код приложения фактически создает источники событий поскольку это бежит тогда, нет никакого смысла для меня. Если уже есть события, то уже должен быть источник события. Чтобы убедиться, что они созданы, вы можете просто автоматически запустить службу.

1 голос
/ 06 мая 2009

У меня такие же проблемы. В моем случае кажется, что установщик Windows автоматически добавляет источник события, имя которого совпадает с именем моей службы, и это может вызвать проблемы. Вы используете одно и то же имя для службы Windows и для источника журнала? Попробуйте изменить его так, чтобы ваш источник журнала событий назывался не так, как имя службы.

0 голосов
/ 23 января 2015

Простой способ изменить поведение по умолчанию (то есть, что установщик проекта создает источник журнала событий с именем вашей службы в журнале приложения) - это легко изменить конструктор установщика проекта следующим образом:

[RunInstaller( true )]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
    public ProjectInstaller()
    {
        InitializeComponent();

        //Skip through all ServiceInstallers.
        foreach( ServiceInstaller ThisInstaller in Installers.OfType<ServiceInstaller>() )
        {
            //Find the first default EventLogInstaller.
            EventLogInstaller ThisLogInstaller = ThisInstaller.Installers.OfType<EventLogInstaller>().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;
        }
    }
}
0 голосов
/ 27 февраля 2013

Добавление пустого ключа реестра в HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ services \ eventlog \ Application \ MY_CUSTOM_SOURCE_NAME_HERE, кажется, работает нормально.

0 голосов
/ 11 сентября 2009

После предложения Хельба я решил эту проблему. Уничтожение установщика журнала событий по умолчанию в точке, указанной в его примере, не позволило установщику автоматически зарегистрировать мою службу Windows в журнале событий приложений.

Слишком много времени было потеряно при попытке решить эту неприятную причуду. Спасибо миллион!

FWIW, я не мог изменить код в своем сгенерированном дизайнером классе ProjectInstaller, не заставляя VS беспокоиться о модах. Я удалил сгенерированный дизайнером код и вручную вошел в класс.

...