Using log4net for logging data in an Windows application

Debugging can be a hard and time-consuming process, especially if we are developing in a multithreaded environment. To understand the execution process we need to capture the log, log4net is a convenient way to capture logs. This tutorial will describe how to use it while we are developing TwinCAT HMI Extension.

In this case, the log file is captured for an application such as windows form, console, etc, 

Log4net has used in windows applications to captured log data. This is very important to log data during run time. It is very easy to log data by following the steps:

STEP 1:

Add an App.config file, here is an example:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>
    <startup> 
      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
  <log4net debug="true">
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="logfiletest.txt" />
      <appendToFile value="false" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="50" />
      <maximumFileSize value="1MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%-5p %d %5rms %-22.22c{1} %-18.18M - %m%n" />
      </layout>
    </appender>
    <root>
      <level value="ALL" />
      <appender-ref ref="RollingLogFileAppender" />
    </root>
  </log4net>
</configuration>

If we want to create the log file to a particular directory we can change like the following though @ can’t be used in the config file. In this case, we have log file in the temp folder.

<file value="c:\\temp\\logfiletest.txt" /> 

STEP 2:

Add the log4net DLL as a reference to the project

STEP 3:

            Call the following method during starting the application, a suitable place could be when we call the main method (for example)

            //Load from App.Config file
            log4net.Config.XmlConfigurator.Configure();

STEP 4:

           In the actual class, we declare a variable 

           private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

STEP 5:

          Call the method to capture log data in the log.txt file as defined in the app.config

          log.InfoFormat("OPC identifier = {0}, initialConnection = {1}", Identifier, initialConnection);

Using log4net for logging data in a DLL

In this case log file is captured for an application such as windows DLL that is loaded by another application, 

It is very easy to log data by following the steps:

STEP 1:

Add an log4net.config file in the DLL project:

<log4net>
  <root>
    <level value="ALL" />
    <!--<appender-ref ref="console" />-->
    <appender-ref ref="file" />
  </root>
  <!--<appender name="console" type="log4net.Appender.ConsoleAppender">
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date %level %logger - %message%newline" />
    </layout>
  </appender>-->
  <appender name="file" type="log4net.Appender.RollingFileAppender">
    <file value="logfile.log" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="5" />
    <maximumFileSize value="10MB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %level %logger - %message%newline" />
    </layout>
  </appender>
</log4net>

If we want to create the log file to a particular directory we can change like the following though @ can’t be used in the config file. In this case, we have log file in the temp folder.

<file value="c:\\temp\\logfiletest.txt" /> 

 

STEP 2:

Add the log4net dll as reference to the project

STEP 3:

            Configure the log4net in the main class of your DLL project, in my case it is Class1 is the main class and I have initialized the log4net here. If we know the path of the DLL loader application then we can use the path  and copy the config there by visual studio build event for example. Because log4net.config should be located in the same folder where the DLL is located. 

 

namespace TestDll
{
    public class Class1
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public Class1 ()
        {
            if (!log4net.LogManager.GetRepository().Configured)
            {
                string s = AssemblyDirectory;
                //Explicitly set the path though we should use any of the method shown here
                s = "C:\\HomeAutomation\\Log4Net_DLL_Testing\\TestDll\\TestDll\\bin\\Debug\\log4net.config";
                string path1 = System.Reflection.Assembly.GetAssembly(typeof(Class1)).Location;
                string directory = Path.GetDirectoryName(path1);
                var ss = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                var configFile = new FileInfo(s);
                if (!configFile.Exists)
                {
                    throw new FileLoadException(String.Format("The configuration file {0} does not exist", configFile));
                }
                log4net.Config.XmlConfigurator.Configure(configFile); 
            }
            log.Info("Initializing  Class1");
        }

        public void CallMethodforLog(int i)
        {
            log.InfoFormat("Calling CallMethodforLog i =  {0}", i);
        }
        public  string AssemblyDirectory
        {
            get
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                string path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
        }
    }
}

STEP 4:

           We call CallMethodforLog  to add log in the file:

 

        public void CallMethodforLog(int i)
        {
            log.InfoFormat("Calling CallMethodforLog i =  {0}", i);
        }

Using log4net for logging data in a DLL loaded by browser framework (TwinCAT HMI Extension)

Precaution:

If we use log4net to capture log data from a DLL which uses a database connection specially Nhibernate then we face a problem to make a connection to the database. The reason is that NHibernate uses a different version of logger DLL  and that does not match the one we use. As a result, Nhibernate throws an exception. In that case, we need to disable the logging.

In this case, the log file is captured by the framework and configured by the DLL. We are using the TwinCAT HMI framework as an example.

It is very easy to log data by following the steps:

STEP 1:

Add a log4net.config file in the  extension DLL project (The file should be copied to for R&D C:\TwinCAT\Functions\TE2000-HMI-Engineering\Infrastructure\TcHmiServer\Latest\win-x86 ):

C:\TwinCAT\Functions\TF2000-HMI-Server\API\1.3.0.0\net48 folder contains logfile.log

C:\TwinCAT\Functions\TF2000-HMI-Server contains the log4net.config file

 

<log4net>
  <root>
    <level value="ALL" />
    <appender-ref ref="file" />
  </root>
  //Add the following if you have Nhibernate module and want to avoid debug log from Nhibernate
 <logger name="NHibernate">
   <level value="ERROR" />
 </logger>
 <logger name="NHibernate.SQL">
   <level value="ERROR" />
 </logger>
 //Add upto this
  <appender name="file" type="log4net.Appender.RollingFileAppender">
    <file value="logfile.log" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="5" />
    <maximumFileSize value="10MB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %level %logger - %message%newline" />
    </layout>
  </appender>
</log4net>

If we want to create the log file to a particular directory we can change like the following though @ can’t be used in the config file. In this case, we have log file in the temp folder.

<file value="c:\\temp\\logfiletest.txt" /> 
 

See a complete example: https://www.hemelix.com/scada-hmi/twincat-hmi/data-grid-view/

 

STEP 2:

Add the log4net DLL as a reference to the project

STEP 3:

            C# extension project wizard does not generate the constructor, you need to build a constructor where you set the log4net configuration. We need to copy the log4net.config file to the following folder. 

 
C:\TwinCAT\Functions\TE2000-HMI-Engineering\\Bin\\x86\TcHmiEngineeringExtensions.Server\
And actual log file will be generated to C:\TwinCAT\Functions\TE2000-HMI-Engineering\Infrastructure\TcHmiServer\1.10.1336.404\Win32\logfile.log
Configure the constructor.
 
string s = AssemblyDirectory;
string path1 = System.Reflection.Assembly.GetAssembly(typeof(ServerExtensionCSharpEmpty1)).Location;
string directory = Path.GetDirectoryName(path1);
var ss = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (!log4net.LogManager.GetRepository().Configured) {
  s = "C:\\TwinCAT\\Functions\\TE2000-HMI-Engineering\\Infrastructure\\TcHmiServer\\Latest\\win-x86\\log4net.config";
  var configFile = new FileInfo(s);
  if (!configFile.Exists) {
    throw new FileLoadException(String.Format("The configuration file {0} does not exist", configFile));
  }
  log4net.Config.XmlConfigurator.Configure(configFile);
}
sqlConnector = new SQLConnecter();
sqlConnector.Do();
initDone = true;
log.Info("path1 =" + path1);
log.Info("s =" + s);
log.Info("ss =" + ss);
log.InfoFormat("Initializing WriteValueInit {0}", command.WriteValue);

 

 

 

STEP 4:

           We use the log method   to log in to the file:

                         switch (mapping)
                          {
                            case "RandomValue":
                                log.Info("Accessing random value");
                                ret = RandomValue(command);
                                break;

                            case "MaxRandom":
                                log.Info("Setting max random value");
                                ret = MaxRandom(command);
                                break;

Click here to download the test project (note that the config file is slightly different, also DEBUG message for NHibernate is disabled)

TIPS 01:  If we don’t add the log4net.config file to the HMI framework folder then HMI will not be loaded.

TIPS 02: Check the log4net.config has the right path. HMI Engineering and HMI Server have different paths. If you are mixing then the extension module will not be loaded

TIPS 03:  We can see sometimes the following message “Index (zero-based) must be greater than or equal to zero” instead of generating logs. The underlying reason for this is 

log.InfoFormat ("I am {1} years old", myAge); //Will cause this error, index start with 1, NOK                                               
log.InfoFormat ("I am {0} years old", myAge); //1 has been changed to 0 OK
log.InfoFormat ("I am {0} years old" + myAge); // there is a +, it should be ,

TIPS 04:  If you are taking logs by using hardware (Embedded PC) then you need to copy log4net.config to this directory, C:\TwinCAT\Functions\TF2000-HMI-Server and the log file will be generated in the same folder.

 

 

TIPS 05:  Data is not visible in the GridView

If you are using extension module then make sure the extension module is enabled. Otherwise extension will not read the data from database and data will not be added. Make sure in the config page of the module that it is enabled.

 

TIPS 06:

How do we pass parameters from HMI to an extension? We are passing doingInit during onAttached event of the TcHmiDatagrid control.

We are using Write to Symbol and in the value field  ServerExtensionCSharpEmpty1.WriteValueInit  fx is pressed and we get in the extension doingInit by the following code.

 log.InfoFormat("InitializingWriteValue {0}", command.WriteValue);

 

Following configuration will pass the value to extension as well.

Following configuration will pass the value ‘doingInit’ (not the apostrophe) will be included

Following configuration will  through an exception, this configuration assumes a defined value is passed (which does not exists)

Taking logs for version 1.12.752.0

It looks different version of HMI engineering has different place for storing the config file. The following configuration works for the above version and generate logs.

=> Get the log4net from Nuget

=> Configure the module with the following code (Normally it can be done when the HMI starts for the first time)

if (!initDone) {
  string s = AssemblyDirectory;
  string path1 = System.Reflection.Assembly.GetAssembly(typeof(ServerExtensionCSharpEmpty1)).Location;
  string directory = Path.GetDirectoryName(path1);
  var ss = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
  if (!log4net.LogManager.GetRepository().Configured) {
    s = "C:\\TwinCAT\\Functions\\TE2000-HMI-Engineering\\Infrastructure\\TcHmiServer\\Latest\\win-x86\\log4net.config";
    var configFile = new FileInfo(s);
    if (!configFile.Exists) {
      throw new FileLoadException(String.Format("The configuration file {0} does not exist", configFile));
    }
    log4net.Config.XmlConfigurator.Configure(configFile);
  }
  sqlConnector = new SQLConnecter();
  sqlConnector.Do();
  initDone = true;
  log.Info("path1 =" + path1);
  log.Info("s =" + s);
  log.Info("ss =" + ss);
  log.InfoFormat("Initializing WriteValueInit {0}", command.WriteValue);
}

s can be written as

 s = @”C:\TwinCAT\Functions\TE2000-HMI-Server\log4net.config //For HMI Server

 

Actual log file is generated in the following folder

C:\TwinCAT\Functions\TE2000-HMI-Engineering\Infrastructure\TcHmiServer\1.12.752.0\win-x64\API\1.3.0.0\net48

s can be written as

 s = @”C:\TwinCAT\Functions\TE2000-HMI-Server\log4net.config //For HMI Server

 

<file value="c:\\temp\\logfiletest.txt" />