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:
...
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
Catch err As Exception
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.
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):
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.
...
Private m_LocalIP As String = System.Net.Dns.GetHostName()
Private m_Watcher_IP As String
Private m_Watcher_Port As String
Private m_ErrorLogFile As String
...
Protected Overrides Sub OnStart(ByVal args() As String)
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:
So, when the service starts, we get the configuration settings and start the event log monitor:
Public Sub WatchEventLog()
Try
m_LogWatcherLog = New EventLog()
Try
m_LogWatcherLog.CreateEventSource("LogMonitoringService", "Application")
Catch
End Try
m_LogWatcherLog.Close()
m_LogWatcherLog = New EventLog("Application", ".", "LogMonitoringService")
m_LogWatcherLog.Source = "LogMonitoringService"
m_LogWatcherLog.WriteEntry("LogWacther started." & vbCrLf & _
"Send data to [" & m_Watcher_IP & ":" & m_Watcher_Port & "]" & vbCrLf & _
"Error file [" & m_ErrorLogFile & "]", _
EventLogEntryType.Information)
LogError("LogWacther started." & vbCrLf & _
"Send data to [" & m_Watcher_IP & ":" & m_Watcher_Port & "]" & vbCrLf & _
"Error file [" & m_ErrorLogFile & "]")
m_ApplicationLog = New EventLog()
m_ApplicationLog.Log = "Application"
AddHandler m_ApplicationLog.EntryWritten, AddressOf ApplicationLog_OnEntryWritten
m_ApplicationLog.EnableRaisingEvents = True
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...
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 & "]")
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:
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() & "]")
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:
...
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.
...
...
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 & "]"
Dim objLog As EventLog = New EventLog("Application")
AddHandler objLog.EntryWritten, AddressOf ApplicationLog_OnEntryWritten
objLog.EnableRaisingEvents = True
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:
and the UpdateApplicationStatus()
method simply adds the application events to the list box:
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:
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):
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
If Not objClient.Connected Then
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:
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:
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):
Private Function SendEventLogEntryToHost(ByVal LogName As String, _
ByVal MonitoringHost As String, _
ByVal e As EventLogEntry) As Boolean
Try
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() & "]")
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.