Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

How to Build a Simple Event Log Montior/Watcher (Using TCP in .NET)

4.60/5 (22 votes)
5 Nov 2007CPOL3 min read 1   1  
How to build a simple event log montior/watcher (using TCP in .NET) to monitor event log changes on a remote machine(s).

Introduction

It was a dark and stormy night...and servers kept crashing...

.NET allows a developer to attach a "handler" to monitor for event log changes:

VB
    ...
    Dim objLog As EventLog = New EventLog("Application")
    AddHandler objLog.EntryWritten, AddressOf ApplicationLog_OnEntryWritten
    objLog.EnableRaisingEvents = True
    ...
Public Sub ApplicationLog_OnEntryWritten(ByVal [source] As Object, _
           ByVal e As EntryWrittenEventArgs)
    Try
           'handle event log change here

    Catch err As Exception
    'oops

    End Try
End Sub

The only problem with this approach is it does not allow to monitor for event log changes on a remote machine. See Microsoft support article 815314.

Image 1

The code bellow allows you to build a simple event log "watcher" application to monitor event log changes on a remote machine(s).

The Log Monitor Application ("Log Watcher") consists of two components:

  • An Event Log Monitoring Service - responsible for monitoring event log changes on a machine.
  • Centralized "Watcher Host" - Listener - responsible for gathering data from log monitor services installed on different machines.

First, let's build a service component - responsible for "keeping an eye on" the event log on a machine. To create a service application (i.e., an application that runs as a service):

Image 2

An application that runs as a service has a few events (inherited from System.ServiceProcess.ServiceBase):

  • OnStart() - occurs when the service receives a Start command.
  • OnStop() - occurs when the service receives a Stop command.
  • OnPause() and OnContinue() - occurs when the service receives a Pause/Resume command.

For this application, we only need the OnStart() and OnStop() events.

VB
...
Private m_LocalIP As String = System.Net.Dns.GetHostName()
'points to machine service is running on

Private m_Watcher_IP As String
'Listening Log Watcher Host IP

Private m_Watcher_Port As String
'Listening Log Watcher Host Port

Private m_ErrorLogFile As String
'Log file where we can log useful information while service is running

...

Protected Overrides Sub OnStart(ByVal args() As String)
    ' Add code here to start your service. This method should set things

    ' in motion so your service can do its work.


    'open config file:    LogMonitoringService.exe.config

    m_Watcher_IP = _
      System.Configuration.ConfigurationSettings.AppSettings.Get("watcher_ip")
    m_Watcher_Port = _
      System.Configuration.ConfigurationSettings.AppSettings.Get("watcher_port")
    m_ErrorLogFile = Path.Combine(GetApplicationDirectory(), _
                     "log_monitoring_service_errors.txt")

    WorkerThread = New Thread(AddressOf WatchEventLog)
    WorkerThread.Start()
End Sub

The config file looks like this:

Image 3

So, when the service starts, we get the configuration settings and start the event log monitor:

VB
Public Sub WatchEventLog()
    Try
        m_LogWatcherLog = New EventLog()
        'just to make sure we have LogMonitoringService source registered

        Try
            m_LogWatcherLog.CreateEventSource("LogMonitoringService", "Application")
        Catch
        End Try

        m_LogWatcherLog.Close()
        m_LogWatcherLog = New EventLog("Application", ".", "LogMonitoringService")
        m_LogWatcherLog.Source = "LogMonitoringService"

        'make a record in Application log:    

        m_LogWatcherLog.WriteEntry("LogWacther started." & vbCrLf & _
                    "Send data to [" & m_Watcher_IP & ":" & m_Watcher_Port & "]" & vbCrLf & _
                    "Error file [" & m_ErrorLogFile & "]", _
                    EventLogEntryType.Information)

        'make a record in log file:    

        LogError("LogWacther started." & vbCrLf & _
                    "Send data to [" & m_Watcher_IP & ":" & m_Watcher_Port & "]" & vbCrLf & _
                    "Error file [" & m_ErrorLogFile & "]")

        ' "attach" to Application Log

        m_ApplicationLog = New EventLog()
        m_ApplicationLog.Log = "Application"
        AddHandler m_ApplicationLog.EntryWritten, AddressOf ApplicationLog_OnEntryWritten
        m_ApplicationLog.EnableRaisingEvents = True

        ' "attach" to System Log

        m_SystemLog = New EventLog()
        m_SystemLog.Log = "System"
        AddHandler m_SystemLog.EntryWritten, AddressOf SystemLog_OnEntryWritten
        m_SystemLog.EnableRaisingEvents = True

        m_run = True
        Do While (m_run)
            Thread.Sleep(10000)
        Loop

    Catch e As Exception
        Dim Log As New EventLog("Application")
        Log.WriteEntry("Failed to WatchEventLog:" & e.ToString, EventLogEntryType.Error)
        Log.Close()
        Log.Dispose()
    End Try
End Sub

As soon as a change in the Application or System event logs is detected...

VB
Public Sub ApplicationLog_OnEntryWritten(ByVal [source] As Object, _
                          ByVal e As EntryWrittenEventArgs)
    Try
        LogError("Application Log Entry:" & vbCrLf & "Message [" & e.Entry.Message & "]")

        SendEventLogEntryToHost("Application", e.Entry)

    Catch err As Exception
        LogError("Failed to ApplicationLog_OnEntryWritten:" & err.ToString())
    End Try
End Sub

Public Sub SystemLog_OnEntryWritten(ByVal [source] As Object, _
           ByVal e As EntryWrittenEventArgs)
    Try
        LogError("System Log Entry:" & vbCrLf & "Message [" & e.Entry.Message & "]")

        'send data to watcher

        SendEventLogEntryToHost("System", e.Entry)

    Catch err As Exception
        LogError("Failed to SystemLog_OnEntryWritten:" & err.ToString())
    End Try
End Sub

... the application will "contact" the watching host and send log entry data:

VB
Private Function SendEventLogEntryToHost(ByVal LogName As String, _
                 ByVal e As EventLogEntry) As Boolean
    Try
        Dim objTCP As Socket
        Dim remoteEndPoint As New IPEndPoint( _ 
               IPAddress.Parse(m_Watcher_IP), m_Watcher_Port)

        objTCP = New Socket( _ 
                remoteEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
        objTCP.Connect(remoteEndPoint)

        If objTCP.Connected Then
            LogError("objTCP socket connected to [" & _
                     remoteEndPoint.Address.ToString() & "]" & vbCrLf & _
                     " From Port [" & CType(objTCP.LocalEndPoint, _
                     IPEndPoint).Port.ToString() & "]")

            'send data to watcher host:

            Dim Message As String = e.TimeGenerated.ToString("MM/dd/yyyy HH:mm:ss") & _
                                   "|" & LogName & "|" & _
                                   e.EntryType.ToString() & "|" & _
                                   e.Message

            Dim sendBytes As Byte() = System.Text.Encoding.ASCII.GetBytes(Message)
            objTCP.Send(sendBytes, sendBytes.Length, SocketFlags.None)

            LogError("objTCP socket sent [" & sendBytes.Length & "] bytes")
        Else
            LogError("objTCP socket did not connected to [" & _
                     remoteEndPoint.Address.ToString() & "]")
        End If

        objTCP.Shutdown(SocketShutdown.Both)
        objTCP.Close()
        LogError("TCP client closed")

    Catch err As Exception
        LogError("Failed to SendEventLogEntryToHost:" & err.ToString())
    End Try
End Function

To make life easier, the service application sends the event log entry as a string:

VB
...
Dim Message As String = e.TimeGenerated.ToString("MM/dd/yyyy HH:mm:ss") & "|" & _
                        LogName & "|" & _
                        e.EntryType.ToString() & "|" & _
                        e.Message

Dim sendBytes As Byte() = System.Text.Encoding.ASCII.GetBytes(Message)
objTCP.Send(sendBytes, sendBytes.Length, SocketFlags.None)
...

And, the host will "split" the string into the corresponding fields.

The second part is to build the actual "watch dog" that will receive the notifications from the event log watchers.

For this, a regular Windows application will do just fine. And the idea is simple - start a TCP listener and wait for data from the event log watchers.

Image 4

Image 5

VB
...
...
Private m_ListenerMonitorPort As Integer
Friend m_objListener_Monitor As TcpListener
...
...

Private Sub frmLogMonitor_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ 
            Handles MyBase.Load

    Me.Cursor = Cursors.WaitCursor

    m_ListenerMonitorPort = _
      System.Configuration.ConfigurationSettings.AppSettings.Get("watcher_port")
    m_Notify = System.Configuration.ConfigurationSettings.AppSettings.Get("notify")

    lblPort.Text = "Listening for changes on port [" & _
                   m_ListenerMonitorPort & "]"
    lblNotify.Text = "Notify on change - [" & m_Notify & "]"

    'attach event handler: so we can monitor local events
    'we cannot monitor events on remote computer this way :
    'http://support.microsoft.com/?scid=kb;EN;815314
    'Receive Event Notifications

    'You can receive event notification when an entry 
    'is written to a particular log. To do this, 
    'implement the EntryWritten event handler for the instance of the EventLog. 
    'Also, set EnableRaisingEvents to true.
    'Note You can only receive event notifications 
    'when entries are written on the local computer. 
    'You cannot receive notifications for entries that are written on remote computers.
    'to monitor local event log:

    Dim objLog As EventLog = New EventLog("Application")
    AddHandler objLog.EntryWritten, AddressOf ApplicationLog_OnEntryWritten
    objLog.EnableRaisingEvents = True

    'remember form

    m_FormSize = Me.Size()
    m_FormLocation = Me.Location

    UpdateApplicationStatus("Application started. Port [" & _
          m_ListenerMonitorPort & "]. Notify [" & m_Notify & "]")

    Me.Cursor = Cursors.Default
End Sub

where the configuration file is logmonitor.exe.config:

Image 6

and the UpdateApplicationStatus() method simply adds the application events to the list box:

VB
Friend Sub UpdateApplicationStatus(ByVal Message As String)
    Message = System.DateTime.Now.ToString("HH:mm:ss") & " - " & Message
    lstApplicationEvents.Items.Add(Message)
End Sub

To start the monitoring process:

VB
Private Sub cmdStartListener_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles cmdStartListener.Click
    ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ListenForWatchers), Me)
End Sub

Where ListenForWatchers() opens a port for the listener and waits for incoming connections from the "log watcher" (entries from remote event logs go into the top list view control on the form):

Image 7

VB
Public Sub ListenForWatchers(ByVal objState As Object)
    Dim objUI As frmLogMonitor

    Try
        objUI = CType(objState, frmLogMonitor)

        m_objListener_Monitor = New TcpListener(m_ListenerMonitorPort)
        m_objListener_Monitor.Start()

        objUI.UpdateApplicationStatus("Started listening on port [" & _
          m_ListenerMonitorPort.ToString() & "]")

        Do
            Dim objClient As New Socket(AddressFamily.InterNetwork, _
                          SocketType.Stream, ProtocolType.Tcp)
            objClient = m_objListener_Monitor.AcceptSocket()

            Dim remoteEndPoint As IPEndPoint = CType(objClient.RemoteEndPoint, IPEndPoint)
            objUI.UpdateApplicationStatus("TCP connection from [" & _
                  remoteEndPoint.Address.ToString() & ":" & _
                  remoteEndPoint.Port.ToString() & "]")

            Do While objClient.Available = 0
                'wait...

                If Not objClient.Connected Then
                    'oops...we lost it...

                    Throw New System.Exception("!Did not receive data!Or Not Connected")
                End If
            Loop

            If objClient.Available > 0 Then
                Dim InBytes(objClient.Available) As Byte
                objClient.Receive(InBytes, objClient.Available, SocketFlags.None)
                Dim Message As String = _
                  Replace(System.Text.Encoding.ASCII.GetString(InBytes), Chr(0), "")

                Dim EventLogEntry() As String = Message.Split("|")
                Dim date_time As String = System.DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")
                Dim objItem As ListViewItem = lvwLogEntries.Items.Add(date_time)
                With objItem
                    .SubItems.Add(remoteEndPoint.Address.ToString())
                    .SubItems.Add(EventLogEntry(1))
                    .SubItems.Add(EventLogEntry(2))
                    .SubItems.Add(EventLogEntry(3))
                End With
            Else
                objUI.UpdateApplicationStatus("no data received from TCP connection [" & _
            remoteEndPoint.Address.ToString() & ":" & remoteEndPoint.Port.ToString() & "]")
            End If
        Loop Until False


    Catch err As Exception
        objUI.UpdateApplicationStatus("ListenForWatchers():Process TcpSocket Error [" & _
                                      err.Message & "] ")
    End Try
End Sub

Entries from local event logs go into the bottom list view control on the form:

Image 8

VB
Private Sub frmLogMonitor_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
    ...
    ...
    Dim objLog As EventLog = New EventLog("Application")
    AddHandler objLog.EntryWritten, AddressOf ApplicationLog_OnEntryWritten
    objLog.EnableRaisingEvents = True
    ...
    ...
End Sub

Public Sub ApplicationLog_OnEntryWritten(ByVal [source] As Object, _
                                         ByVal e As EntryWrittenEventArgs)
    Try

        Dim date_time As String = e.Entry.TimeGenerated.ToString("MM/dd/yyyy HH:mm:ss")
        Dim objItem As ListViewItem = _
            lvwLogEntries_OnEntryWritten_Handler.Items.Add(date_time)
        With objItem
            .SubItems.Add(e.Entry.MachineName.ToString())
            .SubItems.Add("Application")
            .SubItems.Add(e.Entry.EntryType.ToString())
            .SubItems.Add(e.Entry.Message)
        End With

    Catch err As Exception
        MessageBox.Show("Failed to process entry:" & vbCrLf & _
                      "-----------------------------------" & vbCrLf & _
                      err.Message & vbCrLf & _
                      "-----------------------------------", _
                      "OnEntryWritten Handler", _
                      MessageBoxButtons.OK, _
                      MessageBoxIcon.Exclamation)
    End Try
End Sub

And that's it!

The application can be extended to monitor only certain events, such as errors only, and/or events from certain event sources only, for example, from an MS SQL Server only.

To do this, we need to change the code in the log monitoring service just a little bit:

VB
Public Sub ApplicationLog_OnEntryWritten(ByVal [source] As Object, _
                          ByVal e As EntryWrittenEventArgs)
    Try
        If e.Entry.EntryType = EventLogEntryType.Error And _
                    e.Entry.Source = "MSSQLSERVER" Then

            LogError("Application Log Entry:" & vbCrLf & _
                     "Message [" & e.Entry.Message & "]")

            SendEventLogEntryToHost("Application", e.Entry)
        End If

    Catch err As Exception
        LogError("Failed to ApplicationLog_OnEntryWritten:" & err.ToString())
    End Try
End Sub

The application can send notifications not to just one log watching host, but to multiple ones. In this case, we need to modify SendEventLogEntryToHost() to pass an additional parameter - the host address (and, of course, we can add the Monitoring Host Port as well):

VB
Private Function SendEventLogEntryToHost(ByVal LogName As String, _
                                        ByVal MonitoringHost As String, _
                                        ByVal e As EventLogEntry) As Boolean
    Try
        'send data to watcher

        Dim objTCP As Socket
        Dim remoteEndPoint As New IPEndPoint(IPAddress.Parse(MonitoringHost), m_Watcher_Port)
        objTCP = New Socket(remoteEndPoint.Address.AddressFamily, _
                            SocketType.Stream, ProtocolType.Tcp)
        objTCP.Connect(remoteEndPoint)

        If objTCP.Connected Then
            LogError("objTCP socket connected to [" & _
                     remoteEndPoint.Address.ToString() & "]" & vbCrLf & _
                     " From Port [" & _ 
                     CType(objTCP.LocalEndPoint, IPEndPoint).Port.ToString() & "]")

            'send data to watcher host:

            Dim Message As String = e.TimeGenerated.ToString("MM/dd/yyyy HH:mm:ss") & "|" & _
                                   LogName & "|" & _
                                   e.EntryType.ToString() & "|" & _
                                   e.Message

            Dim sendBytes As Byte() = System.Text.Encoding.ASCII.GetBytes(Message)
            objTCP.Send(sendBytes, sendBytes.Length, SocketFlags.None)

            LogError("objTCP socket sent [" & sendBytes.Length & "] bytes")
        Else
            LogError("objTCP socket did not connected to [" & _
                     remoteEndPoint.Address.ToString() & "]")
        End If

        objTCP.Shutdown(SocketShutdown.Both)
        objTCP.Close()
        LogError("TCP client closed")

    Catch err As Exception
        LogError("Failed to SendEventLogEntryToHost:" & err.ToString())
    End Try
End Function

If you would like to read more on this story, please take a look at Siccolo - Free Mobile Management Tool For SQL Server and more articles at Siccolo articles.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)