Browse Source

Introduced the following new elements. (#247)

* Introduced the following new elements.
1. logname - you can override the name of the log file rather than using the EXE name, this means you don't have to call your EXE a different name, just name the winsw exe different. Default's the name to the EXE as before.
2. outfiledisabled - you can disable writing to the out file. Defaults to false.
3. errfiledisabled - you can disable writing to the error file. Defaults to false.
4. outfilepattern - you can choose the pattern of the out file. Defaults to .out.log.
5. errfilepattern - you can choos the pattern of the error file. Defaults to .err.log.

* Downgraded from C#7.0 syntax.

* Applied reviewers comment

* not required

* removed the key

* Added unit test for new fields logname, outfiledisabled, errfiledisabled and errfilepattern.

Created a new appender called roll-by-size-time see class RollingSizeTimeLogAppender, this appender supports rolling by time and size and rolling at a specific time each day.

Added unit test for the new appender.

Added a new option testwait which is similar to test but waits for the user to press any key before calling the stop method.

* Update loggingAndErrorReporting.md

* Cannot use $ string.format syntax, downgraded code to string.format.

* Another syntax found of $

* Fixed a unit tests
Dinz 8 years ago
parent
commit
221d30f271

+ 2 - 0
.gitignore

@@ -6,3 +6,5 @@ obj
 /src/packages/
 /winsw_key.snk
 /winsw_key.pfx
+/src/.vs/winsw/v15/sqlite3/storage.ide
+/src/Core/WinSWCore/WinSWCore.csproj.DotSettings

+ 15 - 0
doc/loggingAndErrorReporting.md

@@ -48,7 +48,22 @@ This configuration must accompany a nested `<pattern>` element, which specifies
 The syntax of the pattern string is specified by [DateTime.ToString()](http://msdn.microsoft.com/en-us/library/zdtaw1bw.aspx). 
 For example, in the above example, the log of Jan 1, 2013 gets written to `myapp.20130101.out.log` and `myapp.20130101.err.log`. 
 
+### Rotate by size and time mode
+Works in a combination of rotate size mode and rotate time mode, if the log file gets bigger than a set size, it gets rotated using `<pattern>` provided.
 
+```
+    <log mode="roll-by-size-time">
+      <sizeThreshold>10240</sizeThreshold>
+      <pattern>yyyyMMdd</pattern>
+      <autoRollAtTime>00:00:00</autoRollAtTime>
+    </log>
+```
+
+The syntax of the pattern string is specified by [DateTime.ToString()](http://msdn.microsoft.com/en-us/library/zdtaw1bw.aspx). 
+For example, in the above example, the log of Jan 1, 2013 gets written to `myapp.20130101.out.log` and `myapp.20130101.err.log`. 
+
+The syntax of the autoRollAtTime is specified by [TimeSpan.ToString()](https://msdn.microsoft.com/en-us/library/1ecy8h51(v=vs.110).aspx).
+For example, in the above example, at the start of the day it will roll the file over.
 
 ### Error reporting
 

+ 11 - 1
src/Core/ServiceWrapper/Main.cs

@@ -737,6 +737,15 @@ namespace winsw
                     wsvc.OnStop();
                     return;
                 }
+                if (args[0] == "testwait")
+                {
+                    WrapperService wsvc = new WrapperService();
+                    wsvc.OnStart(args.ToArray());
+                    Console.WriteLine("Press any key to stop the service...");
+                    Console.Read();
+                    wsvc.OnStop();
+                    return;
+                }
                 if (args[0] == "help" || args[0] == "--help" || args[0] == "-h" 
                     || args[0] == "-?" || args[0] == "/?")
                 {
@@ -869,7 +878,8 @@ namespace winsw
             Console.WriteLine("- 'restart'   - restart the service");
             Console.WriteLine("- 'restart!'  - self-restart (can be called from child processes)");
             Console.WriteLine("- 'status'    - check the current status of the service");
-            Console.WriteLine("- 'test'      - check if the service can be started and then stopped");  
+            Console.WriteLine("- 'test'      - check if the service can be started and then stopped");
+            Console.WriteLine("- 'testwait'  - starts the service and waits until a key is pressed then stops the service");
             Console.WriteLine("- 'version'   - print the version info");
             Console.WriteLine("- 'help'      - print the help info (aliases: -h,--help,-?,/?)");
         }

+ 0 - 1
src/Core/ServiceWrapper_dotNET4/winsw_dotNET4.csproj

@@ -79,7 +79,6 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>
-    <Content Include="manifest.xml" />
     <Content Include="winsw.xml">
       <SubType>Designer</SubType>
     </Content>

+ 5 - 0
src/Core/WinSWCore/Configuration/DefaultSettings.cs

@@ -59,6 +59,11 @@ namespace winsw.Configuration
         public string LogDirectory { get { return Path.GetDirectoryName(ExecutablePath); } }
         public string LogMode { get { return "append"; } }
 
+        public bool OutFileDisabled { get { return false; } }
+        public bool ErrFileDisabled { get { return false; } }
+        public string OutFilePattern { get { return ".out.log"; } }
+        public string ErrFilePattern { get { return ".err.log"; } }
+
         // Environment
         public List<Download> Downloads { get { return new List<Download>(); } }
         public Dictionary<string, string> EnvironmentVariables { get { return new Dictionary<string, string>(); } }

+ 206 - 23
src/Core/WinSWCore/LogAppenders.cs

@@ -1,6 +1,8 @@
+using System;
 using System.Diagnostics;
 using System.IO;
 using System.Threading;
+using System.Timers;
 
 namespace winsw
 {
@@ -64,10 +66,18 @@ namespace winsw
     public abstract class AbstractFileLogAppender : LogHandler
     {
         protected string BaseLogFileName { private set; get; }
+        protected bool OutFileDisabled { private set; get; }
+        protected bool ErrFileDisabled { private set; get; }
+        protected string OutFilePattern { private set; get; }
+        protected string ErrFilePattern { private set; get; }
 
-        public AbstractFileLogAppender(string logDirectory, string baseName)
+        protected AbstractFileLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
         {
             BaseLogFileName = Path.Combine(logDirectory, baseName);
+            OutFileDisabled = outFileDisabled;
+            OutFilePattern = outFilePattern;
+            ErrFileDisabled = errFileDisabled;
+            ErrFilePattern = errFilePattern;
         }
     }
 
@@ -77,8 +87,8 @@ namespace winsw
         public string OutputLogFileName { private set; get; }
         public string ErrorLogFileName { private set; get; }
 
-        public SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode)
-            : base(logDirectory, baseName)
+        protected SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
+            : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
         {
             FileMode = fileMode;
             OutputLogFileName = BaseLogFileName + ".out.log";
@@ -87,23 +97,23 @@ namespace winsw
 
         public override void log(Stream outputStream, Stream errorStream)
         {
-            new Thread(delegate() { CopyStream(outputStream, new FileStream(OutputLogFileName, FileMode)); }).Start();
-            new Thread(delegate() { CopyStream(errorStream, new FileStream(ErrorLogFileName, FileMode)); }).Start();
+            if (!OutFileDisabled) new Thread(delegate() { CopyStream(outputStream, new FileStream(OutputLogFileName, FileMode)); }).Start();
+            if (!ErrFileDisabled) new Thread(delegate() { CopyStream(errorStream, new FileStream(ErrorLogFileName, FileMode)); }).Start();
         }
     }
 
     public class DefaultLogAppender : SimpleLogAppender
     {
-        public DefaultLogAppender(string logDirectory, string baseName)
-            : base(logDirectory, baseName, FileMode.Append)
+        public DefaultLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
+            : base(logDirectory, baseName, FileMode.Append, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
         {
         }
     }
 
     public class ResetLogAppender : SimpleLogAppender
     {
-        public ResetLogAppender(string logDirectory, string baseName)
-            : base(logDirectory, baseName, FileMode.Create)
+        public ResetLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
+            : base(logDirectory, baseName, FileMode.Create, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
         {
         }
     }
@@ -125,8 +135,8 @@ namespace winsw
         public string Pattern { get; private set; }
         public int Period { get; private set; }
 
-        public TimeBasedRollingLogAppender(string logDirectory, string baseName, string pattern, int period)
-            : base(logDirectory, baseName)
+        public TimeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, string pattern, int period)
+            : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
         {
             Pattern = pattern;
             Period = period;
@@ -134,8 +144,8 @@ namespace winsw
 
         public override void log(Stream outputStream, Stream errorStream)
         {
-            new Thread(delegate() { CopyStreamWithDateRotation(outputStream, ".out.log"); }).Start();
-            new Thread(delegate() { CopyStreamWithDateRotation(errorStream, ".err.log"); }).Start();
+            if (!OutFileDisabled) new Thread(delegate() { CopyStreamWithDateRotation(outputStream, OutFilePattern); }).Start();
+            if (!ErrFileDisabled) new Thread(delegate() { CopyStreamWithDateRotation(errorStream, ErrFilePattern); }).Start();
         }
 
         /// <summary>
@@ -213,20 +223,20 @@ namespace winsw
 
         public int FilesToKeep { private set; get; }
 
-        public SizeBasedRollingLogAppender(string logDirectory, string baseName, int sizeThreshold, int filesToKeep)
-            : base(logDirectory, baseName)
+        public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, int sizeThreshold, int filesToKeep)
+            : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
         {
             SizeTheshold = sizeThreshold;
             FilesToKeep = filesToKeep;
         }
 
-        public SizeBasedRollingLogAppender(string logDirectory, string baseName)
-            : this(logDirectory, baseName, DEFAULT_SIZE_THRESHOLD, DEFAULT_FILES_TO_KEEP) { }
+        public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
+            : this(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern, DEFAULT_SIZE_THRESHOLD, DEFAULT_FILES_TO_KEEP) { }
 
         public override void log(Stream outputStream, Stream errorStream)
         {
-            new Thread(delegate() { CopyStreamWithRotation(outputStream, ".out.log"); }).Start();
-            new Thread(delegate() { CopyStreamWithRotation(errorStream, ".err.log"); }).Start();
+            if (!OutFileDisabled) new Thread(delegate() { CopyStreamWithRotation(outputStream, OutFilePattern); }).Start();
+            if (!ErrFileDisabled) new Thread(delegate() { CopyStreamWithRotation(errorStream, ErrFilePattern); }).Start();
         }
 
         /// <summary>
@@ -299,16 +309,189 @@ namespace winsw
     /// </summary>
     public class RollingLogAppender : SimpleLogAppender
     {
-        public RollingLogAppender(string logDirectory, string baseName)
-            : base(logDirectory, baseName, FileMode.Append)
+        public RollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
+            : base(logDirectory, baseName, FileMode.Append, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
         {
         }
 
         public override void log(Stream outputStream, Stream errorStream)
         {
-            CopyFile(OutputLogFileName, OutputLogFileName + ".old");
-            CopyFile(ErrorLogFileName, ErrorLogFileName + ".old");
+            if (!OutFileDisabled) CopyFile(OutputLogFileName, OutputLogFileName + ".old");
+            if (!ErrFileDisabled) CopyFile(ErrorLogFileName, ErrorLogFileName + ".old");
             base.log(outputStream, errorStream);
         }
     }
+
+    public class RollingSizeTimeLogAppender : AbstractFileLogAppender
+    {
+        public static int BYTES_PER_KB = 1024;
+        public int SizeTheshold { private set; get; }
+        public string FilePattern { private set; get; }
+        public TimeSpan? AutoRollAtTime { private set; get; }
+
+        public RollingSizeTimeLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, int sizeThreshold, string filePattern, TimeSpan? autoRollAtTime)
+            : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
+        {
+            SizeTheshold = sizeThreshold;
+            FilePattern = filePattern;
+            AutoRollAtTime = autoRollAtTime;
+        }
+
+        public override void log(Stream outputStream, Stream errorStream)
+        {
+            if (!OutFileDisabled) new Thread(delegate () { CopyStreamWithRotation(outputStream, OutFilePattern); }).Start();
+            if (!ErrFileDisabled) new Thread(delegate () { CopyStreamWithRotation(errorStream, ErrFilePattern); }).Start();
+        }
+
+        private void CopyStreamWithRotation(Stream data, string ext)
+        {
+            // lock required as the timer thread and the thread that will write to the stream could try and access the file stream at the same time
+            var fileLock = new object();
+
+            var buf = new byte[1024];
+
+            var baseDirectory = Path.GetDirectoryName(BaseLogFileName);
+            var baseFileName = Path.GetFileName(BaseLogFileName);
+            var logFile = string.Format("{0}{1}", BaseLogFileName, ext);
+
+            var w = new FileStream(logFile, FileMode.Append);
+            var sz = new FileInfo(logFile).Length;
+
+            // We auto roll at time is configured then we need to create a timer and wait until time is elasped and roll the file over
+            if (AutoRollAtTime != null)
+            {
+                var tickTime = SetupRollTimer();
+                var timer = new System.Timers.Timer(tickTime);
+                timer.Elapsed += (s, e) =>
+                {
+                    try
+                    {
+                        timer.Stop();
+                        lock (fileLock)
+                        {
+                            w.Close();
+
+                            var nextFileNumber = GetNextFileNumber(ext, baseDirectory, baseFileName);
+                            var nextFileName =  Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, DateTime.UtcNow.ToString(FilePattern), nextFileNumber, ext));
+                            File.Move(logFile, nextFileName);
+
+                            w = new FileStream(logFile, FileMode.Create);
+                            sz = new FileInfo(logFile).Length;
+                        }
+                    }
+                    catch (Exception et)
+                    {
+                        EventLogger.LogEvent(string.Format("Failed to to trigger auto roll at time event due to: {0}", et.Message));
+                    }
+                    finally
+                    {
+                        // Recalculate the next interval
+                        timer.Interval = SetupRollTimer();
+                        timer.Start();
+                    }
+                };
+                timer.Start();
+            }
+            while (true)
+            {
+                var len = data.Read(buf, 0, buf.Length);
+                if (len == 0) break;    // EOF
+                lock (fileLock)
+                {
+                    if (sz + len < SizeTheshold)
+                    {
+                        // typical case. write the whole thing into the current file
+                        w.Write(buf, 0, len);
+                        sz += len;
+                    }
+                    else
+                    {
+                        try
+                        {
+                            // rotate at the line boundary
+                            int s = 0;
+                            for (int i = 0; i < len; i++)
+                            {
+                                if (buf[i] != 0x0A) continue;
+                                if (sz + i < SizeTheshold) continue;
+
+                                // at the line boundary and exceeded the rotation unit.
+                                // time to rotate.
+                                w.Write(buf, s, i + 1);
+                                w.Close();
+                                s = i + 1;
+
+                                // rotate file
+                                var nextFileNumber = GetNextFileNumber(ext, baseDirectory, baseFileName);
+                                var nextFileName =
+                                    Path.Combine(baseDirectory,
+                                        string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, DateTime.UtcNow.ToString(FilePattern), nextFileNumber, ext));
+                                File.Move(logFile, nextFileName);
+
+                                // even if the log rotation fails, create a new one, or else
+                                // we'll infinitely try to rotate.
+                                w = new FileStream(logFile, FileMode.Create);
+                                sz = new FileInfo(logFile).Length;
+                            }
+                        }
+                        catch (Exception e)
+                        {
+                            EventLogger.LogEvent(string.Format("Failed to roll size time log: {0}", e.Message));
+                        }
+                    }
+                    w.Flush();
+                }
+            }
+            data.Close();
+            w.Close();
+        }
+
+        private double SetupRollTimer()
+        {
+            var nowTime = DateTime.Now.ToUniversalTime();
+            var scheduledTime = new DateTime(nowTime.Year, nowTime.Month, nowTime.Day, AutoRollAtTime.Value.Hours,
+                AutoRollAtTime.Value.Minutes, AutoRollAtTime.Value.Seconds, 0).ToUniversalTime(); //Specify your time HH,MM,SS
+            if (nowTime > scheduledTime)
+                scheduledTime = scheduledTime.AddDays(1);
+
+            double tickTime = (double) (scheduledTime - DateTime.Now.ToUniversalTime()).TotalMilliseconds;
+            return tickTime;
+        }
+
+        private int GetNextFileNumber(string ext, string baseDirectory, string baseFileName)
+        {
+            var nextFileNumber = 0;
+            var files = Directory.GetFiles(baseDirectory, String.Format("{0}.{1}.#*{2}", baseFileName, DateTime.UtcNow.ToString(FilePattern), ext));
+            if (files.Length == 0)
+            {
+                nextFileNumber = 1;
+            }
+            else
+            {
+                foreach (var f in files)
+                {
+                    try
+                    {
+                        var filenameOnly = Path.GetFileNameWithoutExtension(f);
+                        var lastNumberAsString = filenameOnly.Substring(filenameOnly.Length - 4, 4);
+                        int lastNumber = 0;
+                        if (int.TryParse(lastNumberAsString, out lastNumber))
+                        {
+                            if (lastNumber > nextFileNumber)
+                                nextFileNumber = lastNumber;
+                        }
+                        else
+                            throw new IOException(string.Format("File {0} does not follow the pattern provided",f));
+                    }
+                    catch (Exception e)
+                    {
+                        throw new IOException(string.Format("Failed to process file {0} due to error {1}",f, e.Message), e);
+                    }
+                }
+                if (nextFileNumber == 0) throw new IOException("Cannot roll the file because matching pattern not found");
+                nextFileNumber++;
+            }
+            return nextFileNumber;
+        }
+    }
 }

+ 80 - 9
src/Core/WinSWCore/ServiceDescriptor.cs

@@ -103,13 +103,20 @@ namespace winsw
             return SingleElement(tagName, false);
         }
 
-        private string SingleElement(string tagName, Boolean optional)
+        private string SingleElement(string tagName, bool optional)
         {
             var n = dom.SelectSingleNode("//" + tagName);
             if (n == null && !optional) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
             return n == null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
         }
 
+        private bool SingleBoolElement(string tagName, bool defaultValue)
+        {
+            var e = dom.SelectSingleNode("//" + tagName);
+
+            return e == null ? defaultValue : bool.Parse(e.InnerText);
+        }
+
         private int SingleIntElement(XmlNode parent, string tagName, int defaultValue)
         {
             var e = parent.SelectSingleNode(tagName);
@@ -355,6 +362,49 @@ namespace winsw
             }
         }
 
+        public string LogName
+        {
+            get
+            {
+                XmlNode loggingName = dom.SelectSingleNode("//logname");
+
+                return loggingName != null ? Environment.ExpandEnvironmentVariables(loggingName.InnerText) : BaseName;
+            }
+        }
+
+        public bool OutFileDisabled
+        {
+            get { return SingleBoolElement("outfiledisabled", Defaults.OutFileDisabled); }
+        }
+
+        public bool ErrFileDisabled
+        {
+            get
+            {
+                return SingleBoolElement("errfiledisabled", Defaults.ErrFileDisabled);
+            }
+        }
+
+        public string OutFilePattern
+        {
+            get
+            {
+                XmlNode loggingName = dom.SelectSingleNode("//outfilepattern");
+
+                return loggingName != null ? Environment.ExpandEnvironmentVariables(loggingName.InnerText) : Defaults.OutFilePattern;
+            }
+        }
+
+        public string ErrFilePattern
+        {
+            get
+            {
+                XmlNode loggingName = dom.SelectSingleNode("//errfilepattern");
+
+                return loggingName != null ? Environment.ExpandEnvironmentVariables(loggingName.InnerText) : Defaults.ErrFilePattern;
+            }
+        }
+
         public LogHandler LogHandler
         {
             
@@ -366,19 +416,20 @@ namespace winsw
                     // this is more modern way, to support nested elements as configuration
                     e = (XmlElement)dom.SelectSingleNode("//log");
                 }
+                int sizeThreshold;
                 switch (LogMode)
                 {
                     case "rotate":
-                        return new SizeBasedRollingLogAppender(LogDirectory, BaseName);
+                        return new SizeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
 
                     case "none":
                         return new IgnoreLogAppender();
 
                     case "reset":
-                        return new ResetLogAppender(LogDirectory, BaseName);
+                        return new ResetLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
 
                     case "roll":
-                        return new RollingLogAppender(LogDirectory, BaseName);
+                        return new RollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
 
                     case "roll-by-time":
                         XmlNode patternNode = e.SelectSingleNode("pattern");
@@ -386,17 +437,37 @@ namespace winsw
                         {
                             throw new InvalidDataException("Time Based rolling policy is specified but no pattern can be found in configuration XML.");
                         }
-                        string pattern = patternNode.InnerText;
+                        var pattern = patternNode.InnerText;
                         int period = SingleIntElement(e,"period",1);
-                        return new TimeBasedRollingLogAppender(LogDirectory, BaseName, pattern, period);
+                        return new TimeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, pattern, period);
 
                     case "roll-by-size":
-                        int sizeThreshold = SingleIntElement(e,"sizeThreshold",10*1024)  * SizeBasedRollingLogAppender.BYTES_PER_KB;
+                        sizeThreshold = SingleIntElement(e,"sizeThreshold",10*1024)  * SizeBasedRollingLogAppender.BYTES_PER_KB;
                         int keepFiles = SingleIntElement(e,"keepFiles",SizeBasedRollingLogAppender.DEFAULT_FILES_TO_KEEP);
-                        return new SizeBasedRollingLogAppender(LogDirectory, BaseName, sizeThreshold, keepFiles);
+                        return new SizeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, sizeThreshold, keepFiles);
 
                     case "append":
-                        return new DefaultLogAppender(LogDirectory, BaseName);
+                        return new DefaultLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
+
+                    case "roll-by-size-time":
+                        sizeThreshold = SingleIntElement(e, "sizeThreshold", 10 * 1024) * RollingSizeTimeLogAppender.BYTES_PER_KB;
+                        XmlNode filePatternNode = e.SelectSingleNode("pattern");
+                        if (filePatternNode == null)
+                        {
+                            throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but no pattern can be found in configuration XML.");
+                        }
+                        XmlNode autoRollAtTimeNode = e.SelectSingleNode("autoRollAtTime");
+                        TimeSpan? autoRollAtTime = null;
+                        if (autoRollAtTimeNode != null)
+                        {
+                            TimeSpan autoRollAtTimeValue;
+                            // validate it
+                            if (!TimeSpan.TryParse(autoRollAtTimeNode.InnerText, out autoRollAtTimeValue))
+                                throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but autoRollAtTime does not match the TimeSpan format HH:mm:ss found in configuration XML.");
+                            autoRollAtTime = autoRollAtTimeValue;
+                        }
+
+                        return new RollingSizeTimeLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, sizeThreshold, filePatternNode.InnerText, autoRollAtTime);
 
                     default:
                         throw new InvalidDataException("Undefined logging mode: " + LogMode);

+ 79 - 2
src/Test/winswTests/ServiceDescriptorTests.cs

@@ -182,8 +182,63 @@ namespace winswTests
             var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
 
             Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromMinutes(10)));
-        }   
-        
+        }
+
+        [Test]
+        public void CanParseLogname()
+        {
+            const string seedXml = "<service>"
+                                   + "<logname>MyTestApp</logname>"
+                                   + "</service>";
+            var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
+
+            Assert.That(serviceDescriptor.LogName, Is.EqualTo("MyTestApp"));
+        }
+
+        [Test]
+        public void CanParseOutfileDisabled()
+        {
+            const string seedXml = "<service>"
+                                   + "<outfiledisabled>true</outfiledisabled>"
+                                   + "</service>";
+            var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
+
+            Assert.That(serviceDescriptor.OutFileDisabled, Is.EqualTo(true));
+        }
+
+        [Test]
+        public void CanParseErrfileDisabled()
+        {
+            const string seedXml = "<service>"
+                                   + "<errfiledisabled>true</errfiledisabled>"
+                                   + "</service>";
+            var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
+
+            Assert.That(serviceDescriptor.ErrFileDisabled, Is.EqualTo(true));
+        }
+
+        [Test]
+        public void CanParseOutfilePattern()
+        {
+            const string seedXml = "<service>"
+                                   + "<outfilepattern>.out.test.log</outfilepattern>"
+                                   + "</service>";
+            var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
+
+            Assert.That(serviceDescriptor.OutFilePattern, Is.EqualTo(".out.test.log"));
+        }
+
+        [Test]
+        public void CanParseErrfilePattern()
+        {
+            const string seedXml = "<service>"
+                                   + "<errfilepattern>.err.test.log</errfilepattern>"
+                                   + "</service>";
+            var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
+
+            Assert.That(serviceDescriptor.ErrFilePattern, Is.EqualTo(".err.test.log"));
+        }
+
         [Test]
         public void LogModeRollBySize()
         {
@@ -224,6 +279,28 @@ namespace winswTests
             Assert.That(logHandler.Pattern, Is.EqualTo("log pattern"));
         }
 
+        [Test]
+        public void LogModeRollBySizeTime()
+        {
+            const string seedXml = "<service>"
+                                   + "<logpath>c:\\</logpath>"
+                                   + "<log mode=\"roll-by-size-time\">"
+                                   + "<sizeThreshold>10240</sizeThreshold>"
+                                   + "<pattern>yyyy-MM-dd</pattern>"
+                                   + "<autoRollAtTime>00:00:00</autoRollAtTime>"
+                                   + "</log>"
+                                   + "</service>";
+
+            var serviceDescriptor = ServiceDescriptor.FromXML(seedXml);
+            serviceDescriptor.BaseName = "service";
+
+            var logHandler = serviceDescriptor.LogHandler as RollingSizeTimeLogAppender;
+            Assert.NotNull(logHandler);
+            Assert.That(logHandler.SizeTheshold, Is.EqualTo(10240 * 1024));
+            Assert.That(logHandler.FilePattern, Is.EqualTo("yyyy-MM-dd"));
+            Assert.That(logHandler.AutoRollAtTime, Is.EqualTo((TimeSpan?)new TimeSpan(0,0,0)));
+        }
+
         [Test]
         public void VerifyServiceLogonRightGraceful()
         {