Written by 7:27 am Show me the code

Is Asp.Net Core unnecessarily complex?

Source Code can be found at github.

By examining the source code of the logging framework of Asp.Net Core, a conclusion comes out with no difficulty: it is a complex framework.

Thus some questions arise naturally

  • is this complexity necessary?
  • what are the technical arguments that justify that level of complexity?
  • what is the cost of such a complexity, in man-hours, regarding, lets say, a junior and a senior developer?
  • could a logging framework be much more simpler?

It may sound inpolite and ironic, but I think we have several sides in the Asp.Net Core which cultivate the fine art of converting a simple thing to a complex one.

It’s a fact. An Asp.Net Core developer has to cope with a particularly high complexity.

And I think the idea that the use of Dependency Injection should be universal, everything must be converted into service instances that are injected into a constructor and that the use of static classes must completely disappear, is responsible for that complexity.

And if the above is true then we are facing an ideological issue. Not a technical one. I hope that’s not true.

How a logging framework could be simpler

A logging framework can be implemented around a static Logger class.

That static Logger class should provide a set of static log methods.

static public class Logger
{
    static public void Log(LogEntry Info);

    static public void Info(string Text, ...);
    static public void Warn(string Text, ...);
    static public void Error(Exception Ex, ...);

    static public void Add(ILogListener listener);
    static public void Remove(ILogListener listener);
}

Such a log method examines the passed parameters and generates a unit of log information, an instance of, say, a LogEntry class.

That LogEntry instance is then passed, asynchronously, meaning using a thread, to any registered log listener for further processing. A listener may display the log information to a console while another listener may save that information to a text file or a database table.

That same static Logger class will provide methods such as Add(ILogListner listener) and Remove(ILogListner listener), that is, it’ll be a log listeners register.

An example of a logging system built around a static Logger class

Here is an example of a logging system. It contains the following parts:

  • LogLevel enumeration type with just three levels. It is easy to add more levels if needed.
  • LogEntry class that serves as a container for a unit of log information.
  • An ILogListener interface with just a single method, ProcessLog(LogEntry Info). That method is called whenever a new LogEntry comes to existense.
  • A static Logger class which provides log methods, Info()Warn() and Error().
    Logger is also an ILogListener register. It calls all registered listeners, asynchronously (that is using a thread), whenever any of its log methods is called and a new LogEntry is generated.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Logging
{
    public enum LogLevel
    {
        None = 0,
        Info = 1,
        Warn = 2,
        Error = 3,
    }
 
    public class LogEntry
    {
        public LogEntry()
        {
            TimeStampUtc = DateTime.UtcNow;
            UserName = Environment.UserName;
        }
        public LogEntry(LogLevel Level, string Text, Exception Exception = null, string Source = "", int EventId = 0)
            : this()
        {
            this.Level = Level;
            this.Text = Text;
            this.Exception = Exception;
            this.Source = Source;
            this.EventId = EventId;
        }
  
        static public readonly string StaticHostName = System.Net.Dns.GetHostName();
 
        public string UserName { get; private set; }
        public string HostName { get { return StaticHostName; } }
        public DateTime TimeStampUtc { get; private set; }
        public string Source { get; set; }
        public LogLevel Level { get; set; }
        public string Text { get; set; }
        public Exception Exception { get; set; }
        public int EventId { get; set; } 
    }
 
    public interface ILogListener
    {
        void ProcessLog(LogEntry Info);
    }
 
    static public class Logger
    {
        static object syncLock = new object();
        static int fActive = 0;
        static List<ILogListener> listeners = new List<ILogListener>();
 
        static public void Log(LogEntry Info)
        {
            lock (syncLock)
            {
                if (Active)
                {
                    foreach (ILogListener listener in listeners)
                    {
 
                        Task.Run(() => {
 
                            try
                            {
                                listener.ProcessLog(Info);
                            }
                            catch // (Exception ex)
                            {
                            }
 
                        });
 
                    }
 
                }
 
            }
        }
        static public void Log(LogLevel Level, string Text, Exception Exception = null, string Source = "", int EventId = 0)
        {
            LogEntry Info = new LogEntry(Level, Text, Exception, Source, EventId);
            Log(Info);
        }
 
        static public void Info(string Text, string Source = "", int EventId = 0)
        {
            Log(LogLevel.Info, Text, null, Source, EventId);
        }
        static public void Warn(string Text, string Source = "", int EventId = 0)
        {
            Log(LogLevel.Warn, Text, null, Source, EventId);
        }
        static public void Error(Exception Exception, string Source = "", int EventId = 0)
        {
            Log(LogLevel.Error, null, Exception, Source, EventId);
        }
 
        static public void Add(ILogListener Listener)
        {
            lock (syncLock)
            {
                try
                {
                    if (!listeners.Contains(Listener))
                    {
                        listeners.Add(Listener);
                    }
                }
                catch
                {
                }
            }
 
        }
        static public void Remove(ILogListener Listener)
        {
            lock (syncLock)
            {
                try
                {
                    if (listeners.Contains(Listener))
                    {
                        listeners.Remove(Listener);
                    }
                }
                catch
                {
                }
            }
        }
 
        static public bool Active
        {
            get
            {
                lock (syncLock)
                {
                    return fActive == 0;
                }
            }
            set
            {
                lock (syncLock)
                {
                    if (!value)
                        fActive++;
                    else
                    {
                        fActive--;
                        if (fActive < 0)
                            fActive = 0;
                    }
                }
            }

There is also a fully functional LogFileListener class, implementing the ILogListener interface, which saves log information to a text file, and also provides settings for log file retention policy. LogFileListener is included in the github sources.

The above logging system does not support Scopes, as the logging framework of Asp.Net Core does. It’s easy to implement Scopes though.

The first thing you need is another one Logger method, getting as parameter a string or whatever represents a Scope, and returning an interface with log methods, more or less, similar to the log methods of the static Logger class.

You also need a nested class, private to Logger class, implementing that interface. Last, you should alter Logger methods to support Scope too.

That’s all.

(Visited 66 times, 1 visits today)
Share the Post
Tags: , , , Last modified: October 14, 2020
Close