Log4net Configuration with Static File Names But Archives with Date in Names - c#

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.

Related

Changing log4net file name at runtime without changing the appender

I'd like to change the name of my log at runtime without changing the appender. Basically, once it gets to a certain set time of day I'd like to be able to use a different file.
This is what my appender looks like:
<appender name="info" type="log4net.Appender.RollingFileAppender">
<file value="logs\" />
<datePattern value="yyyyMMdd'_INFO.log'" />
<staticLogFileName value="false" />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="10MB" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%newline[%date]-%level-%logger[%M]- Linea:%L - %message%newline"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="INFO"/>
<param name="LevelMax" value="INFO"/>
</filter>
</appender>
This is the function where I iterate through the appenders:
public void SetLogFile(string fileName)
{
foreach (var appender in log.Logger.Repository.GetAppenders())
{
try
{
((log4net.Appender.FileAppender)appender).File = fileName;
((log4net.Appender.FileAppender)appender).ActivateOptions();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
SetLogFile("log1.txt");
Which creates the following file for this specific example:
log1.txt20171221_INFO.log
I'd like for my log file to be named log1.txt and not have the datePattern set in the log4net.config file concatenated with the name, but I can't figure out how to do it without changing the log4net.config appender configuration.
As you are using RollFileAppender, you have to change the RollingStyle and set the path accordingly.
foreach (var appender in log.Logger.Repository.GetAppenders())
{
try
{
string file = Path.GetDirectoryName(((log4net.Appender.RollingFileAppender)appender).File);
string filename = Path.Combine(file, fileName);
switch (((log4net.Appender.RollingFileAppender)appender).RollingStyle)
{
case log4net.Appender.RollingFileAppender.RollingMode.Date:
((log4net.Appender.RollingFileAppender)appender).RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Once;
break;
case log4net.Appender.RollingFileAppender.RollingMode.Composite:
((log4net.Appender.RollingFileAppender)appender).RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Size;
break;
}
((log4net.Appender.FileAppender)appender).File = filename;
((log4net.Appender.FileAppender)appender).ActivateOptions();
}
catch (Exception ex) {}
}

Folder name of where file is getting logged

Here is how log4net is been setup
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="..\AppLogs\%property{LogName}.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="5MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d{yyyy/MM/dd HH:mm:ss} [%thread] %-5level %logger - %message%newline" />
</layout>
............
</log4net>
I know I can get the file name of log file using something like below.
var fileAppender = log4net.LogManager.GetRepository().GetAppenders().First(appender => appender is RollingFileAppender);
However , I want to get the folder name where the log file will be created. Is there anyway of getting this?
var fileAppender = _logger.Logger.Repository.GetAppenders().OfType<RollingFileAppender>().FirstOrDefault();
assuming that you are declaring your instance of the _logger like this
private static readonly ILog _logger = LogManager.GetLogger(typeof(YourClass));
The File property contains the full path to the log file. Once you have that, use Path.GetDirectoryName to get the folder.
This MSTest unit test shows that:
[TestInitialize]
public void Setup()
{
log4net.GlobalContext.Properties["LogName"] = "testlogger";
var fileInfo = new FileInfo("log4net.config.xml");
if (fileInfo.Exists == false)
{
throw new InvalidOperationException("Can't locate the log4net config file");
}
LogManager.ResetConfiguration();
log4net.Config.XmlConfigurator.Configure(fileInfo);
}
[TestMethod]
[DeploymentItem(#"log4net.config.xml")]
public void log4net_RollingFileAppender_Is_Configured_Correctly()
{
var appender = log4net.LogManager.GetRepository()
.GetAppenders()
.OfType<RollingFileAppender>()
.First();
Console.WriteLine(appender.File);
Console.WriteLine(Path.GetDirectoryName(appender.File));
}
And in the config:
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="..\AppLogs\%property{LogName}.txt" />
Test result:

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.

NUnit & testing log4net dynamic log file location

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.

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