NUnit & testing log4net dynamic log file location - c#

I'm writing an application where the user can change (at runtime) the directory where the log4net log is stored. The directory string is stored in the app.config.
When I want to test if the log file is created in the right directory with NUnit, the logfile (and the corresponding directory) is not created.
When looking online for this problem I read that NUnit stops the logging from working because it uses log4net itself. The provided sample tells you to create a additional .config (Test.config) which also contains the log4net sections and to load the configuration inside the testing class, which I did.
There is still no log file created when using the unit test.
When starting the application, the log file is created as it should.
Method to set the log directory:
public void MyMethod()
{
string logDirectory = app.Settings["LogDirectory"].Value;
//assure that the file name can be appended to the path
if (!logDirectory.EndsWith(#"\"))
{
logDirectory += #"\";
}
//find the rolling file appender and set its file name
XmlConfigurator.Configure();
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
foreach (IAppender appender in hierarchy.Root.Appenders)
{
if (appender is RollingFileAppender)
{
RollingFileAppender fileAppender = (RollingFileAppender)appender;
string logFileLocation = string.Format("{0}Estimation_Protocol_{1}.txt",
logDirectory, EstimatorHelper.SoftwareVersionAndDateTime());
fileAppender.File = logFileLocation;
fileAppender.ActivateOptions();
break;
}
}
log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
log.Debug("Logging directory & file name set.");
}
The test class:
class EstimatorTests
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public EstimatorTests()
{
FileInfo fileInfo = new FileInfo(#"%property{LogName}");
log4net.Config.XmlConfigurator.Configure(fileInfo);
}
[Test]
public void TestLoadInputPaths()
{
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
AppSettingsSection app = config.AppSettings;
string time = DateTime.Now.Ticks.ToString();
app.Settings["ProcessingProtocolDirectory"].Value = "C:\\thisFolderDoesntExist" + time;
Form mainForm = new Form();
Form.MyMethod();
Assert.IsTrue(Directory.Exists("C:\\thisFolderDoesntExist" + time));
//this assert fails!
}
}
The log4net config:
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="%property{LogName}" />
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="5"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %level %logger - %message%newline%exception%newline"/>
</layout>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="RollingFileAppender"/>
</root>
</log4net>

I did not test it but I think you need to remove the call to XmlConfigurator.Configure(); in MyMethod because this will overwrite the configuration you do in the test class.

Related

A Russian string is displayed in the log.txt - this: ???? C# when used log4net

I try to print russian string to log. used log4net.
this is my code:
A file where the messages are written in Web.config
<configuration>
<appSettings>
<add key="logFilePath" value="~/logTest.log" />
</appSettings>
</configSections>
<log4net>
<appender name="RollingFileAppender"
type="log4net.Appender.RollingFileAppender">
<file value="C:\log.txt"/>
<encoding value="utf-8" />
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="5"/>
<maximumFileSize value="2MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%newline%date [%thread] %level %logger
- %message%newline%exception%newline-------------------------
---------------------------------------"/>
</layout>
</appender>
<root>
<level value="WARN"/>
<appender-ref ref="RollingFileAppender"/>
</root>
</log4net>
the class Handles writing to the log and configurations:
namespace Utils
{
public class ClsLog
{
private static readonly log4net.ILog log = log4net.LogManager
.GetLogger(System.Reflection.MethodBase
.GetCurrentMethod().DeclaringType);
private static bool isInitialized = false;
public static void WriteLog(enmLogType logType, string message)
{
Initialize();
log.Debug(message);
}
private static void Initialize()
{
if (isInitialized)
return;
string path = ConfigurationManager.AppSettings["logFilePath"];
string currentDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
currentDir = currentDir.Substring(6);
currentDir += "\\..";
FileInfo finfo = new FileInfo(currentDir + "\\log4net.config");
log4net.Config.XmlConfigurator.ConfigureAndWatch(finfo);
log4net.Repository.Hierarchy.Hierarchy h =
(log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
foreach (IAppender a in h.Root.Appenders)
{
if (a is FileAppender)
{
FileAppender fa = (FileAppender)a;
// Programmatically set this to the desired location here
if (path != null)
{
string logFileLocation = path;
fa.File = logFileLocation;
fa.ActivateOptions();
break;
}
}
}
isInitialized = true;
}
}
}
print string in Russian:
public void print()
{
Utils.ClsLog.WriteLog(enmLogType.Debug, "Русский");
}
but the output in the file log is:
2023-01-16 12:52:17,282 DEBUG ???????
how I will get this result?:
2023-01-16 12:52:17,282 DEBUG Русский
and how I know what Encoding is used when file is created?
The following line:
<encoding value="utf-8" />
Was hidden in the project. It should have been added to log4net.Config, and not in Web.Config.

Log4net Configuration with Static File Names But Archives with Date in Names

I would like to configure log4net to have 2 different active log files (lets say debug.log and error.log) under "logs" directory and daily roll them into directories named with date (yyyyMMdd) and also change the name of the files by adding the date to their names.
Here is an example directory structure output that I would like to have:
logs/
logs/debug.log
logs/error.log
logs/20180117/debug.log_20180117
logs/20180117/error.log_20180117
logs/20180116/debug.log_20180116
logs/20180116/error.log_20180116
...
Is it possible to have this with log4net? If so please share the configuration.
You going to need custom rolling appender to handle your unique requirement.
First you will have create custom appender from RollingFileAppender, override AdjustFileBeforeAppend() method as below
public class CustomRollingAppender : RollingFileAppender
{
DateTime next = DateTime.Today;
public CustomRollingAppender()
{
}
protected override void AdjustFileBeforeAppend()
{
string file = File;
DateTime newDt = DateTime.Today;
if (next < newDt)
{
next = newDt.AddDays(1);
string rollDir = Path.Combine(Path.GetDirectoryName(file), DateTime.Today.ToString("yyyyMMdd"));
Directory.CreateDirectory(rollDir);
string toFile = Path.Combine(rollDir, String.Format("{0}_{1}", Path.GetFileName(file), DateTime.Today.ToString("yyyyMMdd")));
this.CloseFile();
RollFile(file, toFile);
SafeOpenFile(File, false);
}
base.AdjustFileBeforeAppend();
}
}
Finally configure it in app.config as below
<appender name="FileAppender" type="Log4NetTest1.CustomRollingAppender">
<file value="logs/error.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] – %message%newline" />
</layout>
<preserveLogFileNameExtension value="true" />
</appender>
PS: It is not 100% tested, please test it thoroughly before put it into PROD.
Hope it helps.

Add variables to filename of rollingfileappender in Log4Net

Is there a way to make the name of the fileAppender variable?
I.e. when I call an action on my controller which takes an object, I would like to write this to a log file.
The name of the file would look something like :
yyyyMMdd_hhmssms_[controller]_[method].json
this is what I have in my config-file:
<appender name="JsonFileAppender" type="log4net.Appender.RollingFileAppender" >
<file value="c:\temp\" />
<datePattern value="yyyyMMdd_hh.mm.ss.ms_%thread{CommonApplicationData}'.json'" />
<staticLogFileName value="false" />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="5MB" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%message%newline" />
</layout>
</appender>
This returns the following filename : 20160224_01.30.28.3028_P1rea24{Co30onApplicaPionDaPa}.json
one way is to set an Environment Variable in your code like:
Environment.SetEnvironmentVariable("APPENDER_FILE", "Your File Path");
and then, configure this environment variable in log4net XML:
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="${APPENDER_FILE}"/>
You can access the appenders of your log4net configuration at run-time like so
var repository = (Hierarchy)LogManager.GetRepository();
var appenders = repository.GetAppenders().Where(x => x is FileAppender);
You can get specific appender then by name
var appender = appenders.FirstOrDefault(x => x.Name.Equals("MyAppeader"));
Once you have an appender you can modify it how you like. You want to set the filepath
appender.File = #"c:\folder\yyyyMMdd_hhmssms_[controller]_[method].json";
You should not have the do anything else as log4net should automatically start using the new configuration.
Placing this all into a little helper method, you'd get this
public static void SetAppenderPath(string appender, string path)
{
var repository = (Hierarchy)LogManager.GetRepository();
var appenders = repository.GetAppenders().Where(x => x is FileAppender);
var appender = appenders.FirstOfDefault(x => x.Name.Equals(appender));
if (appender == null)
{
throw new ConfigurationErrorsException("Appender not found (" + appender + ")");
}
appender.File = path;
}
...
LogHelper.SetAppenderPath("MyAppender", #"yyyyMMdd_hhmssms_[controller]_[method].json");

Wrapper around log4net is not writing anything to text files

I have been given a dll named MYLogger.dll which is apparently a wrapper around log4net and has a MyLogger and a DefaultLogger classes defined in it and both these classes inherit ILogger. And I have to log exceptions and info for an Asp.Net Web Api service which has several projects defined within one solution in .net.
And these the signature for MyLogger class
public MyLogger(string name, string configFilePath = "",
string newLogFileFolderName = "");
public MyLogger(Type t, string configFilePath = "",
string newLogFileFolderName = "");
public event EventHandler<EventArgs<string>> MessageLogged;
public void Log(LogLevel logLevel, string message, object[] args = null);
public void LogException(Exception ex, string customMessage = "",
LogLevel logLevel = LogLevel.Error);
And signature for DefaultLogger class is
public DefaultLog();
public static DefaultLog Logger { get; }
public event EventHandler<EventArgs<string>> MessageLogged;
public void Log(LogLevel logLevel, string message, object[] args = null);
public void LogException(Exception ex, string customMessage = "",
LogLevel logLevel = LogLevel.Error);
And my config file named MyLog.Config looks like
<configuration>
<!-- Register a section handler for the log4net section -->
<configSections>
<section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
</configSections>
<log4net>
<appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="C\Data\Log\MyErrorLog.txt" />
<appendToFile value="true" />
<maxSizeRollBackups value="-1" />
<maximumFileSize value="10MB" />
<rollingStyle value="Size" />
<layout type="MyLogger.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] <%property{auth}> - %message%newline" />
</layout>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="ERROR" />
<levelMax value="FATAL" />
</filter>
</appender>
<appender name="InfoFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="C\Data\Log\MyInfoLog.txt" />
<appendToFile value="true" />
<maxSizeRollBackups value="-1" />
<maximumFileSize value="10MB" />
<rollingStyle value="Size" />
<layout type="MyLogger.MyPatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] <%property{auth}> - %message%newline" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="INFO" />
</filter>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="ErrorFileAppender" />
<appender-ref ref="InfoFileAppender" />
</root>
</log4net>
</configuration>
And I'm trying to use both the logger class viz MyLogger for both exception logging and info logging as below.
private static ILogger log = new Logger(MethodBase.GetCurrentMethod()
.DeclaringType, #"~\MyLog.config"," ");
Even when i give path to second parameter(for either info or exception text file) rather being empty, still nothing is written to either of them. I tried to throw the exception but still no success for exception logging.
And with the help of log object I'm trying to achieve logging for info as below
string message = string.Format("The feature class name is {0} and the type is {1}",Name,Type);
//DefaultLog.Logger.Log(LogLevel.Info, message);
log.Log(LogLevel.Info, message);
as you can see I tried both with DefaultLog and log object(which is an implementation of ILog interface with MyLogger class) and the same way I'm trying to achieve exceptions using following code at the time some exception is thrown inside by a controller action
catch (HttpResponseException ex)
{
string errorMessage = string.Format("Getting {0} data gives error because either fcollection is empty or soemthing is null Error:{1}",
Name, ex.Message);
//DefaultLog.Logger.LogException(ex, errorMessage, LogLevel.Error);
log.LogException(ex, errorMessage, LogLevel.Error);
}
For both info and exception nothing gets logged/written into text files mentioned along with their respective paths inside my Xml config file.
And the weirdest thing about the two constructors of MyLogger class is that both constructors have string as a parameter for configfile path and the string as a parameter for newLogFileFolderName, how could I figure out that this newLogFileFolderName is asking for path for text file where info logs are written or text file where exception logs are written. And I'm assuming that inside the constructors of MyLogger class something like
XmlConfigurator.ConfigureAndWatch(new FileInfo(MyLog.config))
has been used that's why 1st parameter for config file has been substituted for ConfigureAndWatch ( ) method's argument but I have no idea what is the use of second parameter inside constructor code of MyLogger i.e do I need to instantiate MyLogger class twice once with path for info text file and second time path for exception text file as a second parameter in the constructor. That doesn't make sense to me.Its highly confusing. All suggestion will be highly appreciated.

Get log4net log file in C#

This is my configuration for log4net:
<log4net>
<appender name="MyLogger" type="log4net.Appender.RollingFileAppender">
<file value="MyLog.log" />
<appendToFile value="true" />
<rollingStyle value="Size"/>
<maxSizeRollBackups value="20"/>
<maximumFileSize value="1000KB"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss},%p,%m%n" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="MyLogger" />
</root>
</log4net>
In C# I'm trying to get the name of the log file (which is MyLog.log). I googled and tried many things but failed to do so. Any help?
Thanks!
Solution is quite easy in your situation; just use this code:
var rootAppender = ((Hierarchy)LogManager.GetRepository())
.Root.Appenders.OfType<FileAppender>()
.FirstOrDefault();
string filename = rootAppender != null ? rootAppender.File : string.Empty;
When having multiple file appenders, you might want to get them by name. Also to make sure to get the appender even if it is not referenced by the root node, the following code helps:
public static string GetLogFileName(string name)
{
var rootAppender = LogManager.GetRepository()
.GetAppenders()
.OfType<FileAppender>()
.FirstOrDefault(fa => fa.Name == name);
return rootAppender != null ? rootAppender.File : string.Empty;
}
Since I already had a logger defined in the class I just used it. One thing to be aware of is that there may be more than one appender and often the first one is the console (which doesn't have a file). Here is my solution for what its worth.
using log4net;
using log4net.Appender;
using log4net.Repository;
namespace MyNameSpace {
public class MyClass {
private static readonly ILog logger = LogManager.GetLogger(typeof(MyClass));
public String GetLogFileName() {
String filename = null;
IAppender[] appenders = logger.Logger.Repository.GetAppenders();
// Check each appender this logger has
foreach (IAppender appender in appenders) {
Type t = appender.GetType();
// Get the file name from the first FileAppender found and return
if (t.Equals(typeof(FileAppender)) || t.Equals(typeof(RollingFileAppender))) {
filename = ((FileAppender)appender).File;
break;
}
}
return filename;
}
}
}
String filename = null;
Hierarchy hierarchy = LogManager.GetRepository() as Hierarchy;
Logger logger = hierarchy.Root;
IAppender[] appenders = logger.Repository.GetAppenders();
// Check each appender this logger has
foreach (IAppender appender in appenders)
{
Type t = appender.GetType();
// Get the file name from the first FileAppender found and return
if (t.Equals(typeof(FileAppender)) || t.Equals(typeof(RollingFileAppender)))
{
filename = ((FileAppender)appender).File;
break;
}
}
System.Diagnostics.Process.Start(filename); //for example, open file in notepad
((log4net.Appender.FileAppender)(_log.Logger.Repository.GetAppenders())[0]).File
If your config does not have a <root> node then the above solution will not work for you. Read on.
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="${LOCALAPPDATA}\Anonymous.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="2000KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<logger name="AnonymousLog">
<level value="All" />
<appender-ref ref="RollingFileAppender" />
</logger>
</log4net>
This retrieves the log file:
string path = (LogManager.GetCurrentLoggers()[0].Logger.Repository.GetAppenders()[0] as FileAppender).File;
The (hopefully) crash-proof version:
string path = null;
if (LogManager.GetCurrentLoggers().Length > 0 && LogManager.GetCurrentLoggers()[0].Logger.Repository.GetAppenders().Length > 0)
{
path = (LogManager.GetCurrentLoggers()[0].Logger.Repository.GetAppenders()[0] as FileAppender).File;
}
Finally, if you get stuck with log4net add this to your <appSettings> section:
<add key="log4net.Internal.Debug" value="true"/>
I didn't find the above code working.
This worked for me
var filename= ((log4net.Appender.FileAppender)(((log4net.Appender.IAppender[])((((((log4net.Repository.Hierarchy.Hierarchy)((((log4net.Core.LoggerWrapperImpl)(log)).Logger).Repository)).Root).Hierarchy.Root).Appenders).SyncRoot))[0])).File

Categories