Question

I want to use archiving to limit the number of log files, and I want the file name of each archived log to be the date which the log is from. Simple enough.

This is one of my targets:

<target xsi:type="File" name="info" fileName="${basedir}/logs/info.log"
        layout="${date:format=HH\:mm\:ss}&#009;|&#009;${uppercase:${level}}&#009;|&#009;${message}"
        archiveEvery="Day"
        archiveFileName="${basedir}/logs/archive/info/${shortdate}.{#}.log"
        archiveNumbering="Rolling"
        maxArchiveFiles="30"/>

I have read that you must have the {#} in the archiveFileName or the archiving won't work at all when you have the date in the file name, which is kind of annoying but I guess I can live with that.

However, because the archiving is performed after the date is changed, the ${shortdate} will always become the new day's date, i.e. one day (or more) after the desired date. If a message is logged today, 2013-06-12, then when it's archived tomorrow it will be placed in a file called 2013-06-13.log.

Is there any way to get the correct date? I have seen someone suggesting a variable, but I don't see how that would work... This just seems like such an obvious feature to have. It should definitely be one of the archiveNumbering modes! The numbering modes available now just seem so impractical compared to dates.

This question is kind of related to Delete log files after x days. If it was possible to set max number of log files (because that's what I actually want from the archiving feature) without using the archiving feature, that would actually be even better because I wouldn't have to use {#} either, but I suspect that isn't possible.

Was it helpful?

Solution

NLog has since posting this question added the numbering format "Date" which is exactly what I was looking for.

See https://github.com/nlog/NLog/wiki/File-target#archival-options for more information.

OTHER TIPS

This might be considered sort of a Rube Goldberg approach, but it might work... You could write a custom LayoutRenderer that computes the "previous" date. A parameter for this LayoutRenderer would be the "archiveEvery" setting from your Target configuration.

Using the ShortDateLayoutRenderer as a basis...

(Taken from NLog's git repository...)

[LayoutRenderer("shortdate")]
[ThreadAgnostic]
public class ShortDateLayoutRenderer : LayoutRenderer
{
    /// <summary>
    /// Gets or sets a value indicating whether to output UTC time instead of local time.
    /// </summary>
    /// <docgen category='Rendering Options' order='10' />
    [DefaultValue(false)]
    public bool UniversalTime { get; set; }

    /// <summary>
    /// Renders the current short date string (yyyy-MM-dd) and appends it to the specified <see cref="StringBuilder" />.
    /// </summary>
    /// <param name="builder">The <see cref="StringBuilder"/> to append the rendered data to.</param>
    /// <param name="logEvent">Logging event.</param>
    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        var ts = logEvent.TimeStamp;
        if (this.UniversalTime)
        {
            ts = ts.ToUniversalTime();
        }

        builder.Append(ts.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
    }
}

A PreviousDateLayoutRenderer might look something like this: (Note that I have neither compiled nor tested this code, but I have written LayoutRenderers before).

[LayoutRenderer("previousdate")]
[ThreadAgnostic]
public class PreviousDateLayoutRenderer : LayoutRenderer
{
    /// <summary>
    /// Gets or sets a value indicating whether to output UTC time instead of local time.
    /// </summary>
    /// <docgen category='Rendering Options' order='10' />
    [DefaultValue(false)]
    public bool UniversalTime { get; set; }

    /// <summary>
    /// Gets or sets the value indicating the unit of time to subtract to get previous date.
    /// </summary>
    [DefaultValue("Day")]
    public string TimeUnit { get; set; }

    /// <summary>
    /// Gets the current date, subtracts one TimeUnit, renders the resulting short date string,
    /// then appends it to the specified <see cref="StringBuilder" />.
    /// </summary>
    /// <param name="builder">The <see cref="StringBuilder"/> to append the rendered data to.</param>
    /// <param name="logEvent">Logging event.</param>
    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        var ts = logEvent.TimeStamp;
        if (this.UniversalTime)
        {
            ts = ts.ToUniversalTime();
        }

        // This could certainly be better.  Probably smarter to put code in the setter of the
        // TimeUnit property to compute a TimeSpan member variable that could then be subtracted
        // in this method rather than check the TimeUnit and compute the TimeSpan every time.

        TimeSpan span;

        switch (TimeUnit)
        {
          case "Day":
            span = TimeSpan.FromDays(1);
            break;
          case "Hour":
            span = TimeSpan.FromHours(1);
            break;
        }

        ts -= span;

        builder.Append(ts.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
    }
}

Alternatively, you could write a LayoutRendererWrapper, which would apply very similar logic, but it would be passed a string (that the wrapper would try to interpret as a date) and would subtract the desired TimeSpan from that.

Given a wrapper as I described briefly above, it might be configured like this:

<target xsi:type="File" name="info" fileName="${basedir}/logs/info.log"
        layout="${date:format=HH\:mm\:ss}&#009;|&#009;${uppercase:${level}}&#009;|&#009;${message}"
        archiveEvery="Day"
        archiveFileName="${basedir}/logs/archive/info/${previousdate{shortdate,"Day"}}.{#}.log"
        archiveNumbering="Rolling"
        maxArchiveFiles="30"/>

My proposal assumes that the archived file should be named based on the "previous date" to the current date. It also sort of depends on the fact that you mentioned above that the file is rolled upon date change, thus the assignment of the "current" date to the filename rather than the "previous" date. There are certainly cases where this approach might not give the results that you might want. What if your application only runs on weekdays? It runs all day Monday, then with the first log on Tuesday the log file rolls and is named based on the previous day's (Monday) date. That is fine. It is fine for the rest of the week, that is, until the weekend. As the program runs on Friday, logs are captured. The program does not run over the weekend. On Monday, the first time a message is logged, the log file is rolled. In this case, the previous date will be Sunday when you probably would prefer that the previous date be Friday. It could also be the case that, for whatever reason, there are no logs for a given day. The following day there is a log, which causes a rollover. Again, the approach I describe will determine the previous date to be the actual "physical" previous day, when you might prefer "previous" to mean the previous day "when there were any logs written".

This has gotten kind of long, but maybe you will find it useful.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top