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

日志服务器 - 日志的 Web 服务器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (9投票s)

2010 年 5 月 1 日

CPOL

4分钟阅读

viewsIcon

27113

downloadIcon

635

在浏览器中查看您的日志。

Log.Server.png

引言

在我上一个项目中,我需要查看应用程序编写的日志。由于我在此不提及的原因,我无法访问运行该应用程序的机器。我需要找到一个创造性的解决方案来克服这个限制,经过多次思考,我找到了一个。一个网页服务器,每 20 秒显示文件中的最后几行。

使用演示应用程序

使用“启动”和“停止”按钮来启动和停止日志服务器。服务器监听端口 8080,因此要显示带有监控文件列表的索引页面,请使用地址:“http:\\127.0.0.1:8080”。

要添加一个文件进行监控,请使用“添加”按钮。将会出现一个新的对话框。您应该提供 URL 的前缀和要监控的文件的路径,然后按“确定”。该文件应该添加到索引页面中,其中包含指向最后 10、20、50 和 100 行的页面的链接。这些页面的地址格式如下:http://127.0.0.1:8080/main/last/20,其中 main 是提供的前缀,20 是要显示的最后一行数。

这些页面每 20 秒刷新一次,因此我们知道文件正在发生什么情况。这可能对诊断问题非常有帮助,当无法调试它或者无法访问机器时。

在 C# 程序中嵌入服务器

有时,更好的解决方案是将日志服务器嵌入到应用程序中。例如,要添加两个文件“main.log”和“error.log”,我们可以使用以下代码

LogServer _logServer = new LogServer();
FileMonitorListHandler _handler = new FileMonitorListHandler();
_logServer.AddHandler(_handler);
_handler.AddFile("main","main.log");
_handler.AddFile("error","error.log");
server.Start(8080);

LogServer 是可以显示日志文件的 Web 服务器。首先,我们添加实例 FileMonitorListHandler,它负责显示文件列表和页面文件。要添加一个日志文件,应该使用 AddFile 方法。此方法接受 URL 前缀(“main”和“error”)并将其映射到监控的文件路径(“main.log”和“error.log”)。

服务器在 8080 上启动后,我们可以使用以下地址查看这些文件的最后几行:http://127.0.0.1:8080/main/last/20http://127.0.0.1:8080/error/last/20,其中 20 是要显示的最后行数的最大值。

以下部分解释了这是如何实现的。

创建 Web 服务器

当 Web 服务器启动时,将创建一个新线程来侦听传入的 HTTP 请求。_stopEvent 将允许我们在需要时正常停止服务器。

public bool Start(int port) {
  if (_thread != null) {
    return false;
  }
  Port = port;
  bool rc = false;
  try {
    _thread = new Thread(new ThreadStart(Listen));
    _stopEvent = new AutoResetEvent(false);
    _thread.Start();
    rc = true;
  } catch (Exception ee) {
    Logger.Info("{0}",ee.Message);
  }
  return rc;
}

监听线程将侦听传入的请求。我们等待它空闲,直到设置 _stopEvent 或有新的连接要处理。在第一种情况下,我们只是从无限循环中跳出,这会终止服务器监听线程并停止服务器。在第二种情况下,我们通过调用 MainHandler.HandleInThread() 在一个新线程中处理请求。

private void Listen() {
  _listener = new TcpListener(Port);
  _listener.Start();
  Logger.Info("Listening on {0}",Port);
  while (true) {
    IAsyncResult ar = _listener.BeginAcceptSocket(
        new AsyncCallback(delegate(IAsyncResult aa) { }),null);
    WaitHandle[] handles = new WaitHandle[] { _stopEvent, ar.AsyncWaitHandle };
    int reason = WaitHandle.WaitAny(handles);
    if (reason == 0) {
      break;
    }
    Socket socket = _listener.EndAcceptSocket(ar);
    if (socket.Connected) {
      Logger.Info("Client Connected from IP {0}",socket.RemoteEndPoint);
      MainHandler handler = new MainHandler(ref socket,ref _handlers);
      handler.HandleInThread();
    }
  }
  _thread = null;
}

MainHandler 负责解析请求并创建响应。这些对象将被传递给 MainHandler.Handle 进行额外的处理。处理完成后,响应将被发送到浏览器,套接字将被关闭,并且线程终止。

public void Handle() {
  Request request = Request.GetRequest(ref _socket);
  Response response = new Response();
  Handle(ref request,ref response);
  response.Send(ref _socket);
  _socket.Close();
}

处理程序

每个服务器都有一个可以添加的处理程序列表,可以使用 LogServer.AddHandle。处理程序实现 IHandler 接口

public interface IHandler {    
  HandleStatus Handle(ref Request request,ref Response response);
}

方法 Handle 接受两个参数:requestresponse。该方法应该添加所需的功能(例如,更新 StatusCodeContentTyperesponse 对象的 Data)并返回 HandleStatus。这被 MainHandler.Handle 使用。MainHandler.Handlerequestresponse 对象一个接一个地移动到处理程序,直到其中一个处理程序 Handle 返回 HandleStatus.Done。如果返回 HandleStatus.Next,则将调用下一个处理程序。

public HandleStatus Handle(ref Request request,ref Response response) {
  foreach (IHandler handler in _handlers) {
    if ( handler.Handle(ref request,ref response) == HandleStatus.Done ) {
      break;
    }
  }
  return HandleStatus.Done;
}

FileMonitorHandler

魔术发生在 FileMonitorHandler.Handle 中,它显示页面。

public HandleStatus Handle(ref Request request,ref Response response) {
  char[] sep = new char[1];
  sep[0] = '/';
  string[] parts = request.Uri.LocalPath.Split(sep, 
                           StringSplitOptions.RemoveEmptyEntries);
  if (parts.Length > 0 && parts[0] != _prefix) {
    return HandleStatus.Next;
  }
  string contents = String.Empty;
  if (parts.Length > 2 && parts[1] == "last") {
    int num = Int16.Parse(parts[2]);
    int count = 0;
    foreach( string line in GetLastLines(_filename,num)) {
      contents += String.Format("<p class='row{0} row'>{1}</p>",count % 2,line);
      count++;
    }
  }

  response.Data += String.Format(
    @"<html>
        <head>
          <title>{0} - {1}</title>
          <meta http-equiv='refresh' content='20'>
          <style>                
            body {{ font-size: 14px; }}
            h1 {{font-size: 16px; }}
            .row0 {{ background-color: #f0f0f0; }}
            .row1 {{ background-color: #ffff99; }}
            .row  {{ margin:0px; border: 1px #00f solid; padding:2px 10px;}}
          </style>
        </head>
        <body>
          <h1>{0} - {1}</h1>
          {2}
        </body>
      </html>",Path.GetFileName(_filename),DateTime.Now.ToString(),contents);
  return HandleStatus.Done;
}

首先,我们检查请求是否属于此处理程序。如果这是这种情况,它会找出要显示多少最后几行。然后它读取最后几行,然后使用此内容生成 HTML,并指示浏览器每 20 秒刷新页面(meta 命令:<meta http-equiv='refresh' content='20'>)。

FileMonitorListHandler

此处理程序负责显示带有监控文件的索引页面,并带有指向其最后一行页面的链接。

public HandleStatus Handle(ref Request request,ref Response response) {
  foreach (IHandler handler in _handlers) {
    if (handler.Handle(ref request,ref response) == HandleStatus.Done) {
      return HandleStatus.Done;
    }
  }
  StringBuilder fileList = new StringBuilder();
  foreach (FileMonitorHandler handler in _handlers) {
    fileList.Append("<tr>");
    fileList.Append(String.Format("<td>{0}</td>",handler.Prefix));
    fileList.Append(String.Format("<td><a href='https://codeproject.org.cn" + 
       "/{0}/last/10'>Last 10</a>&nbsp;<a href='https://codeproject.org.cn/" + 
       "{0}/last/20'>Last 20</a>&nbsp;<a href='https://codeproject.org.cn/" + 
       "{0}/last/50'>Last 50</a>&nbsp;<a href='https://codeproject.org.cn/" + 
       "{0}/last/100'>Last 100</a>&nbsp;",handler.Prefix));
    fileList.Append("</tr>");
  }
  response.Data += String.Format(
    @"<html>
        <head>
          <title>Welcome to Log Server</title<
          <style<                
            body {{ font-size: 14px; }}
            h1 {{font-size: 16px; }}
            td,th {{ border: 1px solid #000000; 
                     text-align:center; padding: 0px 10px; }}
            th {{ background-color:#ffffbb;}}
            td {{ background-color:#ffffcc;}}
            table {{ border-collapse:collapse; }}

          </style<
        </head<
        <body<
          <p<Monintoring the following files : </p<
          <table<
          <tr<<th<Prefix</th< <th<Last Lines</th<<tr<
           {0}
          </table<
        </body<
      </html<",fileList);
    
    return HandleStatus.Done;
}

What Next?

虽然服务器没有提供 ASP.NET 提供的所有功能,但它有一些优点。服务器轻量级,非常简单易用,并且很容易将其嵌入到任何 C# 应用程序中。

另一个优点是能够扩展服务器以响应新的请求类型。唯一需要做的事情是实现 IHandler 接口,并使用 LogServer.AddHandler 将其新实例添加到服务器。

历史

  • 版本 1 - 第一个版本。
  • 版本 2 - 添加了日志服务器演示应用程序。
© . All rights reserved.