集成Javascript Logging on MVC or Core
ASP.NET Core provides us a rich Logging APIs which have a set of logger providers including: ConsoleLoggerPtovider, AzureAppServicesDiagnosticsLoggerProvider, EventLogLoggerProvider and much more.
This let C# developers happy than before, because you need to implement them loggers yourself in the past, however a lot of us writing JavaScript code in almost web applications which is little hard, but find the client-side exceptions & errors are even harder.
There are many client-side loggers that you can name it, which are fit our needs, but today I wanna talk about the client-side logging from different angle. Me and you as developers suffer a lot from unexpected javascript error that happen occasionally, sometimes the reason is silly such missing a curly braces or broke a link .. etc.
With that I was thinking last few days that it would be nice to integrate the client-side & server-side logs, so all the logs will be logged from the ASP.NET Core logger providers that we like and love instead of managing two different logger providers for both client-side & server-side.
Logging JavaScript Events
Basically the idea is very simple, I need to inject the client-side logging APIs script into our view, after that I need the JavaScriptLoggingMiddleware to listening to the upcoming script logs and forward them to ASP.NET Core logger providers.
With that we're ready to show some code, but before that I need to mention that I didn't introduce a new logging APIs, but I could, so the javascript console object is enough for logging. In both cases we need to intercept the console logs as the following:
(function () {
var trace = console.trace;
var debug = console.debug;
var info = console.info;
var warn = console.warn;
var error = console.error;
console.trace = function (message) {
log(logLevel.Trace, message);
trace.call(this, arguments);
};
console.debug = function (message) {
log(logLevel.Debug, message);
debug.call(this, arguments);
};
console.info = function (message) {
log(logLevel.Information, message);
info.call(this, arguments);
};
console.warn = function (message) {
log(logLevel.Warning, message);
warn.call(this, arguments);
};
console.error = function (message) {
log(logLevel.Error, message);
error.call(this, arguments);
};
})();
The log is a method that I created to post the actual logs to the server, which will handled by the JavaScriptLoggingMiddleware that shown below.
At this point I was wondering whether to store jsLogger script into the disk and render it using a tag helper or not!! after awhile I inspired by Application Insights and decided to store it into a resx file and retrieve them later using JavaScriptLoggingSnippet.
public class JavaScriptLoggingMiddleware
{
private readonly ILogger _logger;
private readonly RequestDelegate _next;
public JavaScriptLoggingMiddleware(ILoggerFactory loggerFactory, RequestDelegate next)
{
_logger = loggerFactory.CreateLogger<JavaScriptLoggingMiddleware>();
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Path == "/log" && context.Request.Method == "POST" && context.Request.HasFormContentType)
{
var form = await context.Request.ReadFormAsync();
var level = Convert.ToInt32(form["level"].First());
var message = form["message"].First();
switch ((LogLevel)level)
{
case LogLevel.Trace:
_logger.LogTrace(message);
break;
case LogLevel.Debug:
_logger.LogDebug(message);
break;
case LogLevel.Information:
_logger.LogInformation(message);
break;
case LogLevel.Warning:
_logger.LogWarning(message);
break;
case LogLevel.Error:
_logger.LogError(message);
break;
default:
return;
}
}
else
{
await _next(context);
}
}
}
If you look closely to the code above, you will notice that the middleware listening for specific url, when it hit I need to map the client-side log levels with the server-side once, after that I log them normally.
I can finally log JavaScript Exceptions!
Last but not least, sometimes we come up to a situation that we need to catch the global javascript exception that may occur for whatever reason could be. window.onerror is a good place to catch such exception.
I added a JavaScriptLoggingOptions which is shown below to make things configurable in the way that you want.
public class JavaScriptLoggingOptions { public bool HandleGlobalExceptions { get; set; } }
By adding this simple property, the JavaScriptLoggingSnippet is now able to choose the proper script to render in the view.
public class JavaScriptLoggingSnippet { private readonly JavaScriptLoggingOptions _loggingOptions; private static readonly HtmlString JavaScriptLoggingScript = new HtmlString(Resources.Script); private static readonly HtmlString JavaScriptLoggingGlobalExceptionHandlingScript = new HtmlString(Resources.GlobalExceptionHandlingScript); public JavaScriptLoggingSnippet(IOptions<JavaScriptLoggingOptions> loggingOptions) { _loggingOptions = loggingOptions.Value; } public HtmlString Script => _loggingOptions.HandleGlobalExceptions ? FullScript : JavaScriptLoggingScript; private static HtmlString FullScript => JavaScriptLoggingScript.Concat(HtmlString.NewLine, JavaScriptLoggingGlobalExceptionHandlingScript); }
What it takes to make everything happen?
We need few steps to make this happen:
First we need to configure the JavaScriptLogging service in the ConfigureServices method
services.AddJavaScriptLogging(options => { options.HandleGlobalExceptions = true; });
After that adding the JavaScriptLoggingMiddleware to the Configure method
app.UseJavaScriptLogging();
Finally add the following line into your view or layout page to render the jsLogger script.
@Html.Raw(JavaScriptLoggingSnippet.Script)
You can download the source code for this post from my jsLogger repository on GitHub.

浙公网安备 33010602011771号