c# - ¿Cuál es la forma más confiable de crear un registro de eventos personalizado y un origen de eventos durante la instalación de un servicio.Net?
windows-services event-log (12)
Tengo dificultades para crear / eliminar fuentes de eventos de manera confiable durante la instalación de mi servicio .Net Windows.
Aquí está el código de mi clase 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 });
Los métodos de fachada a los que se hace referencia simplemente devuelven las cadenas para el nombre del registro, servicio, etc.
Este código funciona la mayor parte del tiempo, pero recientemente, después de la instalación, comencé a mostrar mis entradas de registro en el Registro de la aplicación en lugar del registro personalizado. Y los siguientes errores están también en el registro:
La descripción para la identificación del evento (0) en la fuente (AutoCheckout) no se puede encontrar. Es posible que la computadora local no tenga la información de registro o los archivos DLL de mensajes necesarios para mostrar mensajes desde una computadora remota. Puede usar el indicador / AUXSOURCE = para recuperar esta descripción; ver Ayuda y Soporte para más detalles.
Por algún motivo, no está eliminando correctamente la fuente durante la desinstalación o no la está creando durante la instalación.
Cualquier ayuda con las mejores prácticas aquí es apreciada.
¡Gracias!
Además, aquí hay una muestra de cómo estoy escribiendo excepciones al registro:
// Write to Log
EventLog.WriteEntry(Facade.GetEventLogSource(), errorDetails, EventLogEntryType.Error, 99);
Con respecto a la respuesta de stephbu: la ruta recomendada es una secuencia de comandos del instalador e installutil, o una rutina de instalación de Windows.
Estoy usando un Proyecto de configuración, que realiza la instalación del servicio y configura el registro. Ya sea que use el installutil.exe o el proyecto de instalación de Windows, creo que ambos llaman a la misma clase ProjectInstaller que muestro arriba.
Veo cómo el estado de mi máquina de prueba podría estar causando el error si el registro no se elimina realmente hasta que se reinicia. Voy a experimentar más para ver si eso resuelve el problema.
Editar: estoy interesado en una forma segura de registrar el origen y el nombre de registro durante la instalación del servicio. Por lo tanto, si el servicio se había instalado previamente, eliminaría la fuente o reutilizaría la fuente durante las instalaciones posteriores.
Todavía no he tenido la oportunidad de aprender WiX para probar esa ruta.
Tengo que estar de acuerdo con stephbu acerca de los "estados extraños" que entra en el registro de eventos, me he encontrado con eso antes. Si tuviera que adivinar, algunas de tus dificultades yacen allí.
Sin embargo, la mejor manera que conozco para hacer un registro de eventos en la aplicación es en realidad con un TraceListener. Puede configurarlos a través de app.config del servicio:
http://msdn.microsoft.com/en-us/library/system.diagnostics.eventlogtracelistener.aspx
Hay una sección cerca del medio de esa página que describe cómo usar la propiedad EventLog para especificar la EventLog en la que desea escribir.
Espero que ayude.
Experimenté un comportamiento extraño similar porque traté de registrar una fuente de evento con el mismo nombre que el servicio que estaba comenzando.
Observé que también tiene DisplayName configurado con el mismo nombre que su fuente de evento.
Al iniciar el servicio, descubrimos que Windows registró una entrada de "Servicio iniciado correctamente" en el registro de la aplicación, con la fuente como DisplayName. Esto pareció tener el efecto de registrar el Nombre de la aplicación con el registro de la aplicación.
En mi clase de registrador de eventos intenté registrar Application Name como fuente con un registro de eventos diferente, pero cuando se trataba de agregar nuevas entradas de registro de eventos, siempre se agregaban al registro de la aplicación.
También recibí el mensaje "La descripción de la identificación de evento (0) en la fuente" varias veces.
Como solución, simplemente registré el origen del mensaje con un nombre ligeramente diferente a DisplayName, y se ha trabajado desde entonces. Valdría la pena intentar esto si aún no lo has hecho.
Estoy teniendo los mismos problemas. En mi caso, parece que el instalador de Windows está agregando el origen del evento que es del mismo nombre que mi servicio automáticamente y esto parece causar problemas. ¿Está usando el mismo nombre para el servicio de Windows y para el origen de registro? Intente cambiarlo para que su fuente de registro de eventos se llame de manera diferente que el nombre del servicio.
El problema proviene de installutil, que de forma predeterminada registra un origen de eventos con su nombre de servicios en la "Aplicación" EventLog. Todavía estoy buscando una forma de detenerlo haciendo esta mierda. Sería realmente bueno si uno pudiera influir en el comportamiento de installutil :(
Seguir la sugerencia de helb resolvió el problema para mí. La eliminación del instalador de registro de eventos predeterminado, en el punto indicado en su ejemplo, impidió que el instalador registre automáticamente mi Servicio de Windows en el registro de eventos de la aplicación.
Se perdió demasiado tiempo tratando de resolver este capricho frustrante. ¡Un millón de gracias!
FWIW, no pude modificar el código dentro de mi clase ProjectInstaller generada por un diseñador sin provocar que VS se interese por las modificaciones. Eliminé el código generado por el diseñador e ingresé manualmente la clase.
También seguí la sugerencia de helb , excepto que básicamente utilicé las clases generadas por el diseñador estándar (los objetos por defecto "ServiceProcessInstaller1" y "ServiceInstaller1"). Decidí publicar esto ya que es una versión un poco más simple; y también porque estoy trabajando en VB y la gente a veces le gusta ver la VB-way.
Como dijo tartheode , no debe modificar la clase ProjectInstaller generada por el diseñador en el archivo ProjectInstaller.Designer.vb , pero puede modificar el código en el archivo ProjectInstaller.vb . Después de crear un ProjectInstaller normal (usando el mecanismo estándar ''Add Installer''), el único cambio que hice fue en la clase New () de ProjectInstaller. Después de la llamada normal "InitializeComponent ()", inserté este código:
'' 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)
Esto funcionó como se esperaba, ya que el instalador no creó el origen del evento en el registro de la aplicación, sino que se creó en el nuevo archivo de registro personalizado.
Sin embargo, había jodido lo suficiente como para tener un lío en un servidor. El problema con los registros personalizados es que si el nombre de origen del evento está asociado al archivo de registro incorrecto (por ejemplo, el registro de "Aplicación" en lugar de su nuevo registro personalizado), primero debe eliminarse el nombre de origen; entonces la máquina reinició; entonces la fuente se puede crear con asociación al registro correcto. La Ayuda de Microsoft establece claramente (en la descripción de la clase EventLogInstaller ):
El método Install arroja una excepción si la propiedad Source coincide con un nombre de fuente que está registrado para un registro de eventos diferente en la computadora.
Por lo tanto, también tengo esta función en mi servicio, que se llama cuando comienza el servicio:
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 función devuelve False, el código de inicio del servicio simplemente detiene el servicio. Esta función prácticamente garantiza que eventualmente obtendrá el nombre correcto de la Fuente de Evento asociado al archivo de Registro de Eventos correcto. Es posible que deba reiniciar la máquina una vez; y es posible que deba intentar comenzar el servicio más de una vez.
Acabo de publicar una solución para esto en los foros de MSDN, que fue a la que logré solucionar esto utilizando un proyecto de instalación estándar de MSI. Lo que hice fue agregar código a los eventos PreInstall y Committed, lo que significaba que podía mantener todo lo demás exactamente como estaba:
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);
}
}
El código podría modificarse un poco más para eliminar solo los orígenes de eventos que no existían o crearlos (aunque el nombre de registro debería almacenarse en algún lugar contra el instalador) pero dado que mi código de aplicación realmente crea los orígenes de eventos mientras se ejecuta entonces no tiene sentido para mí. Si ya hay eventos, entonces ya debería haber una fuente de evento. Para asegurarse de que se creen, puede iniciar el servicio automáticamente.
La clase ServiceInstaller
crea automáticamente un EventLogInstaller
y lo coloca dentro de su propia colección de Instaladores.
Prueba este código:
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
});
Agregar una clave de registro vacía a HKEY_LOCAL_MACHINE / SYSTEM / CurrentControlSet / services / eventlog / Application / MY_CUSTOM_SOURCE_NAME_HERE parece funcionar bien.
Una manera fácil de cambiar el comportamiento predeterminado (es decir, que el instalador del proyecto cree un origen de registro de eventos con el nombre de su servicio en el registro de la aplicación) es modificar fácilmente el constructor del instalador del proyecto de la siguiente manera:
[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;
}
}
}
Un par de cosas aquí
Crear registros de eventos y fuentes sobre la marcha es bastante desaprobado. principalmente debido a los derechos requeridos para realizar la acción; realmente no desea bendecir sus aplicaciones con ese poder.
Además, si elimina un registro de eventos o una fuente, la entrada solo se borrará cuando el servidor se reinicie, de modo que puede entrar en estados extraños si elimina y vuelve a crear las entradas sin rebotar en la casilla. También hay un montón de reglas no escritas sobre conflictos de nombres debido a la forma en que los metadatos se almacenan en el registro.
La ruta recomendada es una secuencia de comandos del instalador e installutil, o una rutina de instalación de Windows.