Quel est le moyen le plus fiable de créer un journal des événements personnalisé et une source d'événements lors de l'installation d'un service .Net

StackOverflow https://stackoverflow.com/questions/115488

Question

Je rencontre des difficultés lors de la création / suppression de sources d'événements lors de l'installation de mon service Windows .Net.

Voici le code de ma classe 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 });

Les méthodes de façade référencées renvoient simplement les chaînes contenant le nom du journal, du service, etc.

Ce code fonctionne la plupart du temps, mais récemment après l’installation, j’ai commencé à obtenir mes entrées de journal dans le journal des applications plutôt que dans le journal personnalisé. Et les erreurs suivantes sont également dans le journal:

  

La description de l'ID d'événement (0) dans la source (AutoCheckout) est introuvable. L'ordinateur local peut ne pas disposer des informations de registre nécessaires ou des fichiers DLL de message pour afficher les messages d'un ordinateur distant. Vous pourrez peut-être utiliser le drapeau / AUXSOURCE = pour récupérer cette description; voir Aide et support pour plus de détails.

Pour une raison quelconque, il ne supprime pas correctement la source lors de la désinstallation ou ne la crée pas lors de l'installation.

Toute aide sur les meilleures pratiques ici est appréciée.

Merci!

De plus, voici un exemple de la manière dont j'écris les exceptions dans le journal:

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

Concernant la réponse de stephbu: Le chemin recommandé est un script d’installation et installutil, ou une routine d’installation de Windows.

J'utilise un projet d'installation qui effectue l'installation du service et configure le journal. Que j'utilise le fichier installutil.exe ou le projet d'installation Windows, je pense qu'ils appellent tous les deux la même classe ProjectInstaller que celle présentée ci-dessus.

Je vois comment l'état de ma machine de test pourrait être à l'origine de l'erreur si le journal n'est pas véritablement supprimé jusqu'au redémarrage. Je vais expérimenter davantage pour voir si cela résout le problème.

Modifier: Je suis intéressé par un moyen infaillible pour enregistrer la source et le nom du journal lors de l'installation du service. Ainsi, si le service avait déjà été installé, il supprimerait la source ou la réutiliserait lors d’installations ultérieures.

Je n'ai pas encore eu l'occasion d'apprendre le WiX pour essayer cette voie.

Était-ce utile?

La solution

La meilleure recommandation serait de ne pas utiliser le projet d'installation dans Visual Studio. Il a des limitations très graves. J'ai eu de très bons résultats avec WiX

Autres conseils

La classe ServiceInstaller crée automatiquement un EventLogInstaller et le place dans sa propre collection Installers.

Essayez ce code:

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

Quelques choses ici

La création de journaux d’événements et de sources à la volée est très mal vue. principalement en raison des droits requis pour exécuter l'action - vous ne voulez pas vraiment bénir vos applications avec ce pouvoir.

De plus, si vous supprimez un journal des événements ou une source, l'entrée n'est véritablement qu'au moment du redémarrage du serveur. Vous pouvez donc entrer dans des états inhabituels si vous supprimez et recréez des entrées sans faire rebondir la boîte. Il existe également de nombreuses règles non écrites sur les conflits de noms en raison de la manière dont les métadonnées sont stockées dans le registre.

Le chemin recommandé est un script d’installation et installutil, ou une routine d’installation de Windows.

Je suis d'accord avec stephbu à propos des "états étranges". que le journal des événements entre dans, je l'ai déjà rencontré auparavant. Si je devais deviner, certaines de vos difficultés sont là.

Cependant, le meilleur moyen que je connaisse de consigner les événements dans l'application consiste en fait à utiliser TraceListener. Vous pouvez les configurer via le fichier app.config du service:

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

Au milieu de cette page, une section explique comment utiliser la propriété EventLog pour spécifier le journal des événements sur lequel vous souhaitez écrire.

L’espoir que cela aide.

J'ai également suivi la suggestion de helb , sauf que j'ai essentiellement utilisé les classes générées par le concepteur standard (les objets par défaut "ServiceProcessInstaller1" et "ServiceInstaller1"). J'ai décidé de poster ceci car c'est une version légèrement plus simple; et aussi parce que je travaille dans VB et que les gens aiment parfois voir le VB-way.

Comme le dit tartheode , vous ne devez pas modifier la classe ProjectInstaller générée par le concepteur dans le fichier ProjectInstaller.Designer.vb , mais vous pouvez modifiez le code dans le fichier ProjectInstaller.vb . Après avoir créé un ProjectInstaller normal (en utilisant le mécanisme standard 'Ajouter un installateur'), ??le seul changement que j'ai apporté a été fait dans le New () de la classe ProjectInstaller. Après la procédure normale "InitializeComponent ()" appeler, j’ai inséré ce code:

  ' 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)

Cela a fonctionné comme prévu, en ce sens que le programme d'installation n'a pas créé la source d'événements dans le journal des applications, mais créé dans le nouveau fichier journal personnalisé.

Cependant, j’avais eu assez de bêtises pour avoir un peu le bordel sur un serveur. Le problème avec les journaux personnalisés est que si le nom de source de l'événement associé au fichier journal incorrect (par exemple, le journal "Application" au lieu de votre nouveau journal personnalisé), le nom de la source doit d'abord être supprimé. ; puis la machine a redémarré; alors la source peut être créée en association avec le bon journal. L’aide de Microsoft indique clairement (dans la Description de la classe EventLogInstaller ):

  

La méthode d'installation lève une exception   si la propriété Source correspond à un   nom de source enregistré pour un   journal des événements différent sur l'ordinateur.

Par conséquent, j'ai également cette fonction dans mon service, appelé au démarrage du service:

   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

Si la fonction renvoie False, le code de démarrage du service arrête simplement le service. Cette fonction garantit à peu près que vous obtiendrez éventuellement le nom de source d'événement correct associé au fichier de journal des événements correct. Vous devrez peut-être redémarrer la machine une fois; et vous devrez peut-être essayer de démarrer le service plusieurs fois.

J'ai les mêmes problèmes. Dans mon cas, il semble que le programme d'installation de Windows ajoute automatiquement la source d'événements qui porte le même nom que mon service, ce qui semble poser problème. Utilisez-vous le même nom pour le service Windows et pour la source de journal? Essayez de le changer pour que votre source de journal des événements soit appelée différemment, puis le nom du service.

Je viens de publier une solution à ce problème sur les forums MSDN. J'ai réussi à le contourner à l'aide d'un projet MSI d'installation standard. Ce que j’ai fait, c’est d’ajouter du code aux événements PreInstall et Committed, ce qui signifie que je peux garder tout le reste tel quel:

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

Le code pourrait être modifié un peu plus loin pour ne supprimer que les sources d'événement qui n'existaient pas déjà ou les créer (bien que le nom du fichier journal doive être stocké quelque part par rapport au programme d'installation), mais comme mon code d'application crée réellement les sources d'événement comme ça marche alors ça ne sert à rien. S'il y a déjà des événements, il devrait déjà y avoir une source d'événements. Pour vous assurer qu'ils sont créés, vous pouvez simplement démarrer le service automatiquement.

J'ai eu un comportement étrange similaire en essayant d'enregistrer une source d'événements portant le même nom que le service que je démarrais.

Je remarque que DisplayName est également défini sur le même nom que votre source d'événement.

Lors du démarrage du service, nous avons constaté que Windows avait enregistré un " Service démarré avec succès " entrée dans le journal des applications, avec source comme Nom d'affichage. Cela semblait avoir pour effet d’enregistrer Nom de l’application dans le journal des applications.

Dans ma classe de consignateur d’événements, j’ai essayé par la suite d’enregistrer Nom de l’application en tant que source avec un journal des événements différent, mais lorsqu’il s’agissait d’ajouter de nouvelles entrées au journal des événements, elles étaient toujours ajoutées au journal des applications.

J'ai également reçu le " La description de l'ID d'événement (0) dans Source " message plusieurs fois.

En guise de solution de contournement, j'ai simplement enregistré la source du message sous un nom légèrement différent du nom d'affichage, et cela fonctionne depuis. Si vous ne l’avez pas déjà fait, cela vaudrait la peine d’être essayé.

Le problème provient de installutil qui, par défaut, enregistre une source d'événements avec le nom de vos services dans le champ "Application". Journal des événements. Je cherche toujours un moyen de l'empêcher de faire cette merde. Ce serait vraiment bien si on pouvait influencer le comportement de installutil: (

Suivre la suggestion de helb a résolu le problème pour moi. La suppression du programme d'installation du journal des événements par défaut, au point indiqué dans son exemple, l'empêchait d'enregistrer automatiquement mon service Windows dans le journal des événements de l'application.

Beaucoup trop de temps a été perdu pour tenter de résoudre ce caprice frustrant. Merci mille fois!

FWIW, je ne pouvais pas modifier le code de la classe ProjectInstaller générée par le concepteur sans que VS se moque des mods. J'ai supprimé le code généré par le concepteur et entré manuellement la classe.

L'ajout d'une clé de registre vide à HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ services \ Eventlog \ Application \ MY_CUSTOM_SOURCE_NAME_HERE semble fonctionner correctement.

Un moyen simple de changer le comportement par défaut (le programme d'installation du projet crée une source de journal des événements avec le nom de votre service dans le journal de l'application) consiste à modifier facilement le constructeur du programme d'installation du projet comme suit:

[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;
        }
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top