日志服务器 - 日志的 Web 服务器
在浏览器中查看您的日志。
引言
在我上一个项目中,我需要查看应用程序编写的日志。由于我在此不提及的原因,我无法访问运行该应用程序的机器。我需要找到一个创造性的解决方案来克服这个限制,经过多次思考,我找到了一个。一个网页服务器,每 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/20 和 http://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
接受两个参数:request
和 response
。该方法应该添加所需的功能(例如,更新 StatusCode
、ContentType
或 response
对象的 Data
)并返回 HandleStatus
。这被 MainHandler.Handle
使用。MainHandler.Handle
将 request
和 response
对象一个接一个地移动到处理程序,直到其中一个处理程序 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> <a href='https://codeproject.org.cn/" +
"{0}/last/20'>Last 20</a> <a href='https://codeproject.org.cn/" +
"{0}/last/50'>Last 50</a> <a href='https://codeproject.org.cn/" +
"{0}/last/100'>Last 100</a> ",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 - 添加了日志服务器演示应用程序。