Introduction
Sometimes you need to read online log stream from your web site, but you don´t always have FTP access to log files or visual studio to attach to process and read debug output.
In this article we will use SignalR and NLog to send log events to a web browser.
Background
There are many cases when you need to have access to your logs online. In my case I have a webservice that starts long-running background process. This process generates log events as it proceeds. Instead of dealing with progress reporting, I decided to extend my logging system and send logs to a browser.
SignalR is a framework for ASP.NET that allows easy developing of real-time web applications. SignalR supports WebSockets, but falls back to other compatible techniques if WebSockets are not supported. The important think for us is that is allows a server to push content to connected clients/browsers.
NLog is logging platform for .NET. It supports tens of log targets (Files, Event Logs, database, email, ...). On top of that any developer can write custom targets. It is also partly compatible with Log4Net.
This sample uses the simplest possible way how to implement log streaming using SignalR.
Using the code
The Server Part
Adding logging through SignalR 2.0 is an easy process. The only requirement is .NET 4.5. Changing the code to use SignalR 1.0 would allow you to use .NET 4.0. Attached code uses Web Forms, but SignalR and NLog can be used with MVC both as Web Site and Web App.
First you need to add SignalR and NLog nuget packages. This is simple and automatic step. Then you need to add SignalR Startup class. This sets-up all configurations required by SignalR pipeline.
[assembly: OwinStartup(typeof(SignalRStartup))]
public class SignalRStartup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
SignalR uses so-called HUBs to provide services to a client. Logging is one-way process thus we need to transfer data from server to client only and not the other way. For easier debugging there is a Hello
method, that can be later changed to implement (un)subscription to particular log levels.
public class SignalRTargetHub : Hub
{
public void Hello()
{
this.Clients.Caller.logEvent(
DateTime.UtcNow.ToLongTimeString(),
"info",
"SignalR connected");
}
static IHubContext signalRHub;
public static void Send(string longdate, string logLevel, String message)
{
if (signalRHub == null)
{
signalRHub = GlobalHost.ConnectionManager.GetHubContext<SignalRTargetHub>();
}
if (signalRHub != null)
{
signalRHub.Clients.All.logEvent(longdate, logLevel, message);
}
}
}
NLog supports MethodCallTarget out of box. This allows us to pass data from logging system back to our code. This target can call any static method with any number of arguments. In our case the method being called is SignalRTargetHub.Send(longdate, logLevel, message)
. We cannot simply create new instance of SignalRTargetHub, but we have to find a context in ConnectionManager
. That context can be used for sending data to connected clients. If the context is found we call javascript logEvent
function on all connected clients.
Hello
method is used only for easier debugging. Once a client connects, the browser calls Hello
method and the server responds (call callers javascript method) with a string "SignalR connected". It is not necessary, but it can be later extended and used for client registration.
The Client Part
The only remaining part is the code for a client/browser. SignalR automatically generates javascript objects for accessing hub methods. This auto-generated javascript can be downloaded from /signalr/hubs
.
<script src="/Scripts/jquery-1.6.4.min.js"></script>
<script src="/Scripts/jquery.signalR-2.0.3.min.js"></script>
<script src="/signalr/hubs"></script>
<script>
$(function () {
var logTable = $("#logTable");
var nlog = $.connection.signalRTargetHub;
nlog.client.logEvent = function (datetime, logLevel, message) {
var tr = $("<tr>");
tr.append($("<td>").text(datetime));
tr.append($("<td>").text(logLevel));
tr.append($("<td style='white-space: pre;'>").text(message));
logTable.append(tr);
};
$.connection.hub.start().done(function () {
nlog.server.hello();
});
});
</script>
<table id="logTable">
</table>
Notice the logEvent
function definition. This will be called from server whenever ASP.NET calls method signalRHub.Clients.All.logEvent
.
To test it
We will add error handling method to global.asax. This method will log all application errors (e.g. requests to non-existent web pages)
void Application_Error(object sender, EventArgs e)
{
Exception lastException = Server.GetLastError();
NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
logger.Fatal("Request: '{0}'\n Exception:{1}", HttpContext.Current.Request.Url, lastException);
}
Now we can load Log.aspx web page and send second request to some random page. On the first line you will se response from Hello method - "SignalR connected".
To summarize all the steps:
- Add SignalR nuget package to your project (Microsoft.AspNet.SignalR)
- Add NLog nuget package
- Add SignalRStartup.cs
- Add SignalRTargetHub.cs
- Update your web.config (nlog and configSections)
- Add Log.aspx page
- Add global.asax
Hub method Hello is not necessary. It just returns first record to your web page. You can use it to extend your hub - e.g. allow the user to register only for some levels of logging - check SignalR support for groups - http://www.asp.net/signalr/overview/signalr-20/hubs-api/working-with-groups
Future tasks to consider