Home > Software engineering >  .NET Core 6, NLog And ILogger, log to SQL Server database, custom fields issue
.NET Core 6, NLog And ILogger, log to SQL Server database, custom fields issue

Time:04-06

I use .NET Core 6 with NLog used via ILogger, to access information in a SQL Server database.
I have two custom fields that I cannot save in the database. For these two fields I always got 0 in the database regardless of the passed values.
I can't figure out what's wrong and why it always saves me 0 in the Category and Action fields.

.csproj File

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
    <IsPackable>false</IsPackable>
    <SpaRoot>ClientApp\</SpaRoot>
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

    <!-- Set this to true if you enable server-side prerendering -->
    <BuildServerSideRenderer>false</BuildServerSideRenderer>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="AutoMapper" Version="11.0.1" />
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.2" />
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.2" />
    <PackageReference Include="Microsoft.Data.SqlClient" Version="4.1.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="NLog" Version="4.7.15" />
    <PackageReference Include="NLog.Appsettings.Standard" Version="2.1.0" />
    <PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
    <PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" />
    <PackageReference Include="RestSharp" Version="107.3.0" />
  </ItemGroup>

  <ItemGroup>
    <!-- Don't publish the SPA source files, but do show them in the project files list -->
    <Content Remove="$(SpaRoot)**" />
    <None Remove="$(SpaRoot)**" />
    <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
  </ItemGroup>

  <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
  </Target>

  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --configuration production" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --configuration production" Condition=" '$(BuildServerSideRenderer)' == 'true' " />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
      <DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

</Project>

Program

public class Program
{
    public static void Main(string[] args)
    {
        var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
        try
        {
            CreateHostBuilder(args).Build().Run();
        }
        catch (Exception exception)
        {
            logger.Error(exception, "Stopped program because of exception");
            throw;
        }
        finally
        {
            NLog.LogManager.Shutdown();
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.ClearProviders();
                logging.SetMinimumLevel(LogLevel.Trace);
            }).UseNLog();
}

nlog.config

<?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="info" internalLogFile="internalLog.txt" internalLogToConsole="true">
  <extensions>
    <add assembly="NLog.Web.AspNetCore" />
  </extensions>
  <!-- the targets to write to -->
  <targets>
    <target name="DBLog" xsi:type="Database" dbProvider="Microsoft.Data.SqlClient.SqlConnection, Microsoft.Data.SqlClient" connectionString="${appsettings:name=ConnectionStrings.default}">
      <commandtext>INSERT INTO [dbo].[AtlassianEventLog] ([Date], [Level], [Category], [Action], [EventData]) VALUES (@Date, @Level, @Category, @Action, @EventData)</commandtext>
      <parameter name="@Date" layout="${date}" dbType="SqlDbType.DateTime" />
      <parameter name="@Level" layout="${level}" dbType="DbType.Int32" />
      <parameter name="@Category" layout="${mdlc:Category}" dbType="DbType.Int32" /> <-- Custom Field Category
      <parameter name="@Action" layout="${mdlc:Action}" dbType="DbType.Int32" /> <-- Custom Field Action
      <parameter name="@EventData" layout="${message}" dbType="SqlDbType.NVarChar" size="500" />
    </target>
  </targets>
  <!-- rules to map from logger name to target -->
  <rules>
    <logger name="Microsoft.*" maxlevel="off" final="true" />
    <logger name="*" minlevel="Trace" writeTo="DBLog" />
  </rules>
</nlog>

Log Class

public class LogSupport : ILogSupport
{
    private readonly ILogger logger;

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

    public void LogServiceEvent(Interfaces.LogLevel logLevel, LogCategory logCategory, LogAction logAction, string eventData)
    {
        var config = new KeyValuePair<string, int>[]
        {
            new KeyValuePair<string, int>("Category", (int)logCategory),
            new KeyValuePair<string, int>("Action", (int)logAction)
        };

        using (logger.BeginScope(config))
        {
            switch (logLevel)
            {
                case Interfaces.LogLevel.Trace:
                    logger.LogTrace(eventData);
                    break;
                case Interfaces.LogLevel.Debug:
                    logger.LogDebug(eventData);
                    break;
                case Interfaces.LogLevel.Information:
                    logger.LogInformation(eventData);
                    break;
                case Interfaces.LogLevel.Warning:
                    logger.LogWarning(eventData);
                    break;
                case Interfaces.LogLevel.Error:
                    logger.LogError(eventData);
                    break;
                case Interfaces.LogLevel.Critical:
                    logger.LogCritical(eventData);
                    break;
            }
        }
    }
}

EDIT #1
I tried accessing the file as suggested by Rolf Kristensen, and tried all types of mapping, I only got empty values except for the $ {ndlc} layout.

nlog.config updated

<?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="info" internalLogFile="internalLog.txt" internalLogToConsole="true">
  <extensions>
    <add assembly="NLog.Web.AspNetCore" />
  </extensions>
  <!-- the targets to write to -->
  <targets>
    <!--     <target name="DBLog" xsi:type="Database" dbProvider="Microsoft.Data.SqlClient.SqlConnection, Microsoft.Data.SqlClient" connectionString="${appsettings:name=ConnectionStrings.default}">
      <commandtext>INSERT INTO [dbo].[AtlassianEventLog] ([Date], [Level], [Category], [Action], [EventData]) VALUES (@Date, @Level, @Category, @Action, @EventData)</commandtext>
      <parameter name="@Date" layout="${date}" dbType="SqlDbType.DateTime" />
      <parameter name="@Level" layout="${level}" dbType="DbType.Int32" />
      <parameter name="@Category" layout="${mdlc:item=Category}" dbType="DbType.Int32" />
      <parameter name="@Action" layout="${mdlc:item=Action}" dbType="DbType.Int32" />
      <parameter name="@EventData" layout="${message}" dbType="SqlDbType.NVarChar" size="500" />
    </target> -->
    <!-- local file target -->
    <target xsi:type="File" name="file" fileName="log.txt" archiveFileName="log.{#}.txt" archiveNumbering="Date" archiveEvery="Day" archiveDateFormat="yyyyMMdd" layout="
-------------- ${level} (${longdate}) --------------${newline}${newline}
Category: 
scopeproperty:item= ${scopeproperty:item=Category}${newline}
scopeproperty: ${scopeproperty:Category}${newline}
event-properties:item= ${event-properties:item=Category}${newline}
event-properties: ${event-properties:Category}${newline}
gdc:item= ${gdc:item=Category}${newline}
gdc: ${gdc:Category}${newline}
mdc:item= ${mdc:item=Category}${newline}
mdc: ${mdc:Category}${newline}
mdlc:item= ${mdlc:item=Category}${newline}
mdlc: ${mdlc:Category}${newline}
ndc:item= ${ndc:item=Category}${newline}
ndc: ${ndc:Category}${newline}
ndlc:item= ${ndlc:item=Category}${newline}
ndlc: ${ndlc:Category}${newline}
${newline}
Action:
scopeproperty:item= ${scopeproperty:item=Action}${newline}
scopeproperty: ${scopeproperty:Action}${newline}
event-properties:item= ${event-properties:item=Action}${newline}
event-properties: ${event-properties:Action}${newline}
gdc:item= ${gdc:item=Action}${newline}
gdc: ${gdc:Action}${newline}
mdc:item= ${mdc:item=Action}${newline}
mdc: ${mdc:Action}${newline}
mdlc:item= ${mdlc:item=Action}${newline}
mdlc = ${mdlc:Action}${newline}
ndc:item= ${ndc:item=Action}${newline}
ndc: ${ndc:Action}${newline}
ndlc:item= ${ndlc:item=Action}${newline}
ndlc: ${ndlc:Action}${newline}" />

  </targets>
<!-- rules to map from logger name to target -->
<rules>
<logger name="Microsoft.*" maxlevel="off" final="true" />
<!--     <logger name="*" minlevel="Trace" writeTo="DBLog" /> -->
<logger name="*" minlevel="Trace" writeTo="file"/>

</rules>
</nlog>

File Log

 -------------- Warn (2022-04-05 18:08:49.4041) --------------

 Category:  
 scopeproperty:item= 
 scopeproperty: 
 event-properties:item= 
 event-properties: 
 gdc:item= 
 gdc: 
 mdc:item= 
 mdc: 
 mdlc:item= 
 mdlc: 
 ndc:item= 
 ndc: 
 ndlc:item= TcAtlassianServices.ServerApp.Controllers.EventLogsController.GetEventLogs (TcAtlassianServices) System.Collections.Generic.KeyValuePair`2[System.String,System.Int32][]
 ndlc: TcAtlassianServices.ServerApp.Controllers.EventLogsController.GetEventLogs (TcAtlassianServices) System.Collections.Generic.KeyValuePair`2[System.String,System.Int32][]
 
 Action: 
 scopeproperty:item= 
 scopeproperty: 
 event-properties:item= 
 event-properties: 
 gdc:item= 
 gdc: 
 mdc:item= 
 mdc: 
 mdlc:item= 
 mdlc = 
 ndc:item= 
 ndc: 
 ndlc:item= TcAtlassianServices.ServerApp.Controllers.EventLogsController.GetEventLogs (TcAtlassianServices) System.Collections.Generic.KeyValuePair`2[System.String,System.Int32][]
 ndlc: TcAtlassianServices.ServerApp.Controllers.EventLogsController.GetEventLogs (TcAtlassianServices) System.Collections.Generic.KeyValuePair`2[System.String,System.Int32][]

CodePudding user response:

Think you need to inject the scope-properties like this:

var config = new KeyValuePair<string, object>[]
{
    new KeyValuePair<string, object>("Category", (object)(int)logCategory),
    new KeyValuePair<string, object>("Action", (object)(int)logAction)
};

using (logger.BeginScope(config))
{
    // Logging
}
  • Related