eatthecode.com

dotnet core logging

 Dotnet core logging

Introduction

ASP.NET Core provides support for logs that enable developers to easily leverage the logging framework functionality. Installing Login for your application will require very few code changes. Once these are installed, the Logging system can be created whenever the user desires.

How does logging work in .net core?

In .net core, the logger added by default in the different visual studio project templates as the “api”, “mvc” templates. In .net core the log be default show in the console window with information level and above. fortunately, .net give use the ability to customize the log to write log to different channels as database, files or system log.

Dotnet core logging ILogger, ILoggerFactory, and ILoggerProvider interfaces

ILoggerFactory

ILoggerFactory is a main interface in the Microsoft logging extension Using the ILoggerFactory, we can add logger providers and create instances from ILogger.

 

  ILoggerFactory-ILoggerProvider-ILogger

ILoggerFactory-ILoggerProvider-ILogger

public interface ILoggerFactory : IDisposable
{
    ILogger CreateLogger(string categoryName);
    void AddProvider(ILoggerProvider provider);
}

ILoggerFactory code

ILogger

ILogger is a generic interface for logging, it takes a generic type as a category name that is derived from the type name, for ex:

using Microsoft.AspNetCore.Mvc;

namespace DotnetCoreLogging.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CustomerController : ControllerBase
    {

        private readonly ILogger<CustomerController> _logger;
        public CustomerController(ILogger<CustomerController> logger)
        {
            _logger = logger;
        }

        public string Get()
        {
            _logger.LogInformation("Get customer at "+ DateTimeOffset.UtcNow.ToString());
return "Customer 1"; } } }

 

ILoggerProvider

ILoggerProvider is an interface that enables us to create a custom provider.

public interface ILoggerProvider : IDisposable
{
    ILogger CreateLogger(string categoryName);
}

ILoggerProvider code

Log level

The following table represents a list of log levels produced by default in the asp.net core application. 

 

Log levels

In the following code snippet, I changed the log default log behavior so that in the case of a “console”, the log of level trace and above will be written in the log. 

// appsettings.json

{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" }, "Console": { "LogLevel": { "Microsoft": "Trace" } } }, "AllowedHosts": "*" }

Log level

You can find the log settings at the application root file with the name “appsettings.json”

Configure logging

In the .net core to configure the logger, you may find the logger configured by default. For example in the controller, you can inject the logger in the constructor. For example if you have a customer controller, you can inject the ILogger in the controller constructor as the following code sample:

private readonly ILogger<CustomerController> _logger;
public CustomerController(ILogger<CustomerController> logger)
{
   _logger = logger;
}

Inject logger in a controller constructor

Log events

.net core Logging providers

Built-in logging providers

 

Built-in logging providers

Custom logging provider

Asp.net core provide a method to build a custom logger provider. For example in this section, we will build a custom logger that will save the log to the sql server database. I have create some classes to apply the custom provider. for ex:

DatabaseLogger

using DotnetCoreLogging.CustomLog.Enums;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Data.SqlClient;

namespace DotnetCoreLogging.CustomLog
{
    public class DatabaseLogger : ILogger
    {
        private readonly DatabaseLoggerProvider provider;
        public DatabaseLogger(DatabaseLoggerProvider _provider)
        {
            provider = _provider;
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            return null;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return logLevel != LogLevel.None;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        {
            if (IsEnabled(logLevel) == false)
            {
                return;
            }

            var threadId = Thread.CurrentThread.ManagedThreadId;
            using (var connection = new SqlConnection(provider.options.ConnectionString))
            {
                connection.Open();

                var values = new JObject();

            if (provider?.options?.Fields?.Any() ?? false)
            {
                foreach (var field in provider.options.Fields)
                {
                    if (field == LogFieldEnum.LogLevel.ToString())
                    {
                        values[field] = logLevel.ToString();
                        continue;
                    }
                    else if (field == LogFieldEnum.ThreadId.ToString())
                    {
                        values[field] = threadId;
                        continue;
                    }
                    else if (field == LogFieldEnum.EventId.ToString())
                    {
                        values[field] = eventId.Id;
                        continue;
                    }
                    else if (field == LogFieldEnum.EventName.ToString())
                    {
                        values[field] = eventId.Name;
                        continue;
                    }
                    else if (field == LogFieldEnum.Message.ToString())
                    {
                        values[field] = formatter(state, exception);
                        continue;
                    }
                    else if (field == LogFieldEnum.ExceptionMessage.ToString())
                    {
                        values[field] = exception?.Message;
                        continue;
                    }
                    else if (field == LogFieldEnum.ExceptionStackTrace.ToString())
                    {
                        values[field] = exception?.StackTrace;
                        continue;
                    }
                    else if (field == LogFieldEnum.ExceptionSource.ToString())
                    {
                        values[field] = exception?.Source;
                        continue;
                    }
                }
            }


                using (var command = new SqlCommand())
                {
                    command.Connection = connection;
                    command.CommandType = System.Data.CommandType.Text;
                    command.CommandText = string.Format("INSERT INTO {0} ([Text], [CreatedAt]) " +
                        "VALUES (@Text, @CreatedAt)",
                        provider.options.Table);

                    command.Parameters.Add(new SqlParameter("@Text",
                        JsonConvert.SerializeObject(values, new JsonSerializerSettings
                        {
                            NullValueHandling = NullValueHandling.Ignore,
                            DefaultValueHandling = DefaultValueHandling.Ignore,
                            Formatting = Formatting.None
                        }).ToString()));
                    command.Parameters.Add(new SqlParameter("@CreatedAt", DateTimeOffset.Now));

                    command.ExecuteNonQuery();
                }

                connection.Close();
            }

        }

    }
}

DatabaseLogger class

DatabaseLoggerExtensions

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace DotnetCoreLogging.CustomLog
{
    public static class DatabaseLoggerExtensions
    {
        public static ILoggingBuilder AddDatabaseLogger(this ILoggingBuilder builder,
            Action<DatabaseLoggerOptions> configure)
        {
            builder.Services.AddSingleton<ILoggerProvider, DatabaseLoggerProvider>();
            builder.Services.Configure(configure);
            return builder;
        }
    }
}

DatabaseLoggerExtensions class

DatabaseLoggerOptions

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotnetCoreLogging.CustomLog
{
    public class DatabaseLoggerOptions
    {
        public string ConnectionString { get; set; }
        public string[] Fields { get; set; }
        public string Table { get; set; }

        public DatabaseLoggerOptions()
        {

        }
    }
}

DatabaseLoggerOptions class

DatabaseLoggerProvider

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotnetCoreLogging.CustomLog
{
    public class DatabaseLoggerProvider:ILoggerProvider
    {
        public readonly DatabaseLoggerOptions options;

        public DatabaseLoggerProvider(IOptions<DatabaseLoggerOptions> _options)
        {
            options = _options.Value;
        }

        public ILogger CreateLogger(string categoryName)
        {
            return new DatabaseLogger(this);
        }

        public void Dispose()
        {
        }
    }
}

DatabaseLoggerProvider class

Logger extension method

As what you can see in the class “DatabaseLoggerExtensions”, .net core extenstion package “Microsoft.Extensions.Logging” give us the ability to extend the logger via extenstion method so that we can use in the program file to append the custom log in our case is the database log.

Conclusion

This post provides how the log works in .net core with provide a sample for log at sql server database. The sample done via extend the logger to write to the database as the default logger don’t provide this ability.

You can find the project source code at Github

You can find more of my sample source codes on Github Enjoy…!!!

I can help you to build such as software tools/snippets, you contact me from here

 
Exit mobile version