2019年3月31日 星期日

[JSNLog] Logging on JS and ASP.NET Core 2.X


 Vue.js   ASP.NET Core    NLog    JSNLog 


Introduction


The scenario is that we want to log on the front-end and write to the backend.
We will us NLog and JSNLog to fulfill the purpose.




Related articles






Environment


Visual Studio 2017 community
NPM: 5.6.0                                     
ASP.NET Core 2.2.101
Vue CLI: 3.5.1


Packages

We will install the following packages into our ASP.NET Core project thru Nuget:

3.  System.Data.SqlClient (Optional, install it when you wanna keep the log in sql server)
4.  JSNLog



NLog config


We have to update NLog settings in NLog.config.

NLog.config

Copy the following content to your NLog.config, and modify the file pathes, database connection string, targets and rules.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Off"
      internalLogFile="C:\Logs\internal-nlog.txt">

  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <!--LOG format-->
  <variable name="Layout" value="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${newline}"/>
  <variable name="LayoutFatal" value="${longdate} | ${level:uppercase=true} | ${logger} | ${message} | ${exception:format=tostring} ${newline}"/>
  <variable name="LayoutEvent" value="${date}: ${message} ${stacktrace}"/>

  <!--LOG path -->
  <variable name="LogTxtLocation" value="${basedir}/App_Data/Logs/${shortdate}/${logger}.log"/>
  <variable name="LogTxtLocationFatal" value="${basedir}/App_Data/Logs/${shortdate}/FatalFile.log"/>
  <variable name="ProjectName" value="MyProject"/>

  <targets>
    <!--Text file-->
    <target name="File" xsi:type="File" fileName="${LogTxtLocation}" layout="${Layout}" />
    <target name="FileFatal" xsi:type="File" fileName="${LogTxtLocationFatal}" layout="${LayoutFatal}"/>
    <!--Event log-->
    <!--<target name="Event" xsi:type="EventLog" source="${ProjectName}" log="Application" layout="${LayoutEvent}" />-->
    <!--Log Viewer(Optional)-->
    <target name="Sentinel" xsi:type="NLogViewer" address="udp://127.0.0.1:3333"/>
    <!--Database-->
    <target name="LogDatabase" xsi:type="Database" >
      <connectionString>
        Data Source=Your-DB-Server-name;Initial Catalog=Your-DB-Name;Integrated Security=True;
      </connectionString>
      <commandText>
         INSERT INTO AspnetCoreLog (
              Application, Logged, Level, Message,
              Logger, CallSite, Exception
              ) values (
              @Application, @Logged, @Level, @Message,
              @Logger, @Callsite, @Exception
              );
      </commandText>
    
      <parameter name="@application" layout="AspNetCoreNlog" />
          <parameter name="@logged" layout="${date}" />
          <parameter name="@level" layout="${level}" />
          <parameter name="@message" layout="${message}" />
          <parameter name="@logger" layout="${logger}" />
          <parameter name="@callSite" layout="${callsite:filename=true}" />
          <parameter name="@exception" layout="${exception:tostring}" />
    </target>
  </targets>

  <rules>
    <logger name="*" levels="Trace, Debug, Info, Warn"  writeTo="File" />
    <logger name="*" levels="Error, Fatal"                           writeTo="FileFatal,LogDatabase" />
    <logger name="*" levels="Trace, Debug, Info, Warn, Error, Fatal" writeTo="Sentinel" />
  </rules>
</nlog>






Config ASP.NET core



Program.cs

Use NLog for DI logger.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .ConfigureLogging(logging =>
            {
                logging.ClearProviders();
                logging.SetMinimumLevel(LogLevel.Trace);
            })
            .UseNLog();


And we can have other NLog config, like NLog.Development.config, for the development environment.
We can optionally load it depends on the environment variable, ASPNETCORE_ENVIRONMENT.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                var env = hostingContext.HostingEnvironment;
                if (env.IsDevelopment())
                {
                    env.ConfigureNLog($"NLog.{environment}.config");
                }
            })
            .ConfigureLogging(logging =>
            {
                logging.ClearProviders();
                logging.SetMinimumLevel(LogLevel.Trace);
            })
            .UseNLog();





Optional: Cross-Origin Resource Sharing (CORS)

If we are going to use the cross-domain logging server, we need to set the allowed domain to JSNLog in backend.
Open Startup.cs and update the Configure function,

using JSNLog;
using Microsoft.Extensions.Logging;

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
 {
            // Configure JSNLog
            var jsnlogConfiguration =
                  new JsnlogConfiguration
                  {
                      corsAllowedOriginsRegex = "^https?://localhost:8080"
                  };
            app.UseJSNLog(new LoggingAdapter(loggerFactory), jsnlogConfiguration);

//…skip
 }



And don’t forget to enable CORS on the ASP.NET Core.

Startup.js

public void ConfigureServices(IServiceCollection services)
{
            //Enable CORS
            services.AddCors(options =>
            {
                options.AddPolicy("AllowSpecificOrigin",
                    builder =>
                    builder.WithOrigins("http://localhost:8080")                   
                    // builder.AllowAnyOrigin()
                    .AllowAnyHeader()
                    .AllowCredentials()
                    .AllowAnyMethod()
                   );
            });

           
            #endregion
}

And applying CORS globally as following,

public void Configure(IApplicationBuilder app)
{
   app.UseCors("AllowSpecificOrigin");
   app.UseMvc();
}


I made a mistake to enable CORS ONLY ON Controller’s actions (as following) and the cross-domain requests are denied from front-end logging.

 // Enable CORS for every MVC actions
 services.Configure<MvcOptions>(options =>
 {
    options.Filters.Add(new CorsAuthorizationFilterFactory("AllowSpecificOrigin"));
 });







Start logging


Backend (MVC controller)

[ApiController]
    public partial class MyController : ControllerBase
    {
        private readonly ILogger<MyController> _logger = null;

        public MyController(
            ILogger<MyController> logger)
        {
            this._logger = logger;
        }

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            this._logger.LogDebug("Testing");
            //…skip
        }
    }
}




Frontend

First install JSNLog by NPM.

$ npm intall jsnlog

Now you can log like this,

import {JL} from "jsnlog";

JL().trace("Trace...");
 JL().debug("Debug...");
 JL().info("Info...");
 JL().warn("Warn...");
 JL().error("Error...");
 JL().fatal("Fatal...");


If the front-end and back-end are cross-domain, create new AjaxAppender to assign the specified logging server

jsnlogger.js

import {JL} from "jsnlog";

const appender = JL.createAjaxAppender("minitisAppender")
appender.setOptions!({
    url: `${process.env.YOUR_HOST_BACKEND_URL}/jsnlog.logger`
});

export default JL("MiniTis-Vue").setOptions({
    "appenders": [appender]
});


Then log by the custom JSNLog object,

import JL from "jsnlogger";

@Component({
  components: {
  },
})
export default class XXXX extends Vue {
    public mounted() {
        JL.trace("Trace...");
        JL.debug("Debug...");
        JL.info("Info...");
        JL.warn("Warn...");
        JL.error("Error...");
        JL.fatal("Fatal...");
    }
}



Result




Reference