65.9K
CodeProject 正在变化。 阅读更多。
Home

如何构建一个简单的事件日志监视器/观察器(使用 .NET 中的 TCP)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (22投票s)

2007年2月22日

CPOL

3分钟阅读

viewsIcon

67868

downloadIcon

1

如何构建一个简单的事件日志监视器/观察器(使用 .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 文章 中的更多文章。

© . All rights reserved.