Log4Net: set Max backup files on RollingFileAppender with rolling Date
Asked Answered
C

9

82

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>
Centralism answered 18/9, 2008 at 18:24 Comment(0)
D
57

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.

Diaeresis answered 18/9, 2008 at 22:26 Comment(6)
Update it is suppoerted with maxSizeRollBackups: logging.apache.org/log4net/release/sdk/…Thallophyte
@schoetbi, it is still not working. I am making some prototype of this and though maxSizeRollBackups is set to 3, more than 4 files backed up.Loudspeaker
that's not what maxSizeRollBackups does, if a file for a date is locked or too big, a backup is created for the same date. this limits those, not how many"day" of files are saved, just multiple files for one day.Sheeree
fixed the documentation link to: logging.apache.org/log4net/release/sdk/html/… which no longer mentions the "Caution"Jump
@Jump but it says: The maximum applies to each time based group of files and not the total.Spoon
And caution is still in RollingFileAppender class documentation: logging.apache.org/log4net/release/sdk/html/…Spoon
F
44

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);
    }
}
Flanch answered 26/5, 2010 at 20:40 Comment(2)
It's worth being aware that the 'Creation Date' might not be reliable due to this - #2109652Gluck
Also worth noting that this class will only function correctly if the following two conditions are met: 1) StaticLogFileName is set to true. 2) preserveLogFileNameExtension is set to false.Goddord
D
14

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.

Dryfoos answered 7/6, 2016 at 12:19 Comment(2)
this sounds like very easy and interesting idea but I see following problem - each app restart will overwrite the existing file for the day. Or if you configure it to not overwrite the file, instead append - the file will grow indefinitely big. How do you configure this exactly?Binah
@LukasK if you set rollingStyle to composite, maximumFileSize and maxSizeRollbackups, then you can limit the size per file. However, it could be confusing if one month you have three rollovers for day 6 and the next month only two. the '_06.3' rollover from the previous month will be mixed in with the current month.Merge
M
13

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 .

Matchbox answered 12/3, 2010 at 17:40 Comment(2)
how does one go about using this? do you have to download the log4net source, add this and then recompile and grab the new assembly file?Loreeloreen
as far as I know, yesMatchbox
C
2

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>
Carola answered 18/9, 2008 at 18:32 Comment(4)
Not sure of why the down vote. It's considered good form to explain why so that we can all learn.Carola
does seem to miss the point of the question. maxSizeRollBackups does not allow you to e.g. delete files more than 10 days old. I believe that's the sort of thing the OP is trying to achieve.Cyrus
I think you are correct that is what OP wanted. I was a little unclear as to what he was trying to accomplish at the time. My answer was simply and attempt to show him my code. I would still answer the questions the same way in an attempt to be helpful but I can see where you are coming from with the down vote.Carola
If you change "Not sure exactly what you need." to "This isn't what you need, but might be helpful..." and explain what yours does, then maybe no downvote.Gignac
L
2

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...

Log answered 12/3, 2013 at 15:9 Comment(0)
R
1

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.

Roncesvalles answered 9/1, 2017 at 21:22 Comment(1)
Could you give exmaple? I cannot find how to configure it in its WIKIAnnorah
W
0

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://mcmap.net/q/260120/-how-i-can-set-log4net-to-log-my-files-into-different-folders-each-day

Waves answered 27/9, 2012 at 2:35 Comment(0)
P
0

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 */
        }
    }
}
Plains answered 17/12, 2021 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.