Purpose of Logging

Logging is the optimum way to monitor application behavior, which is essential for a software project once out for production. C# .NET provides a logging API that works with a variety of built-in and third-party logging providers. Application behavior could be monitored by saving, emitting, indexing the log data.

  1. Purpose of Logging
  2. Culture of Logging
  3. Log Levels
  4. Framework
  5. Best Practices for Logging

Logging allows the user to find a resolution for unhandled exceptions. Moreover, Logs serves resolutions in problem-solving on different platforms for debugging purposes. Bugs Analysis and application performance is analyzed using the generated logs. Logging help gather statistics and data to use with relevance to Big Data.

Logging Practices C#

Culture of Logging

There are 4 goals to accomplish in Logging to monitor and log the application.

Basic Logs – There have to have some basic logging in your code for every Key Transaction.

Provisional Logs – Log data with contextual and relevant logs.

Collective Logs – Logs should be consolidated in an accessible path to all stakeholders.

The active approach in Usage – Real-time Monitoring of logs is essential for errors.

Refer to the example below:

Catch(exception a)
{
                Log.error(a)
}

In this example, the exception may likely have a stack trace. It is a pro-active approach to real-time monitor the bugs.
Logs can give users invaluable information on other components like third-party libraries.

Logs Level

Logging is the best practice to get rid of any errors coming your way. It may save the user from errors and is essential for troubleshooting. The only difficulty is that it could take a big chunk of storage in the hard drive. Logs in big chunks could be challenging to search in some instances.

It is the part where logging levels may use to filter out the outputs. Every logging level is associated with the type of logged data.

DEBUG, INFO, and TRACE events are usually non-error conditions that report on an application’s behavior.

It depends upon the use of it, as Logging is disabled in production and acts passively. It enables in a non-production environment. Production environments have checks of WARN and ERROR enabled to report bugs and warnings. It limits production logging to time-sensitive and critical data that affect application availability.

Frameworks provide liberty to developers to use each logging level with freedom. Dev Teams usually establishes a reliable pattern on what to Log on what Level. It may vary from application to application but should be limited to a single application.

Frameworks

There are various frameworks and tools used in. NET. TraceSource, log4Net, and SeriLog are part of the discussion in this article. The three of those mentioned above are the most used and differs from one another but perform similar functions.

TraceSource

TraceSource is a native library in Microsoft Bundles. It must detect in the same directory as the executable application.

Configuration

TraceSource needs enabling in the configuration files, named as application. Config. After enabling the TraceSoruce, it could use to write messages to the console.

{
 class TraceTest
 private static TraceSource ts = new TraceSource("TraceTest");
 static void Main(string[] args)
 {
  ts.TraceEvent(TraceEventType.Error, 1, "Error message.");
  ts.Close();
  return;
 }
}

It allows users if a series of debug tests are to be run and compare the results or maintain a log event.

Working of TraceSource

  1. This library writes its message to a listener object.
  2. Object sends the received messages to pre-determined locations
  3. Initialize Listener using this code to the config file.
<configuration>
  <system.diagnostics>
    <trace autoflush="false" indentsize="4">
      <listeners>
        <add name="myListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="TextWriterOutput.log" />
        <remove name="Default" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

Trace.Listeners.Add(new TextWriterTraceListener("TextWriterOutput.log", "myListener"));
Trace.TraceInformation("Test message.");
// You must close or flush the trace to empty the output buffer.
Trace.Flush();

Serilog

Serilog offers diagnostic Logging to the console, files, and away. It is easy to composite. Serilog has a clean API, which is portable between recent .NET platforms.

Configuration

It uses a simple C# API for the configuration of Logging. For external configuration, it sparingly blendes using the Serilog.Settings.AppSettings package.

Loggers are created using LoggerConfiguration() in serilog.

Log.Logger = new LoggerConfiguration().CreateLogger();
Log.Information("No one listens to me!");
NuGet is required to use Serilog, and for implementation, the user requires a sink.

Sink

Sinks may configure with Writeto() function. Log event sinks record log events to some external depiction.
It can be a file, console, or data store. The distribution of SeriLog sinks is through NuGet.

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();
Log.Information("Ah, there you are!");

Serilog is most straightforward to set up, and it facilitates with a big group of Sinks as well.

Log4Net

Log4Net is the firstborn of the three frameworks. It finds itself in older .Net Projects. Log4net was dock from Java’s log4j project

Log4net sets up with an XML configuration (log4net.config) file that looks like this with assembly function.

<log4net>
  <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
    <file value="my_log.log" />
    <appendToFile value="true" />
    <maximumFileSize value="50KB" />
    <maxSizeRollBackups value="2" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date %level %message%newline" />
    </layout>
  </appender>
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config")

After XML configurations, Logging is executable.

class MyClass
{
    private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    public void Foo()
    {
        _log.Debug("Foo started");
    }
}

log4net supports structured Logging. It covers a minimum of logging targets called appenders. However, it supports fewer targets than others out of the box. Log4net is vast that whatever isn’t supported could be found on the internet with a custom implementation.

Log Messages with Filters

The log4net bundle provides several filters that control which messages to log by which affixes. Here are some examples of existing filters:

  • log4net.Filter.DenyAllFilter – deny all messages
  • log4net.Filter.StringMatchFilter – match texts containing a string
  • log4net.Filter.LevelRangeFilter – matches text within a range of log levels

It is not hard to think of uses for each of these choices and log4net.Filter.StringMatchFilter is useful for sending log messages from particular application modules to a dedicated path.

The last filter of the list helps to improve the configuration file from the previous section. It filters messages based on their log level.

The below example configuration sends messages at or above INFO to the info.log file and posts at ERROR and FATAL to error.log.

<log4net>
    <appender name="File1Appender" type="log4net.Appender.FileAppender">
        <file value="info.log" />
        <appendToFile value="true" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date %message%newline" />
        </layout>
    </appender>
    <appender name="File2Appender" type="log4net.Appender.FileAppender">
        <file value="error.log" />
        <appendToFile value="true" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date %message%newline" />
        </layout>
        <filter type="log4net.Filter.LevelRangeFilter">
            <levelMin value="ERROR" />
            <levelMax value="FATAL" />
        </filter>
    </appender>

The root logger is writing all the messages, which may forward those messages to the appenders, and the appender either ignores the messages or writes them to a file-based log level.

Best Practice for Logging

You need to follow best practices when creating them to contain the right information when you need it.

One should follow the best approach while creating the logs as they contain useful and useless logs at the same time.

One could create a library from scratch for Logging purposes. However, developers mostly benefit from using built-in or already made SDKs, a better and more time-saving option.

Importance of W’s

Debug logs report application events that are beneficial when diagnosing a problem. Investigations in the application failures need the “W” words: What, When, Where, Who, and Why:

  • Who was using the application when it failed?
  • Where in the program application failed?
  • What was the system’s behavior when it failed?
  • When did the failure take place?
  • Why did the application fail?

“Why the application failed?” is the outcome of a failure investigation and purpose of Logging. Logging targets handle the “when” with timestamps in addition to the log records. The rest of the “Ws” come from logging declarations added to the code.

  • Logging Context
  • Structured Logging

Logging context means adding the “Ws” to log entries. Without context, it can be challenging to relate application failures to logs.

It is a typical log statement:

try
{
   // do something
}
    Catch(sa as exception){
                Logger.error(sa);
                Throw;
}

TraceSource and Log4NET have features that allow adding this contextual information to logs. It makes them far more useful. It adds the contextual data as metadata to the log entries.

Structured Logging urges formatting the data to be logged stably. SeriLog and Log4NET both support arrangements. Indexing is more efficient in structured logs, making them easier to search.

Enable High-Severity Logs in Production

There is useful as well as unnecessary data that gets logged in the activity. For enhancing the performance and avoid using more storage, the target should be Warn and Error messages.

It could be minimized by using #IF Debug in the logging configuration before deploying it in production.

Importance of Log Levels

All frameworks consist of some by default logging levels to each specific message. Info refers to levels, Debug, Warn, Error, and Fatal.

  • Debug & Info usually co-relates as they refer to the vital information to understand the flow and structure.
  • Warn refers to the general warning. It refers to doubtlessness in the program.
  • Error is self-explanatory, as it refers to the error message.
  • Fatal refers to a significant error, which requires termination of the Program or Application.

Logging’s primary purpose is to debug the production environment where the developer cannot explore or debug the code for an exception. Logging gives the developer first-hand information of what happened during execution and what caused the exception. It depends on the developer’s wittiness of what Level of Logging is implemented and what data is logged to be later analyzed to trace issues.

Log Contextual and Diagnostic Data

Using the standard contextual objects available within the library, such as the timestamp, provides much-needed context to the log message. It works as a host of useful information from user location and routes data to HTTPRequest parameters.

With frameworks such as log4net, there is no need to place the developers’ weight to add all of those details to each log message—log4net may add logs automatically. The log4net provides a class, LogicalThreadContext, which allows users to store event-specific data and print it in log files.

For example, the below-mentioned code stores a user ID in the “user” variable. It allows to print it in the log files by adding “%property{user}” to conversionPattern

log4net.LogicalThreadContext.Properties["User"] = User;
Log.Debug(“Found new user”);

Customized layout with Log4net Pattern

<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
      <param name="File" value="stackify.log" />
      <param name="AppendToFile" value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="10MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%-5p %d{MM-dd hh:mm:ss.ffff}  [%thread]  %m%n" />
      </layout>
    </appender>

Using the above layout, write the Level (%p), current date and time (%d), thread # (%thread), the message (%m), and a new line (%n). The -5 in the %-5p Set the width of the field to 5 characters

Some required fields users may log, although it has a performance impact on the app and is not recommended for high capacity logging on a production application.

  • %method: method, where the log message is written.
  • %stacktrace{level}: stack trace output to show where the log message is written
  • %type: type of the caller issuing the log request.
%line: the line number from where logging statement logs

A  layout like this:

<layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%-5p%d{ yyyy-MM-dd HH:mm:ss} – [%thread] %m method:%method %n stacktrace:%stacktrace{5} %n type:%type %n line: %line %n" />
</layout>

Logging is an influential tool for both development and production debugging. To ensure the application user provides the Level of logging necessary for the development and operations sides to track down the bugs and fix them quickly, follow these steps:

  1. Instead of renovating the wheel, use a current logging framework such as TraceSource or log4net.
  2. Use context-rich Logging so that users have all the information they may need when troubleshooting.
  3. Make user logs easily interpreted by other tools by writing structured logs.