如何构建一个简单的事件日志监视器/观察器(使用 .NET 中的 TCP)
如何构建一个简单的事件日志监视器/观察器(使用 .NET 中的 TCP)来监视远程计算机上的事件日志更改。
引言
那是一个黑暗而暴风雨的夜晚……服务器一直崩溃……
.NET 允许开发人员附加一个“处理程序”来监视事件日志更改
...
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
这种方法的唯一问题是它不允许监视远程计算机上的事件日志更改。请参阅 Microsoft 支持文章 815314。
下面的代码允许您构建一个简单的事件日志“观察器”应用程序来监视远程计算机上的事件日志更改。
日志监视器应用程序(“日志观察器”)由两个组件组成
- 事件日志监视服务 - 负责监视计算机上的事件日志更改。
- 集中式“观察器主机”- 监听器 - 负责从安装在不同计算机上的日志监视器服务收集数据。
首先,让我们构建一个服务组件 - 负责“关注”计算机上的事件日志。要创建服务应用程序(即,作为服务运行的应用程序)
作为服务运行的应用程序有一些事件(继承自 System.ServiceProcess.ServiceBase
)
OnStart()
- 当服务收到“启动”命令时发生。OnStop()
- 当服务收到“停止”命令时发生。OnPause()
和OnContinue()
- 当服务收到“暂停/恢复”命令时发生。
对于此应用程序,我们只需要 OnStart()
和 OnStop()
事件。
...
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
配置文件如下所示
因此,当服务启动时,我们获取配置设置并启动事件日志监视器
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
一旦检测到应用程序或系统事件日志中的更改……
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
……应用程序将“联系”观察主机并发送日志条目数据
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
为了简化操作,服务应用程序将事件日志条目作为字符串发送
...
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)
...
并且,主机将把字符串“拆分”成相应的字段。
第二部分是构建实际的“看门狗”,它将接收来自事件日志观察器的通知。
为此,普通的 Windows 应用程序就可以正常工作。这个想法很简单 - 启动一个 TCP 监听器并等待来自事件日志观察器的数据。
...
...
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
其中配置文件是 logmonitor.exe.config
并且 UpdateApplicationStatus()
方法只是将应用程序事件添加到列表框中
Friend Sub UpdateApplicationStatus(ByVal Message As String)
Message = System.DateTime.Now.ToString("HH:mm:ss") & " - " & Message
lstApplicationEvents.Items.Add(Message)
End Sub
要启动监视过程
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
其中 ListenForWatchers()
打开监听器的端口并等待来自“日志观察器”的传入连接(来自远程事件日志的条目进入表单上的顶部列表视图控件)
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
来自本地事件日志的条目进入表单上的底部列表视图控件
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
就是这样!
该应用程序可以扩展为仅监视某些事件,例如仅监视错误,和/或仅监视来自某些事件源的事件,例如,仅监视来自 MS SQL Server 的事件。
为此,我们需要稍微更改日志监视服务中的代码
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
该应用程序可以将通知发送到多个日志观察主机,而不仅仅是一个。在这种情况下,我们需要修改 SendEventLogEntryToHost()
以传递一个额外的参数 - 主机地址(当然,我们也可以添加监视主机端口)
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
如果您想阅读更多关于这个故事的内容,请查看 Siccolo - SQL Server 的免费移动管理工具 和 Siccolo 文章 中的更多文章。