|
|
@@ -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;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|