Share via


Setting up An Event Listener and an Event Subscription

This topic describes the portion of the example that contains the code to create an event listener that is set to subscribe to specific events in an event log and receive notification when specific events are published in the event log. The listener monitors event logs based on an XPath-based query for a group of events that match a specified query criteria. The query filters events based on the event properties.

Example

Description

This portion of the code example uses the System.Diagnostics.Eventing.Reader namespace classes to subscribe to receive event notifications from the application event log. The EventLogQuery class is used to create a query for events that match certain criteria. The EventLogWatcher class is then used to create the subscription by setting an event handler method for the EventRecordWritten event. The event handler method is called when an event that matches the query criteria is published to the Windows Event log.

The following describes parts of the code and highlights the corresponding code examples.

  1. Create an instance of the EventLogQuery class by specifying a query string used to filter events, and the name or location of the event log to subscribe to. For more information about how to create an event query string, see Event Queries and Event XML.

    Dim logWatcher As EventLogWatcher = Nothing
    Dim session As New EventLogSession()
    Dim source As New EventLogQuery( _
        Settings.Default.LogPath, PathType.LogName, Settings.Default.EventQuery)
    Dim program As New EventListenerProgram()
    
    EventLogWatcher logWatcher = null;
    EventLogSession session = new EventLogSession();
    EventLogQuery source = new EventLogQuery(Settings.Default.LogPath, PathType.LogName, Settings.Default.EventQuery);
    EventListenerProgram program = new EventListenerProgram();
    
  2. (Optional) To subscribe to events on a remote computer, set the Session property to an instance of the EventLogSession class and specify the remote computer name, domain, and the user name and password used to connect to the remote computer.

  3. Create a new EventLogWatcher instance by passing in the EventLogQuery instance created in Step 1 to the EventLogWatcher constructor.

    logWatcher = New EventLogWatcher(source, _
        program.bookMarkToStartFrom, Settings.Default.ReadExistingEvents)
    
    logWatcher = new EventLogWatcher(source, program.bookMarkToStartFrom, Settings.Default.ReadExistingEvents);
    
  4. Create a callback method that will execute when an event is reported to the subscription. This method accepts arguments of type Object and EventRecordWrittenEventArgs.

    ''' <summary>
    ''' Called when a subscription is active and a qualified event has been received.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e">Contains information about the event.</param>
    Public Sub reader_EventRecordWritten(ByVal sender As Object, ByVal e As EventRecordWrittenEventArgs)
    
        Dim evtRec As EventRecord = e.EventRecord
    
        If Not e.EventException Is Nothing Then
            Environment.ExitCode = -1
            Console.WriteLine("Error! Unexpected failure to retrieve event {0}. This event will not be saved.", _
                e.EventException.ToString())
    
        Else
            SaveEvent(evtRec)
        End If
    
    End Sub
    
    /// <summary>
    /// Called when a subscription is active and a qualified event has been received.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e">Contains information about the event.</param>
    public void reader_EventRecordWritten(object sender, EventRecordWrittenEventArgs e)
    {
        EventRecord evtRec = e.EventRecord;
            if (e.EventException != null)
            {
                Environment.ExitCode = -1;
                Console.WriteLine("Error! Unexpected failure to retrieve event {0}. This event will not be saved.", e.EventException.ToString());
            }
            else
            {
                SaveEvent(evtRec);
            }
        }
    
  5. Set the EventRecordWritten event handler to a new event handler that points to the callback method created in Step 4.

    AddHandler logWatcher.EventRecordWritten, AddressOf program.reader_EventRecordWritten
    
    logWatcher.EventRecordWritten += new EventHandler<EventRecordWrittenEventArgs>(program.reader_EventRecordWritten);
    
  6. Set the Enabled property to true to start the event subscription and false to stop the event subscription.

  7. This is the portion of the example that processes the event data when a qualified event is received. Depending upon the value of the XMLMode parameter, it either saves the event data into a file or writes it to the database.

    ''' <summary>
    ''' Helps resolve XPath to an actual value in an event.
    ''' </summary>
    ''' <param name="xPathToRun">The xpath to use to get the value.</param>
    ''' <param name="arrivedEventXml">The Xml of the event to get the value from.</param>
    ''' <returns>the value if it is found, else null.</returns>
    Private Shared Function RunXPathOnDocument(ByVal xPathToRun As String, ByVal arrivedEventXml As String) As Object
    
        Dim xmlDoc As New XmlDocument()
    
        ' remove 'default' xmlns as it only complicates things here.
        arrivedEventXml = arrivedEventXml.Replace( _
        "xmlns='https://schemas.microsoft.com/win/2004/08/events/event'", "")
    
        xmlDoc.LoadXml("<?xml version=""1.0"" encoding=""utf-16"" standalone=""yes""?>" & Environment.NewLine & _
            arrivedEventXml)
    
        Dim selectedNode As XmlNode = xmlDoc.SelectSingleNode(xPathToRun)
    
        If Not selectedNode Is Nothing Then
    
            Dim Value As String = selectedNode.Value  ' if it's an attribute
    
            If selectedNode.ChildNodes.Count = 1 AndAlso _
                 selectedNode.FirstChild.NodeType = XmlNodeType.Text Then  ' it's an element text-value
    
                Value = selectedNode.FirstChild.Value
            End If
    
            Return Value
        End If
    
        Return Nothing
    End Function
    
    ''' <summary>
    ''' Given the XML for an event, loads the event into a row-format and adds it to the eventDataSet.
    ''' </summary>
    ''' <param name="EventXml">Event XML to load into DataSet.</param>
    ''' <param name="eventDataSet">The dataset to load event into.</param>
    Private Sub AddEventIntoDataSet(ByVal EventXml As String, ByRef eventDataSet As DataSet)
    
        Dim newRow As DataRow = eventDataSet.Tables("Event").NewRow()
    
        For Each de As DictionaryEntry In nameToXPathMapping
    
            newRow(de.Key) = RunXPathOnDocument(de.Value, EventXml)
        Next
    
        newRow("EventXml") = EventXml
        eventDataSet.Tables("Event").Rows.Add(newRow)
    End Sub
    
    ''' <summary>
    ''' Given a new event saves it to XML or DB, based on the current mode.
    ''' </summary>
    ''' <param name="evtRec">The new event to save.</param>
    Private Sub SaveEvent(ByVal evtRec As EventRecord)
    
        Dim eventXml As String = evtRec.ToXml()
    
        If Not Settings.Default.XmlMode Then
    
            Console.WriteLine("New event, log: {0} event#: {1} to Sql Db: {2} ...", _
                evtRec.LogName, savedEventCount, Settings.Default.DatabaseName)
    
            ' if this function throws the exception will appear on the main thread, 
            ' however because it's a callstack the source call-stack will appear to be some unknown address
            Dim theSqlDataAdapter As New SqlDataAdapter("select * from Event", sqlConn)
            Dim theSqlCommandBuilder As New SqlCommandBuilder(theSqlDataAdapter)
            theSqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
            theSqlDataAdapter.InsertCommand = theSqlCommandBuilder.GetInsertCommand()
    
            AddEventIntoDataSet(eventXml, eventDs)
    
            ' store only relevant changes in memory...
            eventDs = eventDs.GetChanges()
    
            If 0 = savedEventCount Mod Settings.Default.BatchSize Then  ' save in batches
    
                theSqlDataAdapter.Update(eventDs, "Event")
                SaveBookmark(evtRec.Bookmark)
            End If
    
        Else
    
            If (String.IsNullOrEmpty(Settings.Default.DatabaseName)) Then
                Throw New ArgumentException( _
                    String.Format("Setting SqlDatabaseName in App.config is null or empty, " & _
                    "it is used as the name for the XML file to save events to, please give " & _
                    "it a value."))
            End If
    
            ' Use the Database name as the name of the XML file to save to, get the filename only to save in the same dir.
            Dim XmlFilename As String = System.IO.Path.GetFileName(Settings.Default.DatabaseName) & ".xml"
    
            Console.WriteLine("New event, log: {0} event#: {1} to Xml {2}", _
                evtRec.LogName, savedEventCount, XmlFilename)
    
            AddEventIntoDataSet(eventXml, eventDs)
            eventDs.WriteXml(XmlFilename)
    
            SaveBookmark(evtRec.Bookmark)
        End If
    
        savedEventCount = savedEventCount + 1
    End Sub
    
    ''' <summary>
    ''' Resets the bookmark.
    ''' </summary>
    Private Sub ClearBookmark()
        bookMarkToStartFrom = Nothing
    End Sub
    
    ''' <summary>
    ''' Saves/Serializes a bookmark to the target file.
    ''' </summary>
    ''' <param name="BmFileName">The filepath to save to.</param>
    ''' <param name="evtBookmark">The bookmark to save.</param>
    Private Sub SaveBookmark(ByVal evtBookmark As EventBookmark)
    
        Dim formatter As New BinaryFormatter()
        fsBookmark.Seek(0, SeekOrigin.Begin)
        formatter.Serialize(fsBookmark, evtBookmark)
    End Sub
    
    
    ''' <summary>
    ''' Called when a subscription is active and a qualified event has been received.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e">Contains information about the event.</param>
    Public Sub reader_EventRecordWritten(ByVal sender As Object, ByVal e As EventRecordWrittenEventArgs)
    
        Dim evtRec As EventRecord = e.EventRecord
    
        If Not e.EventException Is Nothing Then
            Environment.ExitCode = -1
            Console.WriteLine("Error! Unexpected failure to retrieve event {0}. This event will not be saved.", _
                e.EventException.ToString())
    
        Else
            SaveEvent(evtRec)
        End If
    
    End Sub
    
    #region incoming_event_handler
    /// <summary>
    /// Helps resolve XPath to an actual value in an event.
    /// </summary>
    /// <param name="xPathToRun">The xpath to use to get the value.</param>
    /// <param name="arrivedEventXml">The Xml of the event to get the value from.</param>
    /// <returns>the value if it is found, else null.</returns>
    internal static object RunXPathOnDocument(string xPathToRun, string arrivedEventXml)
    {
        XmlDocument xmlDoc = new XmlDocument();
    
        //remove 'default' xmlns as it only complicates things here.
        arrivedEventXml = arrivedEventXml.Replace("xmlns='https://schemas.microsoft.com/win/2004/08/events/event'", "");
    
        xmlDoc.LoadXml("<?xml version=\"1.0\" encoding=\"utf-16\" standalone=\"yes\"?>\n" + 
                            arrivedEventXml);
    
        XmlNode selectedNode = xmlDoc.SelectSingleNode(xPathToRun);
    
        if (selectedNode != null)
        {
            string Value = selectedNode.Value; //if it's an attribute
    
            if (selectedNode.ChildNodes.Count == 1 &&
                 selectedNode.FirstChild.NodeType == XmlNodeType.Text) //it's an element text-value
            {
                Value = selectedNode.FirstChild.Value;
            }
    
            return Value;
        }
    
        return null;
    }
    /// <summary>
    /// Given the XML for an event, loads the event into a row-format and adds it to the eventDataSet.
    /// </summary>
    /// <param name="EventXml">Event XML to load into DataSet.</param>
    /// <param name="eventDataSet">The dataset to load event into.</param>
    internal void AddEventIntoDataSet(string EventXml, ref DataSet eventDataSet)
    {
        DataRow newRow = eventDataSet.Tables["Event"].NewRow();
    
        foreach (DictionaryEntry de in nameToXPathMapping)
        {
            newRow[(string)de.Key] = RunXPathOnDocument((string)de.Value, EventXml);
        }
    
        newRow["EventXml"] = EventXml;
        eventDataSet.Tables["Event"].Rows.Add(newRow);
    }
    
    /// <summary>
    /// Given a new event saves it to XML or DB, based on the current mode.
    /// </summary>
    /// <param name="evtRec">The new event to save.</param>
    internal void SaveEvent(EventRecord evtRec)
    {
        string eventXml = evtRec.ToXml();
    
        if (!Settings.Default.XmlMode)
        {
            Console.WriteLine("New event, log: {0} event#: {1} to Sql Db: {2} ...", evtRec.LogName, savedEventCount, Settings.Default.DatabaseName);
    
            //if this function throws the exception will appear on the main thread, 
            //however because it's a callstack the source call-stack will appear to be some unknown address
            SqlDataAdapter theSqlDataAdapter = new SqlDataAdapter("select * from Event", sqlConn);
            SqlCommandBuilder theSqlCommandBuilder = new SqlCommandBuilder(theSqlDataAdapter);
            theSqlDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
            theSqlDataAdapter.InsertCommand = theSqlCommandBuilder.GetInsertCommand();
    
            AddEventIntoDataSet(eventXml, ref eventDs);
    
            //store only relevant changes in memory...
            eventDs = eventDs.GetChanges();
    
            if (0 == savedEventCount % Settings.Default.BatchSize) //save in batches
            {
                theSqlDataAdapter.Update(eventDs, "Event");
                SaveBookmark(evtRec.Bookmark);
            }
        }
        else
        {
            if (string.IsNullOrEmpty(Settings.Default.DatabaseName))
            {
                throw new ArgumentException(String.Format("Setting SqlDatabaseName in App.config is null or empty, it is used as the name for the XML file to save events to, please give it a value."));
            }
    
            //Use the Database name as the name of the XML file to save to, get the filename only to save in the same dir.
            string XmlFilename = System.IO.Path.GetFileName(Settings.Default.DatabaseName) + ".xml";
    
            Console.WriteLine("New event, log: {0} event#: {1} to Xml {2}", evtRec.LogName, savedEventCount, XmlFilename);
    
            AddEventIntoDataSet(eventXml, ref eventDs);
            eventDs.WriteXml(XmlFilename);
    
            SaveBookmark(evtRec.Bookmark);
        }
    
        savedEventCount++;
    }
    
    /// <summary>
    /// Resets the bookmark.
    /// </summary>
    internal void ClearBookmark()
    {
        bookMarkToStartFrom = null;
    }
    
    /// <summary>
    /// Saves/Serializes a bookmark to the target file.
    /// </summary>
    /// <param name="BmFileName">The filepath to save to.</param>
    /// <param name="evtBookmark">The bookmark to save.</param>
    internal void SaveBookmark(EventBookmark evtBookmark)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        fsBookmark.Seek(0, SeekOrigin.Begin);
        formatter.Serialize(fsBookmark, evtBookmark);
    }
    /// <summary>
    /// Called when a subscription is active and a qualified event has been received.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e">Contains information about the event.</param>
    public void reader_EventRecordWritten(object sender, EventRecordWrittenEventArgs e)
    {
        EventRecord evtRec = e.EventRecord;
            if (e.EventException != null)
            {
                Environment.ExitCode = -1;
                Console.WriteLine("Error! Unexpected failure to retrieve event {0}. This event will not be saved.", e.EventException.ToString());
            }
            else
            {
                SaveEvent(evtRec);
            }
        }
    
    #endregion
    

Compiling the Code

This code example requires references to the System.dll and System.Core.dll files. Additionally, it references the System.Data.dll and System.Xml.dll to manipulate the event XML and write out the data to a SQL database.

See Also

Concepts

Event Log Scenarios
How to: Listen for Events and Store Them in a SQL Database
How to: Subscribe to Events in an Event Log

Send comments about this topic to Microsoft.

Copyright © 2007 by Microsoft Corporation. All rights reserved.