Integrate JavaScript Logging with ASP.NET Core Logging APIs

Feb 19, 2017     Viewed 3946 times    2 Comments
Posted in #jsLogger  # Logging 

JavaScript is Hard!

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 =;
    var warn = console.warn;
    var error = console.error;

    console.trace = function (message) {
        log(logLevel.Trace, message);, arguments);

    console.debug = function (message) {
        log(logLevel.Debug, message);, arguments);
    }; = function (message) {
        log(logLevel.Information, message);, arguments);

    console.warn = function (message) {
        log(logLevel.Warning, message);, arguments);

    console.error = function (message) {
        log(logLevel.Error, message);, 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:
                case LogLevel.Debug:
                case LogLevel.Information:
                case LogLevel.Warning:
                case LogLevel.Error:
            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,

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


Finally add the following line into your view or layout page to render the jsLogger script.


You can download the source code for this post from my jsLogger repository on GitHub.

Twitter Facebook Google + LinkedIn


Martin Boller (3/16/2017 9:50:00 AM)

Any reason why you decided to use a middleware rather than just a controller/action? Doesnt this mean now every request needs to unnecessarily pass through a string comparison.

Hisham Bin Ateya (3/25/2017 1:43:47 AM)

IMHO middleware is fit in this case while it acts as a handler, of course you can use the Controller/Action, but I need it to be pluggable

Leave a Comment