An initial commit of the rolling file appender ng implementation [LOG4NET-367]
Project: http://git-wip-us.apache.org/repos/asf/logging-log4net/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4net/commit/829b2eb5 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4net/tree/829b2eb5 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4net/diff/829b2eb5 Branch: refs/heads/feature/RollingFileAppender-NG Commit: 829b2eb59d14caa0bcbfeeea34c65154741b9b4c Parents: cde6b69 Author: Dominik Psenner <dpsen...@apache.org> Authored: Fri May 19 21:47:32 2017 +0200 Committer: Dominik Psenner <dpsen...@apache.org> Committed: Fri May 19 21:47:32 2017 +0200 ---------------------------------------------------------------------- src/Appender/Rolling/CronRollingCondition.cs | 230 +++++++++++++++++++ src/Appender/Rolling/IRollingCondition.cs | 38 +++ src/Appender/Rolling/IRollingStrategy.cs | 36 +++ src/Appender/Rolling/IndexRollingStrategy.cs | 77 +++++++ src/Appender/RollingFileAppenderNG.cs | 152 ++++++++++++ .../Rolling/CronRollingConditionTest.cs | 45 ++++ .../Rolling/IndexRollingStrategyTest.cs | 83 +++++++ tests/src/log4net.Tests.vs2012.csproj | 2 + 8 files changed, 663 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/829b2eb5/src/Appender/Rolling/CronRollingCondition.cs ---------------------------------------------------------------------- diff --git a/src/Appender/Rolling/CronRollingCondition.cs b/src/Appender/Rolling/CronRollingCondition.cs new file mode 100644 index 0000000..f355648 --- /dev/null +++ b/src/Appender/Rolling/CronRollingCondition.cs @@ -0,0 +1,230 @@ +#region Apache License +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#endregion + +using System; +using System.IO; + +namespace log4net.Appender.Rolling +{ + /// <summary> + /// A implementation of the <see cref="IRollingCondition"/> interface that rolls + /// the file cronologically. + /// </summary> + /// <author>Dominik Psenner</author> + public class CronRollingCondition : IRollingCondition + { + #region Public Instance Constructors + + public CronRollingCondition() + : this("*", "*", "*", "*", "*") + { + } + + public CronRollingCondition(string dow, string month, string day, string hour, string minute) + { + Dow = TryParse(dow); + Month = TryParse(month); + Day = TryParse(day); + Hour = TryParse(hour); + Minute = TryParse(minute); + } + + #endregion + + #region Protected Instance Properties + + protected Tuple<int?, MatchType> Dow { get; private set; } + protected Tuple<int?, MatchType> Month { get; private set; } + protected Tuple<int?, MatchType> Day { get; private set; } + protected Tuple<int?, MatchType> Hour { get; private set; } + protected Tuple<int?, MatchType> Minute { get; private set; } + + #endregion + + #region Private Instance Properties + + private ulong last_rolled = 0; + + #endregion + + #region Protected Inner Classes + + protected enum MatchType + { + Nothing, + Exact, + Remainder, + } + + #endregion + + #region Protected Static Methods + + /// <summary> + /// This method parses a string to match any of these: + /// i + /// * + /// */i + /// </summary> + /// <param name="input"></param> + /// <returns></returns> + static protected Tuple<int?, MatchType> TryParse(string input) + { + // trim input and strip empty spaces + string inputParsed = input.Trim().Replace(" ", "").Replace("\t", ""); + + // match a remainder: */c + if (inputParsed.StartsWith("*/") || inputParsed.StartsWith(@"*\")) + { + // strip first two chars + inputParsed = inputParsed.Substring(2); + // parse the remainder + int i = -1; + if (int.TryParse(inputParsed, out i)) + { + return Tuple.Create<int?, MatchType>(i, MatchType.Remainder); + } + } + else if (inputParsed.StartsWith("*")) // match anything: * + { + return Tuple.Create<int?, MatchType>(null, MatchType.Nothing); + } + else // match one specific numer: i + { + int i = -1; + if (int.TryParse(inputParsed, out i)) + { + return Tuple.Create<int?, MatchType>(i, MatchType.Exact); + } + } + + // throw exception by default + throw new FormatException(string.Format("The input string '{0}' could not be parsed to a valid format.", input)); + } + + #endregion + + #region Implementation of IRollingCondition + + public bool IsMet(string file) + { + return IsMet(DateTime.Now); + } + + private static ulong GetUniqueIndex(DateTime now) + { + ulong result = (ulong)now.DayOfWeek; + result <<= 3; + result += (ulong)now.Month; + result <<= 4; + result += (ulong)now.Day; + result <<= 5; + result += (ulong)now.Hour; + result <<= 5; + result += (ulong)now.Minute; + return result; + } + + #endregion + + #region Public Methods + + public bool IsMet(DateTime now) + { + Console.WriteLine("test {0}", now); + // check only every minute + // we can skip the check as we checked this minute already + // and if we don't we may run into the situation to roll a file twice + if (GetUniqueIndex(now) == last_rolled) + { + Console.WriteLine(" skipped"); + return false; + } + if (!IsMet(Dow, (int)now.DayOfWeek)) + { + return false; + } + if (!IsMet(Month, now.Month)) + { + return false; + } + if (!IsMet(Day, now.Day)) + { + return false; + } + if (!IsMet(Hour, now.Hour)) + { + return false; + } + if (!IsMet(Minute, now.Minute)) + { + return false; + } + + last_rolled = GetUniqueIndex(now); + return true; + } + + #endregion + + #region Private Methods + + private bool IsMet(Tuple<int?, MatchType> match, int item) + { + switch (match.Item2) + { + case MatchType.Exact: + Console.WriteLine(" {0} != {1} == {2}", match.Item1.Value, item, match.Item1.Value != item); + if (match.Item1.Value != item) + { + return false; + } + break; + case MatchType.Remainder: + // special case: */0, the division through 0 is undefined; this match should pass + if (match.Item1.Value == 0) + { + Console.WriteLine("{0} % 0 == 0", item); + return false; + } + else + { + Console.WriteLine(" {0} % {1} == {2}", item, match.Item1.Value, item % match.Item1.Value); + if (item % match.Item1.Value != 0) + { + return false; + } + } + break; + } + return true; + } + + #endregion + + /// <summary> + /// Converts the given rolling condition to a nicely formatted string representation. + /// </summary> + /// <returns></returns> + public override string ToString() + { + return string.Format("{0} {1} {2} {3} {4}", Dow, Month, Day, Hour, Minute); + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/829b2eb5/src/Appender/Rolling/IRollingCondition.cs ---------------------------------------------------------------------- diff --git a/src/Appender/Rolling/IRollingCondition.cs b/src/Appender/Rolling/IRollingCondition.cs new file mode 100644 index 0000000..e7777b6 --- /dev/null +++ b/src/Appender/Rolling/IRollingCondition.cs @@ -0,0 +1,38 @@ +#region Apache License +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#endregion + +using System; + +namespace log4net.Appender.Rolling +{ + /// <summary> + /// The interface definition of a rolling condition. + /// </summary> + /// <author>Dominik Psenner</author> + public interface IRollingCondition + { + /// <summary> + /// This method should implement all checks needed to determine if a file + /// can be rolled based on the conditions it implies to the file. + /// </summary> + /// <param name="file">the file to be rolled</param> + /// <returns>true when the file has met all conditions to be rolled</returns> + bool IsMet(string file); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/829b2eb5/src/Appender/Rolling/IRollingStrategy.cs ---------------------------------------------------------------------- diff --git a/src/Appender/Rolling/IRollingStrategy.cs b/src/Appender/Rolling/IRollingStrategy.cs new file mode 100644 index 0000000..94ad6d2 --- /dev/null +++ b/src/Appender/Rolling/IRollingStrategy.cs @@ -0,0 +1,36 @@ +#region Apache License +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#endregion + +using System; + +namespace log4net.Appender.Rolling +{ + /// <summary> + /// An interface that can implement a strategy to roll a file. + /// </summary> + /// <author>Dominik Psenner</author> + public interface IRollingStrategy + { + /// <summary> + /// This method should implement all the rolling operations. + /// </summary> + /// <param name="file">the file to be rolled</param> + void Roll(string file); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/829b2eb5/src/Appender/Rolling/IndexRollingStrategy.cs ---------------------------------------------------------------------- diff --git a/src/Appender/Rolling/IndexRollingStrategy.cs b/src/Appender/Rolling/IndexRollingStrategy.cs new file mode 100644 index 0000000..ad58b40 --- /dev/null +++ b/src/Appender/Rolling/IndexRollingStrategy.cs @@ -0,0 +1,77 @@ +#region Apache License +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#endregion + +using System; +using System.IO; + +namespace log4net.Appender.Rolling +{ + /// <summary> + /// This is a simple rolling strategy implementation that rolls a file by + /// appending the index 0. If a file exists that is named exactly as that + /// that file will be renamed to index 1 until reaching the maximum index. + /// The last file will be removed. + /// </summary> + /// <author>Dominik Psenner</author> + public class IndexRollingStrategy : IRollingStrategy + { + #region Implementation of IRollingStrategy + + /// <summary> + /// This method rolls a file with backup indexes between + /// [0..10]. + /// </summary> + /// <param name="file"></param> + public void Roll(string file) + { + DoRoll(file, file, 0, 10); + } + + #endregion + + #region Private Methods + + private void DoRoll(string baseFilename, string currentFilename, int currentIndex, int maxIndex) + { + if (currentIndex > maxIndex) + { + if (File.Exists(currentFilename)) + { + File.Delete(currentFilename); + } + return; + } + if (!File.Exists(currentFilename)) + { + return; + } + + // determine next filename + string nextFilename = string.Format("{0}.{1}", baseFilename, currentIndex); + + // iterate the process until we meet the end + DoRoll(baseFilename, nextFilename, currentIndex + 1, maxIndex); + + // rename this file now that there's free room after us + File.Move(currentFilename, nextFilename); + } + + #endregion + } +} http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/829b2eb5/src/Appender/RollingFileAppenderNG.cs ---------------------------------------------------------------------- diff --git a/src/Appender/RollingFileAppenderNG.cs b/src/Appender/RollingFileAppenderNG.cs new file mode 100644 index 0000000..4b79983 --- /dev/null +++ b/src/Appender/RollingFileAppenderNG.cs @@ -0,0 +1,152 @@ +#region Apache License +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#endregion + +using System; +using System.Collections; +using System.Globalization; +using System.IO; + +using log4net.Util; +using log4net.Core; +using log4net.Appender.Rolling; + +namespace log4net.Appender +{ + /// <summary> + /// Appender that rolls log files based on size or date or both. + /// </summary> + /// <author>Dominik Psenner</author> + public class RollingFileAppenderNG : FileAppender + { + #region Public Instance Constructors + + /// <summary> + /// Initializes a new instance of the <see cref="RollingFileAppenderNG" /> class. + /// </summary> + /// <remarks> + /// <para> + /// Default constructor. + /// </para> + /// </remarks> + public RollingFileAppenderNG() + { + // for now set up the cron rolling condition and the index rolling strategy by default + RollingCondition = new CronRollingCondition("*", "*", "*", "*", "*"); + RollingStrategy = new IndexRollingStrategy(); + } + + #endregion Public Instance Constructors + + #region Public Instance Properties + + /// <summary> + /// Gets the strategy to decide whether or not a file should be rolled over. + /// </summary> + public IRollingCondition RollingCondition + { + get; + private set; + } + + /// <summary> + /// Gets the strategy to roll a file. + /// </summary> + public IRollingStrategy RollingStrategy + { + get; + private set; + } + + #endregion Public Instance Properties + + #region Override implementation of FileAppender + + /// <summary> + /// Sets the quiet writer being used. + /// </summary> + /// <remarks> + /// This method can be overridden by sub classes. + /// </remarks> + /// <param name="writer">the writer to set</param> + override protected void SetQWForFiles(TextWriter writer) + { + QuietWriter = new CountingQuietTextWriter(writer, ErrorHandler); + } + + /// <summary> + /// Write out a logging event. + /// </summary> + /// <param name="loggingEvent">the event to write to file.</param> + /// <remarks> + /// <para> + /// Handles append time behavior for RollingFileAppenderNG. + /// </para> + /// </remarks> + override protected void Append(LoggingEvent loggingEvent) + { + RollFileTrigger(); + base.Append(loggingEvent); + } + + /// <summary> + /// Write out an array of logging events. + /// </summary> + /// <param name="loggingEvents">the events to write to file.</param> + /// <remarks> + /// <para> + /// Handles append time behavior for RollingFileAppenderNG. + /// </para> + /// </remarks> + override protected void Append(LoggingEvent[] loggingEvents) + { + RollFileTrigger(); + base.Append(loggingEvents); + } + + /// <summary> + /// Performs any required rolling before outputting the next event + /// </summary> + /// <remarks> + /// <para> + /// Handles append time behavior for RollingFileAppenderNG. It checks first + /// whether the conditions to roll the file are met and if so asks the roll + /// file strategy to do the roll operation. + /// </para> + /// </remarks> + protected void RollFileTrigger() + { + if (RollingCondition == null) + { + // TODO throw exception + } + if (RollingStrategy == null) + { + // TODO throw exception + } + + // check if the rolling conditions are met + if (RollingCondition.IsMet(File)) + { + // let the strategy do all the required rolling + RollingStrategy.Roll(File); + } + } + #endregion + } +} http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/829b2eb5/tests/src/Appender/Rolling/CronRollingConditionTest.cs ---------------------------------------------------------------------- diff --git a/tests/src/Appender/Rolling/CronRollingConditionTest.cs b/tests/src/Appender/Rolling/CronRollingConditionTest.cs new file mode 100644 index 0000000..8a7f28a --- /dev/null +++ b/tests/src/Appender/Rolling/CronRollingConditionTest.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using log4net.Appender.Rolling; + +namespace log4net.Tests.Appender.Rolling +{ + [TestFixture] + public class CronRollingConditionTest + { + [Test] + public void IsMetTest() + { + Tuple<CronRollingCondition, Tuple<DateTime, bool>[]>[] tests = new Tuple<CronRollingCondition, Tuple<DateTime, bool>[]>[]{ + Tuple.Create(new CronRollingCondition("*", "*", "*", "*", "5"), new Tuple<DateTime, bool>[]{ + Tuple.Create(new DateTime(2009, 10, 10, 12, 5, 0), true), + Tuple.Create(new DateTime(2009, 10, 10, 12, 5, 1), false), + Tuple.Create(new DateTime(2009, 10, 10, 12, 6, 0), false), + Tuple.Create(new DateTime(2009, 10, 10, 12, 10, 0), false), + Tuple.Create(new DateTime(2009, 10, 10, 13, 5, 1), true), + Tuple.Create(new DateTime(2009, 10, 10, 14, 5, 1), true), + }), + Tuple.Create(new CronRollingCondition("*", "*", "*", "*", "*/3"), new Tuple<DateTime, bool>[]{ + Tuple.Create(new DateTime(2009, 10, 10, 12, 0, 0), true), + Tuple.Create(new DateTime(2009, 10, 10, 12, 0, 1), false), + Tuple.Create(new DateTime(2009, 10, 10, 12, 0, 59), false), + Tuple.Create(new DateTime(2009, 10, 10, 12, 3, 0), true), + Tuple.Create(new DateTime(2009, 10, 10, 12, 3, 59), false), + Tuple.Create(new DateTime(2009, 10, 10, 12, 4, 1), false), + Tuple.Create(new DateTime(2009, 10, 10, 12, 6, 33), true), + Tuple.Create(new DateTime(2009, 10, 10, 12, 10, 0), false), + }), + }; + foreach (Tuple<CronRollingCondition, Tuple<DateTime, bool>[]> test in tests) + { + foreach (Tuple<DateTime, bool> testCheck in test.Item2) + { + Assert.AreEqual(test.Item1.IsMet(testCheck.Item1), testCheck.Item2, string.Format("failed for {0} with condition {1}", testCheck.Item1, test.Item1)); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/829b2eb5/tests/src/Appender/Rolling/IndexRollingStrategyTest.cs ---------------------------------------------------------------------- diff --git a/tests/src/Appender/Rolling/IndexRollingStrategyTest.cs b/tests/src/Appender/Rolling/IndexRollingStrategyTest.cs new file mode 100644 index 0000000..b66f7fc --- /dev/null +++ b/tests/src/Appender/Rolling/IndexRollingStrategyTest.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using log4net.Appender.Rolling; +using System.IO; + +namespace log4net.Tests.Appender.Rolling +{ + [TestFixture] + public class IndexRollingStrategyTest + { + [Test] + public void RollTest() + { + IndexRollingStrategy strategy = new IndexRollingStrategy(); + List<string> tmpFiles = new List<string>(); + try + { + // create a dummy temp file + string filename = "logfile.log"; + // add the file + tmpFiles.Add(filename); + for (int i = 0; i <= 10; i++) + { + Console.WriteLine("Now at rolling iteration {0}", i); + // roll the file once + strategy.Roll(filename); + // create filename again + using (File.Create(filename)) + { + // we don't need the filestream anymore + } + tmpFiles.Add(filename + "." + i); + // test if there are rolled files for [0..i] + for (int j = 0; j < i; j++) + { + if (!File.Exists(filename + "." + j)) + { + // fail + Assert.Fail("The file '{0}' was not rolled correctly at iteration i={1}; j={2}", filename, i, j); + } + } + } + + Console.WriteLine("Now at rolling iteration {0}, this should not create another file", 11); + // roll the file once + strategy.Roll(filename); + // test if there are rolled files for [0..i] + for (int j = 0; j <= 10; j++) + { + if (!File.Exists(filename + "." + j)) + { + // fail + Assert.Fail("The file '{0}' was not rolled correctly at iteration j={2}", filename, j); + } + } + if (File.Exists(filename + ".11")) + { + Assert.Fail("The file '{0}' should not have been rolled further iteration 10", filename); + } + + } + catch (Exception e) + { + Assert.Fail("Exception: {0}", e); + } + finally + { + // cleanup + while (tmpFiles.Count > 0) + { + if (File.Exists(tmpFiles[0])) + { + File.Delete(tmpFiles[0]); + } + tmpFiles.RemoveAt(0); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/829b2eb5/tests/src/log4net.Tests.vs2012.csproj ---------------------------------------------------------------------- diff --git a/tests/src/log4net.Tests.vs2012.csproj b/tests/src/log4net.Tests.vs2012.csproj index 7629c5a..c0d8aaa 100644 --- a/tests/src/log4net.Tests.vs2012.csproj +++ b/tests/src/log4net.Tests.vs2012.csproj @@ -159,6 +159,8 @@ <Compile Include="Appender\RemotingAppenderTest.cs"> <SubType>Code</SubType> </Compile> + <Compile Include="Appender\Rolling\CronRollingConditionTest.cs" /> + <Compile Include="Appender\Rolling\IndexRollingStrategyTest.cs" /> <Compile Include="Appender\SmtpPickupDirAppenderTest.cs"> <SubType>Code</SubType> </Compile>