Log4Net: set Max backup files on RollingFileAppender with rolling Date

.NetLog4net

.Net Problem Overview


I have the following configuration, but I have not able to find any documentation on how to set a maximum backup files on date rolling style. I know that you can do this with size rolling style by using the maxSizeRollBackups.

<appender name="AppLogFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="mylog.log" />
    <appendToFile value="true" />
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <rollingStyle value="Date" />
    <datePattern value=".yyMMdd.'log'" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%d %-5p %c - %m%n"  />
    </layout>
</appender>

.Net Solutions


Solution 1 - .Net

You can't.

from log4net SDK Reference
RollingFileAppender Class

> CAUTION > > A maximum number of backup files when rolling on date/time boundaries is not supported.

Solution 2 - .Net

Even though its not supported, here is how I handled this situation:

This is my configuration:

	<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
		<file value="C:\logs\LoggingTest\logfile.txt" />
		<appendToFile value="true" />
		<rollingStyle value="Composite" />
		<datePattern value="yyyyMMdd" />
		<maxSizeRollBackups value="10" />
		<maximumFileSize value="1MB" />
		<layout type="log4net.Layout.PatternLayout">
			<conversionPattern value="%date  - %message%newline" />
		</layout>
	</appender>

On application start up I do:

 XmlConfigurator.Configure();
 var date = DateTime.Now.AddDays(-10);
 var task = new LogFileCleanupTask();
 task.CleanUp(date);

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using log4net;
using log4net.Appender;
using log4net.Config;

    public class LogFileCleanupTask
    {
        #region - Constructor -
        public LogFileCleanupTask()
        {
        }
        #endregion

        #region - Methods -
        /// <summary>
        /// Cleans up. Auto configures the cleanup based on the log4net configuration
        /// </summary>
        /// <param name="date">Anything prior will not be kept.</param>
        public void CleanUp(DateTime date)
        {
            string directory = string.Empty;
            string filePrefix = string.Empty;

            var repo = LogManager.GetAllRepositories().FirstOrDefault(); ;
            if (repo == null)
                throw new NotSupportedException("Log4Net has not been configured yet.");

            var app = repo.GetAppenders().Where(x => x.GetType() == typeof(RollingFileAppender)).FirstOrDefault();
            if (app != null)
            {
                var appender = app as RollingFileAppender;

                directory = Path.GetDirectoryName(appender.File);
                filePrefix = Path.GetFileName(appender.File);

                CleanUp(directory, filePrefix, date);
            }
        }

        /// <summary>
        /// Cleans up.
        /// </summary>
        /// <param name="logDirectory">The log directory.</param>
        /// <param name="logPrefix">The log prefix. Example: logfile dont include the file extension.</param>
        /// <param name="date">Anything prior will not be kept.</param>
        public void CleanUp(string logDirectory, string logPrefix, DateTime date)
        {
            if (string.IsNullOrEmpty(logDirectory))
                throw new ArgumentException("logDirectory is missing");

            if (string.IsNullOrEmpty(logPrefix))
                throw new ArgumentException("logPrefix is missing");
                       
            var dirInfo = new DirectoryInfo(logDirectory);
            if (!dirInfo.Exists)
                return;

            var fileInfos = dirInfo.GetFiles("{0}*.*".Sub(logPrefix));
            if (fileInfos.Length == 0)
                return;

            foreach (var info in fileInfos)
            {
                if (info.CreationTime < date)
                {
                    info.Delete();
                }
            }

        }
        #endregion
    }

The Sub Method is an Extension Method, it basically wraps string.format like so:

/// <summary>
/// Extension helper methods for strings
/// </summary>
[DebuggerStepThrough, DebuggerNonUserCode]
public static class StringExtensions
{
    /// <summary>
    /// Formats a string using the <paramref name="format"/> and <paramref name="args"/>.
    /// </summary>
    /// <param name="format">The format.</param>
    /// <param name="args">The args.</param>
    /// <returns>A string with the format placeholders replaced by the args.</returns>
    public static string Sub(this string format, params object[] args)
    {
        return string.Format(format, args);
    }
}

Solution 3 - .Net

To limit the number of logs, do not include the year or month in the datepattern,e.g. datePattern value="_dd'.log'"

This will create a new log each day, and it will get overwritten next month.

Solution 4 - .Net

I spent some time looking into this a few months ago. v1.2.10 doesn't support deleting older log files based on rolling by date. It is on the task list for the next release. I took the source code and added the functionality myself, and posted it for others if they are interested. The issue and the patch can be found at https://issues.apache.org/jira/browse/LOG4NET-27 .

Solution 5 - .Net

Not sure exactly what you need. Below is an extract from one of my lo4net.config files:

  <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
    <param name="File" value="App_Data\log"/>
    <param name="DatePattern" value=".yyyy-MM-dd-tt&quot;.log&quot;"/>
    <param name="AppendToFile" value="true"/>
    <param name="RollingStyle" value="Date"/>
    <param name="StaticLogFileName" value="false"/>
    <param name="maxSizeRollBackups" value="60" />
    <layout type="log4net.Layout.PatternLayout">
      <param name="ConversionPattern" value="%r %d [%t] %-5p %c - %m%n"/>
    </layout>
  </appender>

Solution 6 - .Net

I recently came across this need when attempting to clean up log logs based on a maxAgeInDays configuration value passed into my service... As many have before me, I became exposed to the NTFS 'feature' Tunneling, which makes using FileInfo.CreationDate problematic (though I have since worked around this as well)...

Since I had a pattern to go off of, I decided to just roll my own clean up method... My logger is configured programmatically, so I merely call the following after my logger setup has completed...

    //.........................
    //Log Config Stuff Above...

    log4net.Config.BasicConfigurator.Configure(fileAppender);
    if(logConfig.DaysToKeep > 0)
       CleanupLogs(logConfig.LogFilePath, logConfig.DaysToKeep);
}

static void CleanupLogs(string logPath, int maxAgeInDays)
{
    if (File.Exists(logPath))
    {
        var datePattern = "yyyy.MM.dd";
        List<string> logPatternsToKeep = new List<string>();
        for (var i = 0; i <= maxAgeInDays; i++)
        {
            logPatternsToKeep.Add(DateTime.Now.AddDays(-i).ToString(datePattern));
        }

        FileInfo fi = new FileInfo(logPath);

        var logFiles = fi.Directory.GetFiles(fi.Name + "*")
            .Where(x => logPatternsToKeep.All(y => !x.Name.Contains(y) && x.Name != fi.Name));
        
        foreach (var log in logFiles)
        {
            if (File.Exists(log.FullName)) File.Delete(log.FullName);
        }
    }
}

Probably not the prettiest approach, but working pretty well for our purposes...

Solution 7 - .Net

NLog, which is set up nearly the same way as Log4Net (& is actively maintained - even has support for .NET Core), supports rolling logs based on date.

Solution 8 - .Net

It's fairly easy to inherit from a log4net appender and add say your own override method which performs the clean up of files. I overrode OpenFile to do this. Here's an example of a custom log4net appender to get you started: https://stackoverflow.com/a/2385874/74585

Solution 9 - .Net

Stopped worrying about a more complex x per date and just specified and arbitrary file count and just sort of threw this one together. Be careful with the [SecurityAction.Demand].

public string LogPath { get; set; }
public int MaxFileCount { get; set; } = 10;

private FileSystemWatcher _fileSystemWatcher;

[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public async Task StartAsync()
{
	await Task.Yield();

	if (!Directory.Exists(LogPath))
	{ Directory.CreateDirectory(LogPath); }

	_fileSystemWatcher = new FileSystemWatcher
	{
		Filter = "*.*",
		Path = LogPath,
        EnableRaisingEvents = true,
		NotifyFilter = NotifyFilters.FileName
			| NotifyFilters.LastAccess
			| NotifyFilters.LastWrite
			| NotifyFilters.Security
			| NotifyFilters.Size
	};

	_fileSystemWatcher.Created += OnCreated;
}

public async Task StopAsync()
{
	await Task.Yield();

	_fileSystemWatcher.Created -= OnCreated; // prevents a resource / memory leak.
	_fileSystemWatcher = null; // not using dispose allows us to re-start if necessary.
}

private void OnCreated(object sender, FileSystemEventArgs e)
{
    var fileInfos = Directory
        .GetFiles(LogPath)
        .Select(filePath => new FileInfo(filePath))
        .OrderBy(fileInfo => fileInfo.LastWriteTime)
        .ToArray();

    if (fileInfos.Length <= MaxFileCount)
    { return; }

    // For every file (over MaxFileCount) delete, starting with the oldest file.
    for (var i = 0; i < fileInfos.Length - MaxFileCount; i++)
    {
        try
        {
            fileInfos[i].Delete();
        }
        catch (Exception ex)
        {
            /* Handle */
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRicky SupitView Question on Stackoverflow
Solution 1 - .NetCharley RathkopfView Answer on Stackoverflow
Solution 2 - .NetJeffView Answer on Stackoverflow
Solution 3 - .NetPhilView Answer on Stackoverflow
Solution 4 - .NetMafu JoshView Answer on Stackoverflow
Solution 5 - .NetwcmView Answer on Stackoverflow
Solution 6 - .NetmattezellView Answer on Stackoverflow
Solution 7 - .NetCoruscate5View Answer on Stackoverflow
Solution 8 - .NetMatthew LockView Answer on Stackoverflow
Solution 9 - .NetHouseCatView Answer on Stackoverflow