A day with .Net

My day to day experince in .net

Posts Tagged ‘asp.net core jsnlog’

ASP.NET Core Server Side And Client Side Exception Logging to SQL Server By Using Serilog and JSNLog

Posted by vivekcek on May 28, 2018

Server side and client side logging is a must feature for most of the production web application.
For server side logging i usually prefer ELMAH, But nowadays they started pricing. So i am going with Serilog.
For client side logging we can use JSNLog.
Due to lack of time i am not going to write a detailed post. But if you follow the steps you are done.
Please note these steps are for ASP.NET Core 2.0 web apps.

1. Create an ASP.NET Core 2.0 Web App.
2. Install the below dependencies.

serilog.aspnetcore

Serilog.Settings.Configuration

Serilog.Sinks.MSSqlServer

JSNLog.AspNetCore

Destructurama.JsonNet

3. Now add two classes CustomLoggingAdapter and LogMessageHelpers as below

using JSNLog;
using JSNLog.Infrastructure;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace Logging
{
    public class CustomLoggingAdapter : ILoggingAdapter
    {
        private ILoggerFactory _loggerFactory;

        public CustomLoggingAdapter(ILoggerFactory loggerFactory)
        {
            _loggerFactory = loggerFactory;
        }

        public void Log(FinalLogData finalLogData)
        {
            ILogger logger = _loggerFactory.CreateLogger(finalLogData.FinalLogger);

            Object message = LogMessageHelpers.DeserializeIfPossible(finalLogData.FinalMessage);

            switch (finalLogData.FinalLevel)
            {
                case Level.TRACE: logger.LogTrace("{@logMessage}", message); break;
                case Level.DEBUG: logger.LogDebug("{@logMessage}", message); break;
                case Level.INFO: logger.LogInformation("{@logMessage}", message); break;
                case Level.WARN: logger.LogWarning("{@logMessage}", message); break;
                case Level.ERROR: logger.LogError("{@logMessage}", message); break;
                case Level.FATAL: logger.LogCritical("{@logMessage}", message); break;
            }
        }
    }

    internal class LogMessageHelpers
    {
        public static T DeserializeJson<T>(string json)
        {
            T result = JsonConvert.DeserializeObject<T>(json);
            return result;
        }
        public static bool IsPotentialJson(string msg)
        {
            string trimmedMsg = msg.Trim();
            return (trimmedMsg.StartsWith("{") && trimmedMsg.EndsWith("}"));
        }

        /// <summary>
        /// Tries to deserialize msg.
        /// If that works, returns the resulting object.
        /// Otherwise returns msg itself (which is a string).
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public static Object DeserializeIfPossible(string msg)
        {
            try
            {
                if (IsPotentialJson(msg))
                {
                    Object result = DeserializeJson<Object>(msg);
                    return result;
                }
            }

            catch
            {
            }
            return msg;

        }

        /// <summary>
        /// Returns true if the msg contains a valid JSON string.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public static bool IsJsonString(string msg)
        {
            try
            {
                if (IsPotentialJson(msg))
                {
                    // Try to deserialise the msg. If that does not throw an exception,
                    // decide that msg is a good JSON string.
                    DeserializeJson<Dictionary<string, Object>>(msg);
                    return true;
                }
            }
            catch
            {
            }
            return false;
        }
        /// <summary>
        /// Takes a log message and finds out if it contains a valid JSON string.
        /// If so, returns it unchanged.
        /// 
        /// Otherwise, surrounds the string with quotes (") and escapes the string for JavaScript.
        /// </summary>
        /// <param name="ms"></param>
        /// <returns></returns>
        public static string EnsureValidJson(string msg)
        {
            if (IsJsonString(msg))
            {
                return msg;
            }
            return HtmlHelpers.JavaScriptStringEncode(msg, true);
        }

    }
}

4. Now update your Program.cs as below.

using System;
using System.IO;
using Destructurama;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Serilog;

namespace Logging
{
    public class Program
    {
        public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
             .SetBasePath(Directory.GetCurrentDirectory())
             .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
             .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
             .Build();

        public static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(Configuration)
                .Destructure.JsonNetTypes()
                .CreateLogger();

            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseSerilog()
                .Build();
    }
}

5. Update your appSettings.json. You need a SQL database. Logs table will be created automatically.

{
  "connectionStrings": {
    "libraryDBConnectionString": "Server=SQLEXPRESS;Database=Test;Trusted_Connection=True;MultipleActiveResultSets=true;"
  },
  "Serilog": {
    "MinimumLevel": "Error",
    "WriteTo": [
      {
        "Name": "MSSqlServer",
        "Args": {
          "connectionString": "Server=SQLEXPRESS;Database=Test;Trusted_Connection=True;MultipleActiveResultSets=true;",
          "tableName": "Logs",
          "autoCreateSqlTable": true
        }
      }
    ]
  }
}

6. Now add a global extension class AspNetCoreGlobalExceptionHandlerExtension

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.Extensions.Logging;
using System;

namespace Logging
{
    public static class AspNetCoreGlobalExceptionHandlerExtension
    {
        public static IApplicationBuilder UseAspNetCoreExceptionHandler(this IApplicationBuilder app)
        {
            var loggerFactory = app.ApplicationServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory;
            return app.UseExceptionHandler(HandleException(loggerFactory));
        }

        public static Action<IApplicationBuilder> HandleException(ILoggerFactory loggerFactory)
        {
            return appBuilder =>
            {
                appBuilder.Run(async context =>
                {
                    var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
                    if (exceptionHandlerFeature != null)
                    {
                        var logger = loggerFactory.CreateLogger("Serilog Global exception logger");
                        logger.LogError(500, exceptionHandlerFeature.Error, exceptionHandlerFeature.Error.Message);

                    }

                    context.Response.Redirect($"/Error/Status/{context.Response.StatusCode}");

                });

            };

        }
    }
}

7. Update your Startup.cs

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
                app.UseAspNetCoreExceptionHandler();
            }
            else
            {
                app.UseAspNetCoreExceptionHandler();
            }

            app.UseStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

8. Inject logger in your HomeController and throw an exception.

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace Logging.Controllers
{
    public class HomeController : Controller
    {
        private ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            try
            {
                throw new InvalidOperationException("Exception message");
            }
            catch (ArgumentNullException e)
            {
                _logger.LogError(e.Message, e.InnerException);
                return View("Index");
            }
        }

    }
}

9. Now go to your database table and see the logged exception.

10. Next we will configure JSNLog for client side logging.

11. Add below code in _Layout.cshtml

@*Add to _Layout.cshtml*@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsnlog/2.26.2/jsnlog.min.js"></script>

12. Update your Startup.cs

using JSNLog;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Logging
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }
     
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

     
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
                app.UseAspNetCoreExceptionHandler();
            }
            else
            {
                app.UseAspNetCoreExceptionHandler();
            }
            // Configure JSNLog
            var jsnlogConfiguration = new JsnlogConfiguration();
            app.UseJSNLog(new LoggingAdapter(loggerFactory), jsnlogConfiguration);

            app.UseStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

13. In the View you can write below codes for logging javascript exceptions and unhandled exceptions.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jsnlog/2.26.2/jsnlog.min.js"></script>
    <script type="text/javascript">

        JL().error("log message");

        // Code included in jsnlog.js to set a handler that logs uncaught JavaScript errors to 
        // the server side log.
     
        if (window && !window.onerror) {
            window.onerror = function (errorMsg, url, lineNumber, column, errorObj) {
                JL("onerrorLogger").fatalException({
                    "msg": "Uncaught Exception",
                    "errorMsg": errorMsg, "url": url,
                    "line number": lineNumber, "column": column
                }, errorObj);

                return false;
            }
        }
    </script>

14. Now see the result in your database.

15. To do the same in WebApi, use below extension.

public static class ApiGlobalExceptionHandlerExtension
    {
        public static IApplicationBuilder UseWebApiExceptionHandler(this IApplicationBuilder app)
        {
            var loggerFactory = app.ApplicationServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory;

            return app.UseExceptionHandler(HandleApiException(loggerFactory));
        }

        public static Action<IApplicationBuilder> HandleApiException(ILoggerFactory loggerFactory)
        {
            return appBuilder =>
            {
                appBuilder.Run(async context =>
                {
                    var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();

                    if (exceptionHandlerFeature != null)
                    {
                        var logger = loggerFactory.CreateLogger("Serilog Global exception logger");
                        logger.LogError(500, exceptionHandlerFeature.Error, exceptionHandlerFeature.Error.Message);
                    }

                    context.Response.StatusCode = 500;
                    await context.Response.WriteAsync("An unexpected fault happened. Try again later.");

                });
            };
        }
    }

16 You can use this by.

app.UseWebApiExceptionHandler();
Advertisements

Posted in ASP.NET Core | Tagged: , , , , | Leave a Comment »