3

我想使用归档来限制日志文件的数量,并且我希望每个归档日志的文件名是日志的日期。很简单。

这是我的目标之一:

<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"/>

我已经读到您必须在archiveFileName 中包含{#},否则当您在文件名中包含日期时,归档将根本不起作用,这有点烦人,但我想我可以忍受。

但是,由于归档是在日期更改执行的,因此 ${shortdate} 将始终成为日期的日期,即在所需日期之后一天(或更多天)。如果今天记录了一条消息,即 2013-06-12,那么当它明天归档时,它将被放置在一个名为 2013-06-13.log 的文件中。

什么办法可以得到正确的日期吗?我见过有人建议一个变量,但我不明白它是如何工作的......这似乎是一个如此明显的特性。它绝对应该是存档编号模式之一!与日期相比,现在可用的编号模式似乎太不切实际了。

这个问题有点与Delete log files after x days相关。如果可以在不使用存档功能的情况下设置最大日志文件数(因为这是我真正想要的存档功能),那实际上会更好,因为我也不必使用 {#},但我怀疑这是不可能的。

4

2 回答 2

1

NLog 自发布此问题以来添加了编号格式“日期”,这正是我正在寻找的。

有关更多信息,请参阅https://github.com/nlog/NLog/wiki/File-target#archival-options

于 2015-10-23T14:18:35.663 回答
0

这可能被认为是一种Rube Goldberg方法,但它可能会起作用......您可以编写一个自定义 LayoutRenderer 来计算“先前”日期。此 LayoutRenderer 的参数将是 Target 配置中的“archiveEvery”设置。

使用 ShortDateLayoutRenderer 作为基础...

(取自 NLog 的 git 存储库...)

[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));
    }
}

PreviousDateLayoutRenderer 可能看起来像这样:(请注意,我既没有编译也没有测试过这段代码,但我之前写过 LayoutRenderers)。

[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));
    }
}

或者,您可以编写一个 LayoutRendererWrapper,它会应用非常相似的逻辑,但它会被传递一个字符串(包装器会尝试将其解释为日期)并从中减去所需的 TimeSpan。

给定我上面简要描述的包装器,它可能配置如下:

<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"/>

我的建议假定归档文件应根据当前日期的“上一个日期”命名。这也有点取决于您上面提到的文件在日期更改时滚动的事实,因此将“当前”日期分配给文件名而不是“上一个”日期。在某些情况下,这种方法可能无法提供您可能想要的结果。如果您的应用程序只在工作日运行怎么办?它在星期一全天运行,然后在星期二的第一个日志中滚动日志文件并根据前一天(星期一)的日期命名。没事儿。本周剩下的时间,也就是直到周末,这都很好。当程序在星期五运行时,会捕获日志。该程序不会在周末运行。星期一,第一次记录消息时,日志文件滚动。在这种情况下,前一个日期将是星期日,而您可能希望前一个日期是星期五。也可能是由于某种原因,某一天没有日志。第二天有一个日志,导致翻转。同样,我描述的方法将确定前一天是实际的“物理”前一天,此时您可能更喜欢“前一天”来表示前一天“有任何日志写入时”。

这已经有点长了,但也许你会发现它很有用。

于 2013-06-12T18:29:37.973 回答