mirror of https://github.com/winsw/winsw
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 testspull/252/head
parent
fad883085d
commit
221d30f271
|
@ -6,3 +6,5 @@ obj
|
||||||
/src/packages/
|
/src/packages/
|
||||||
/winsw_key.snk
|
/winsw_key.snk
|
||||||
/winsw_key.pfx
|
/winsw_key.pfx
|
||||||
|
/src/.vs/winsw/v15/sqlite3/storage.ide
|
||||||
|
/src/Core/WinSWCore/WinSWCore.csproj.DotSettings
|
||||||
|
|
|
@ -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).
|
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`.
|
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
|
### Error reporting
|
||||||
|
|
||||||
|
|
|
@ -737,6 +737,15 @@ namespace winsw
|
||||||
wsvc.OnStop();
|
wsvc.OnStop();
|
||||||
return;
|
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"
|
if (args[0] == "help" || args[0] == "--help" || args[0] == "-h"
|
||||||
|| args[0] == "-?" || args[0] == "/?")
|
|| args[0] == "-?" || args[0] == "/?")
|
||||||
{
|
{
|
||||||
|
@ -870,6 +879,7 @@ namespace winsw
|
||||||
Console.WriteLine("- 'restart!' - self-restart (can be called from child processes)");
|
Console.WriteLine("- 'restart!' - self-restart (can be called from child processes)");
|
||||||
Console.WriteLine("- 'status' - check the current status of the service");
|
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("- 'version' - print the version info");
|
||||||
Console.WriteLine("- 'help' - print the help info (aliases: -h,--help,-?,/?)");
|
Console.WriteLine("- 'help' - print the help info (aliases: -h,--help,-?,/?)");
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,6 @@
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="manifest.xml" />
|
|
||||||
<Content Include="winsw.xml">
|
<Content Include="winsw.xml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
</Content>
|
</Content>
|
||||||
|
|
|
@ -59,6 +59,11 @@ namespace winsw.Configuration
|
||||||
public string LogDirectory { get { return Path.GetDirectoryName(ExecutablePath); } }
|
public string LogDirectory { get { return Path.GetDirectoryName(ExecutablePath); } }
|
||||||
public string LogMode { get { return "append"; } }
|
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
|
// Environment
|
||||||
public List<Download> Downloads { get { return new List<Download>(); } }
|
public List<Download> Downloads { get { return new List<Download>(); } }
|
||||||
public Dictionary<string, string> EnvironmentVariables { get { return new Dictionary<string, string>(); } }
|
public Dictionary<string, string> EnvironmentVariables { get { return new Dictionary<string, string>(); } }
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Timers;
|
||||||
|
|
||||||
namespace winsw
|
namespace winsw
|
||||||
{
|
{
|
||||||
|
@ -64,10 +66,18 @@ namespace winsw
|
||||||
public abstract class AbstractFileLogAppender : LogHandler
|
public abstract class AbstractFileLogAppender : LogHandler
|
||||||
{
|
{
|
||||||
protected string BaseLogFileName { private set; get; }
|
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);
|
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 OutputLogFileName { private set; get; }
|
||||||
public string ErrorLogFileName { private set; get; }
|
public string ErrorLogFileName { private set; get; }
|
||||||
|
|
||||||
public SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode)
|
protected SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
|
||||||
: base(logDirectory, baseName)
|
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
|
||||||
{
|
{
|
||||||
FileMode = fileMode;
|
FileMode = fileMode;
|
||||||
OutputLogFileName = BaseLogFileName + ".out.log";
|
OutputLogFileName = BaseLogFileName + ".out.log";
|
||||||
|
@ -87,23 +97,23 @@ namespace winsw
|
||||||
|
|
||||||
public override void log(Stream outputStream, Stream errorStream)
|
public override void log(Stream outputStream, Stream errorStream)
|
||||||
{
|
{
|
||||||
new Thread(delegate() { CopyStream(outputStream, new FileStream(OutputLogFileName, FileMode)); }).Start();
|
if (!OutFileDisabled) new Thread(delegate() { CopyStream(outputStream, new FileStream(OutputLogFileName, FileMode)); }).Start();
|
||||||
new Thread(delegate() { CopyStream(errorStream, new FileStream(ErrorLogFileName, FileMode)); }).Start();
|
if (!ErrFileDisabled) new Thread(delegate() { CopyStream(errorStream, new FileStream(ErrorLogFileName, FileMode)); }).Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DefaultLogAppender : SimpleLogAppender
|
public class DefaultLogAppender : SimpleLogAppender
|
||||||
{
|
{
|
||||||
public DefaultLogAppender(string logDirectory, string baseName)
|
public DefaultLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
|
||||||
: base(logDirectory, baseName, FileMode.Append)
|
: base(logDirectory, baseName, FileMode.Append, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ResetLogAppender : SimpleLogAppender
|
public class ResetLogAppender : SimpleLogAppender
|
||||||
{
|
{
|
||||||
public ResetLogAppender(string logDirectory, string baseName)
|
public ResetLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
|
||||||
: base(logDirectory, baseName, FileMode.Create)
|
: base(logDirectory, baseName, FileMode.Create, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,8 +135,8 @@ namespace winsw
|
||||||
public string Pattern { get; private set; }
|
public string Pattern { get; private set; }
|
||||||
public int Period { get; private set; }
|
public int Period { get; private set; }
|
||||||
|
|
||||||
public TimeBasedRollingLogAppender(string logDirectory, string baseName, string pattern, int period)
|
public TimeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, string pattern, int period)
|
||||||
: base(logDirectory, baseName)
|
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
|
||||||
{
|
{
|
||||||
Pattern = pattern;
|
Pattern = pattern;
|
||||||
Period = period;
|
Period = period;
|
||||||
|
@ -134,8 +144,8 @@ namespace winsw
|
||||||
|
|
||||||
public override void log(Stream outputStream, Stream errorStream)
|
public override void log(Stream outputStream, Stream errorStream)
|
||||||
{
|
{
|
||||||
new Thread(delegate() { CopyStreamWithDateRotation(outputStream, ".out.log"); }).Start();
|
if (!OutFileDisabled) new Thread(delegate() { CopyStreamWithDateRotation(outputStream, OutFilePattern); }).Start();
|
||||||
new Thread(delegate() { CopyStreamWithDateRotation(errorStream, ".err.log"); }).Start();
|
if (!ErrFileDisabled) new Thread(delegate() { CopyStreamWithDateRotation(errorStream, ErrFilePattern); }).Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -213,20 +223,20 @@ namespace winsw
|
||||||
|
|
||||||
public int FilesToKeep { private set; get; }
|
public int FilesToKeep { private set; get; }
|
||||||
|
|
||||||
public SizeBasedRollingLogAppender(string logDirectory, string baseName, int sizeThreshold, int filesToKeep)
|
public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, int sizeThreshold, int filesToKeep)
|
||||||
: base(logDirectory, baseName)
|
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
|
||||||
{
|
{
|
||||||
SizeTheshold = sizeThreshold;
|
SizeTheshold = sizeThreshold;
|
||||||
FilesToKeep = filesToKeep;
|
FilesToKeep = filesToKeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SizeBasedRollingLogAppender(string logDirectory, string baseName)
|
public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
|
||||||
: this(logDirectory, baseName, DEFAULT_SIZE_THRESHOLD, DEFAULT_FILES_TO_KEEP) { }
|
: this(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern, DEFAULT_SIZE_THRESHOLD, DEFAULT_FILES_TO_KEEP) { }
|
||||||
|
|
||||||
public override void log(Stream outputStream, Stream errorStream)
|
public override void log(Stream outputStream, Stream errorStream)
|
||||||
{
|
{
|
||||||
new Thread(delegate() { CopyStreamWithRotation(outputStream, ".out.log"); }).Start();
|
if (!OutFileDisabled) new Thread(delegate() { CopyStreamWithRotation(outputStream, OutFilePattern); }).Start();
|
||||||
new Thread(delegate() { CopyStreamWithRotation(errorStream, ".err.log"); }).Start();
|
if (!ErrFileDisabled) new Thread(delegate() { CopyStreamWithRotation(errorStream, ErrFilePattern); }).Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -299,16 +309,189 @@ namespace winsw
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RollingLogAppender : SimpleLogAppender
|
public class RollingLogAppender : SimpleLogAppender
|
||||||
{
|
{
|
||||||
public RollingLogAppender(string logDirectory, string baseName)
|
public RollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
|
||||||
: base(logDirectory, baseName, FileMode.Append)
|
: base(logDirectory, baseName, FileMode.Append, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void log(Stream outputStream, Stream errorStream)
|
public override void log(Stream outputStream, Stream errorStream)
|
||||||
{
|
{
|
||||||
CopyFile(OutputLogFileName, OutputLogFileName + ".old");
|
if (!OutFileDisabled) CopyFile(OutputLogFileName, OutputLogFileName + ".old");
|
||||||
CopyFile(ErrorLogFileName, ErrorLogFileName + ".old");
|
if (!ErrFileDisabled) CopyFile(ErrorLogFileName, ErrorLogFileName + ".old");
|
||||||
base.log(outputStream, errorStream);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,13 +103,20 @@ namespace winsw
|
||||||
return SingleElement(tagName, false);
|
return SingleElement(tagName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string SingleElement(string tagName, Boolean optional)
|
private string SingleElement(string tagName, bool optional)
|
||||||
{
|
{
|
||||||
var n = dom.SelectSingleNode("//" + tagName);
|
var n = dom.SelectSingleNode("//" + tagName);
|
||||||
if (n == null && !optional) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
|
if (n == null && !optional) throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
|
||||||
return n == null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
|
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)
|
private int SingleIntElement(XmlNode parent, string tagName, int defaultValue)
|
||||||
{
|
{
|
||||||
var e = parent.SelectSingleNode(tagName);
|
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
|
public LogHandler LogHandler
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -366,19 +416,20 @@ namespace winsw
|
||||||
// this is more modern way, to support nested elements as configuration
|
// this is more modern way, to support nested elements as configuration
|
||||||
e = (XmlElement)dom.SelectSingleNode("//log");
|
e = (XmlElement)dom.SelectSingleNode("//log");
|
||||||
}
|
}
|
||||||
|
int sizeThreshold;
|
||||||
switch (LogMode)
|
switch (LogMode)
|
||||||
{
|
{
|
||||||
case "rotate":
|
case "rotate":
|
||||||
return new SizeBasedRollingLogAppender(LogDirectory, BaseName);
|
return new SizeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
|
||||||
|
|
||||||
case "none":
|
case "none":
|
||||||
return new IgnoreLogAppender();
|
return new IgnoreLogAppender();
|
||||||
|
|
||||||
case "reset":
|
case "reset":
|
||||||
return new ResetLogAppender(LogDirectory, BaseName);
|
return new ResetLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
|
||||||
|
|
||||||
case "roll":
|
case "roll":
|
||||||
return new RollingLogAppender(LogDirectory, BaseName);
|
return new RollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern);
|
||||||
|
|
||||||
case "roll-by-time":
|
case "roll-by-time":
|
||||||
XmlNode patternNode = e.SelectSingleNode("pattern");
|
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.");
|
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);
|
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":
|
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);
|
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":
|
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:
|
default:
|
||||||
throw new InvalidDataException("Undefined logging mode: " + LogMode);
|
throw new InvalidDataException("Undefined logging mode: " + LogMode);
|
||||||
|
|
|
@ -184,6 +184,61 @@ namespace winswTests
|
||||||
Assert.That(serviceDescriptor.StopTimeout, Is.EqualTo(TimeSpan.FromMinutes(10)));
|
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]
|
[Test]
|
||||||
public void LogModeRollBySize()
|
public void LogModeRollBySize()
|
||||||
{
|
{
|
||||||
|
@ -224,6 +279,28 @@ namespace winswTests
|
||||||
Assert.That(logHandler.Pattern, Is.EqualTo("log pattern"));
|
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]
|
[Test]
|
||||||
public void VerifyServiceLogonRightGraceful()
|
public void VerifyServiceLogonRightGraceful()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue