Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Manually applied patch from comment #5 in

  • Loading branch information...
commit 9118145a4b7f46662f9c3b19acd7dc61b0008e0e 1 parent f211481
@kohsuke authored
View
319 LogAppenders.cs
@@ -0,0 +1,319 @@
+using System.IO;
+using System.Diagnostics;
+using System.Threading;
+
+namespace winsw
+{
+ public interface EventLogger
+ {
+ void LogEvent(string message);
+ void LogEvent(string message, EventLogEntryType type);
+ }
+
+ /// <summary>
+ /// Abstraction for handling log.
+ /// </summary>
+ public abstract class LogHandler
+ {
+ private EventLogger eventLogger;
+ private string baseLogFileName;
+
+ public LogHandler(string logDirectory, string baseName)
+ {
+ this.baseLogFileName = Path.Combine(logDirectory, baseName);
+ }
+
+ public abstract void log(Stream outputStream, Stream errorStream);
+
+ public EventLogger EventLogger
+ {
+ set
+ {
+ this.eventLogger = value;
+ }
+ get
+ {
+ return this.eventLogger;
+ }
+ }
+
+ public string BaseLogFileName
+ {
+ get
+ {
+ return this.baseLogFileName;
+ }
+ }
+
+ /// <summary>
+ /// Convenience method to copy stuff from StreamReader to StreamWriter
+ /// </summary>
+ protected void CopyStream(Stream i, Stream o)
+ {
+ byte[] buf = new byte[1024];
+ while (true)
+ {
+ int sz = i.Read(buf, 0, buf.Length);
+ if (sz == 0) break;
+ o.Write(buf, 0, sz);
+ o.Flush();
+ }
+ i.Close();
+ o.Close();
+ }
+
+ /// <summary>
+ /// File replacement.
+ /// </summary>
+ protected void CopyFile(string sourceFileName, string destFileName)
+ {
+ try
+ {
+ File.Delete(destFileName);
+ File.Move(sourceFileName, destFileName);
+ }
+ catch (IOException e)
+ {
+ EventLogger.LogEvent("Failed to copy :" + sourceFileName + " to " + destFileName + " because " + e.Message);
+ }
+ }
+ }
+
+ public abstract class SimpleLogAppender : LogHandler
+ {
+
+ private FileMode fileMode;
+ private string outputLogFileName;
+ private string errorLogFileName;
+
+ public SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode)
+ : base(logDirectory, baseName)
+ {
+ this.fileMode = fileMode;
+ this.outputLogFileName = BaseLogFileName + ".out.log";
+ this.errorLogFileName = BaseLogFileName + ".err.log";
+ }
+
+ public string OutputLogFileName
+ {
+ get
+ {
+ return this.outputLogFileName;
+ }
+ }
+
+ public string ErrorLogFileName
+ {
+ get
+ {
+ return this.errorLogFileName;
+ }
+ }
+
+ 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();
+ }
+ }
+
+ public class DefaultLogAppender : SimpleLogAppender
+ {
+ public DefaultLogAppender(string logDirectory, string baseName)
+ : base(logDirectory, baseName, FileMode.Append)
+ {
+ }
+ }
+
+ public class ResetLogAppender : SimpleLogAppender
+ {
+ public ResetLogAppender(string logDirectory, string baseName)
+ : base(logDirectory, baseName, FileMode.Create)
+ {
+ }
+
+ }
+
+ public class TimeBasedRollingLogAppender : LogHandler
+ {
+
+ private string pattern;
+ private int period;
+
+ public TimeBasedRollingLogAppender(string logDirectory, string baseName, string pattern, int period)
+ : base(logDirectory, baseName)
+ {
+ this.pattern = pattern;
+ this.period = period;
+ }
+
+ public override void log(Stream outputStream, Stream errorStream)
+ {
+ new Thread(delegate() { CopyStreamWithDateRotation(outputStream, ".out.log"); }).Start();
+ new Thread(delegate() { CopyStreamWithDateRotation(errorStream, ".err.log"); }).Start();
+ }
+
+ /// <summary>
+ /// Works like the CopyStream method but does a log rotation based on time.
+ /// </summary>
+ private void CopyStreamWithDateRotation(Stream data, string ext)
+ {
+ PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(pattern, period);
+ periodicRollingCalendar.init();
+
+ byte[] buf = new byte[1024];
+ FileStream w = new FileStream(BaseLogFileName + "_" + periodicRollingCalendar.format + ext, FileMode.Create);
+ while (true)
+ {
+ int len = data.Read(buf, 0, buf.Length);
+ if (len == 0) break; // EOF
+
+ if (periodicRollingCalendar.shouldRoll)
+ {// rotate at the line boundary
+ int offset = 0;
+ bool rolled = false;
+ for (int i = 0; i < len; i++)
+ {
+ if (buf[i] == 0x0A)
+ {// at the line boundary.
+ // time to rotate.
+ w.Write(buf, offset, i + 1);
+ w.Close();
+ offset = i + 1;
+
+ // create a new file.
+ w = new FileStream(BaseLogFileName + "_" + periodicRollingCalendar.format + ext, FileMode.Create);
+ rolled = true;
+ }
+ }
+
+ if (!rolled)
+ {// we didn't roll - most likely as we didnt find a line boundary, so we should log what we read and roll anyway.
+ w.Write(buf, 0, len);
+ w.Close();
+ w = new FileStream(BaseLogFileName + "_" + periodicRollingCalendar.format + ext, FileMode.Create);
+ }
+
+ }
+ else
+ {// typical case. write the whole thing into the current file
+ w.Write(buf, 0, len);
+ }
+
+ w.Flush();
+ }
+ data.Close();
+ w.Close();
+ }
+
+ }
+
+ public class SizeBasedRollingLogAppender : LogHandler
+ {
+ public static int BYTES_PER_KB = 1024;
+ public static int BYTES_PER_MB = 1024 * BYTES_PER_KB;
+ public static int DEFAULT_SIZE_THRESHOLD = 10 * BYTES_PER_MB; // rotate every 10MB.
+ public static int DEFAULT_FILES_TO_KEEP = 8;
+
+ private int sizeThreshold;
+ private int filesToKeep;
+
+ public SizeBasedRollingLogAppender(string logDirectory, string baseName, int sizeThreshold, int filesToKeep)
+ : base(logDirectory, baseName)
+ {
+ this.sizeThreshold = sizeThreshold;
+ this.filesToKeep = filesToKeep;
+ }
+
+ public SizeBasedRollingLogAppender(string logDirectory, string baseName)
+ : this(logDirectory, baseName, 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();
+ }
+
+ /// <summary>
+ /// Works like the CopyStream method but does a log rotation.
+ /// </summary>
+ private void CopyStreamWithRotation(Stream data, string ext)
+ {
+ byte[] buf = new byte[1024];
+ FileStream w = new FileStream(BaseLogFileName + ext, FileMode.Append);
+ long sz = new FileInfo(BaseLogFileName + ext).Length;
+
+ while (true)
+ {
+ int len = data.Read(buf, 0, buf.Length);
+ if (len == 0) break; // EOF
+ if (sz + len < sizeThreshold)
+ {// typical case. write the whole thing into the current file
+ w.Write(buf, 0, len);
+ sz += len;
+ }
+ else
+ {
+ // rotate at the line boundary
+ int s = 0;
+ for (int i = 0; i < len; i++)
+ {
+ if (buf[i] != 0x0A) continue;
+ if (sz + i < sizeThreshold) continue;
+
+ // at the line boundary and exceeded the rotation unit.
+ // time to rotate.
+ w.Write(buf, s, i + 1);
+ w.Close();
+ s = i + 1;
+
+ try
+ {
+ for (int j = filesToKeep; j >= 1; j--)
+ {
+ string dst = BaseLogFileName + "." + (j - 1) + ext;
+ string src = BaseLogFileName + "." + (j - 2) + ext;
+ if (File.Exists(dst))
+ File.Delete(dst);
+ if (File.Exists(src))
+ File.Move(src, dst);
+ }
+ File.Move(BaseLogFileName + ext, BaseLogFileName + ".0" + ext);
+ }
+ catch (IOException e)
+ {
+ EventLogger.LogEvent("Failed to rotate log: " + e.Message);
+ }
+
+ // even if the log rotation fails, create a new one, or else
+ // we'll infinitely try to rotate.
+ w = new FileStream(BaseLogFileName + ext, FileMode.Create);
+ sz = new FileInfo(BaseLogFileName + ext).Length;
+ }
+ }
+
+ w.Flush();
+ }
+ data.Close();
+ w.Close();
+ }
+ }
+
+ /// <summary>
+ /// Rotate log when a service is newly started.
+ /// </summary>
+ public class RollingLogAppender : SimpleLogAppender
+ {
+ public RollingLogAppender(string logDirectory, string baseName)
+ : base(logDirectory, baseName, FileMode.Append)
+ {
+ }
+
+ public override void log(Stream outputStream, Stream errorStream)
+ {
+ CopyFile(OutputLogFileName, OutputLogFileName + ".old");
+ CopyFile(ErrorLogFileName, ErrorLogFileName + ".old");
+ base.log(outputStream, errorStream);
+ }
+ }
+}
View
118 Main.cs
@@ -37,7 +37,7 @@ public enum State
SERVICE_PAUSED = 0x00000007,
}
- public class WrapperService : ServiceBase
+ public class WrapperService : ServiceBase, EventLogger
{
[DllImport("ADVAPI32.DLL")]
private static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
@@ -70,89 +70,6 @@ public WrapperService()
}
/// <summary>
- /// Copy stuff from StreamReader to StreamWriter
- /// </summary>
- private void CopyStream(Stream i, Stream o)
- {
- byte[] buf = new byte[1024];
- while (true)
- {
- int sz = i.Read(buf, 0, buf.Length);
- if (sz == 0) break;
- o.Write(buf, 0, sz);
- o.Flush();
- }
- i.Close();
- o.Close();
- }
-
- /// <summary>
- /// Works like the CopyStream method but does a log rotation.
- /// </summary>
- private void CopyStreamWithRotation(Stream data, string baseName, string ext)
- {
- int THRESHOLD = 10 * 1024 * 1024; // rotate every 10MB. should be made configurable.
-
- byte[] buf = new byte[1024];
- FileStream w = new FileStream(baseName + ext, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
- long sz = new FileInfo(baseName + ext).Length;
-
- while (true)
- {
- int len = data.Read(buf, 0, buf.Length);
- if (len == 0) break; // EOF
- if (sz + len < THRESHOLD)
- {// typical case. write the whole thing into the current file
- w.Write(buf, 0, len);
- sz += len;
- }
- else
- {
- // rotate at the line boundary
- int s = 0;
- for (int i = 0; i < len; i++)
- {
- if (buf[i] != 0x0A) continue;
- if (sz + i < THRESHOLD) continue;
-
- // at the line boundary and exceeded the rotation unit.
- // time to rotate.
- w.Write(buf, s, i + 1);
- w.Close();
- s = i + 1;
-
- try
- {
- for (int j = 8; j >= 0; j--)
- {
- string dst = baseName + "." + (j + 1) + ext;
- string src = baseName + "." + (j + 0) + ext;
- if (File.Exists(dst))
- File.Delete(dst);
- if (File.Exists(src))
- File.Move(src, dst);
- }
- File.Move(baseName + ext, baseName + ".0" + ext);
- }
- catch (IOException e)
- {
- LogEvent("Failed to rotate log: " + e.Message);
- }
-
- // even if the log rotation fails, create a new one, or else
- // we'll infinitely try to rotate.
- w = new FileStream(baseName + ext, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
- sz = new FileInfo(baseName + ext).Length;
- }
- }
-
- w.Flush();
- }
- data.Close();
- w.Close();
- }
-
- /// <summary>
/// Process the file copy instructions, so that we can replace files that are always in use while
/// the service runs.
/// </summary>
@@ -235,35 +152,12 @@ private void HandleLogfiles()
Directory.CreateDirectory(logDirectory);
}
- string baseName = descriptor.BaseName;
- string errorLogfilename = Path.Combine(logDirectory, baseName + ".err.log");
- string outputLogfilename = Path.Combine(logDirectory, baseName + ".out.log");
-
- if (descriptor.Logmode == "rotate")
- {
- string logName = Path.Combine(logDirectory, baseName);
- StartThread(delegate() { CopyStreamWithRotation(process.StandardOutput.BaseStream, logName, ".out.log"); });
- StartThread(delegate() { CopyStreamWithRotation(process.StandardError.BaseStream, logName, ".err.log"); });
- return;
- }
-
- FileMode fileMode = FileMode.Append;
-
- if (descriptor.Logmode == "reset")
- {
- fileMode = FileMode.Create;
- }
- else if (descriptor.Logmode == "roll")
- {
- CopyFile(outputLogfilename, outputLogfilename + ".old");
- CopyFile(errorLogfilename, errorLogfilename + ".old");
- }
-
- StartThread(delegate() { CopyStream(process.StandardOutput.BaseStream, new FileStream(outputLogfilename, fileMode, FileAccess.Write, FileShare.ReadWrite)); });
- StartThread(delegate() { CopyStream(process.StandardError.BaseStream, new FileStream(errorLogfilename, fileMode, FileAccess.Write, FileShare.ReadWrite)); });
+ LogHandler logAppender = descriptor.LogHandler;
+ logAppender.EventLogger = this;
+ logAppender.log(process.StandardOutput.BaseStream, process.StandardError.BaseStream);
}
- private void LogEvent(String message)
+ public void LogEvent(String message)
{
if (systemShuttingdown)
{
@@ -275,7 +169,7 @@ private void LogEvent(String message)
}
}
- private void LogEvent(String message, EventLogEntryType type)
+ public void LogEvent(String message, EventLogEntryType type)
{
if (systemShuttingdown)
{
View
122 PeriodicRollingCalendar.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Data;
+
+namespace winsw
+{
+ /**
+ * This is largely borrowed from the logback Rolling Calendar.
+ **/
+ public class PeriodicRollingCalendar
+ {
+ private PeriodicityType _periodicityType;
+ private string _format;
+ private long _period;
+ private DateTime _currentRoll;
+ private DateTime _nextRoll;
+
+ public PeriodicRollingCalendar(string format, long period)
+ {
+ this._format = format;
+ this._period = period;
+ this._currentRoll = DateTime.Now;
+ }
+
+ public void init()
+ {
+ this._periodicityType = determinePeriodicityType();
+ this._nextRoll = nextTriggeringTime(this._currentRoll);
+ }
+
+ public enum PeriodicityType
+ {
+ ERRONEOUS, TOP_OF_MILLISECOND, TOP_OF_SECOND, TOP_OF_MINUTE, TOP_OF_HOUR, TOP_OF_DAY
+ }
+
+ private static PeriodicityType[] VALID_ORDERED_LIST = new PeriodicityType[] {
+ PeriodicityType.TOP_OF_MILLISECOND, PeriodicityType.TOP_OF_SECOND, PeriodicityType.TOP_OF_MINUTE, PeriodicityType.TOP_OF_HOUR, PeriodicityType.TOP_OF_DAY
+ };
+
+ private PeriodicityType determinePeriodicityType()
+ {
+ PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(_format, _period);
+ DateTime epoch = new DateTime(1970, 1, 1);
+
+ foreach (PeriodicityType i in VALID_ORDERED_LIST)
+ {
+ string r0 = epoch.ToString(_format);
+ periodicRollingCalendar.periodicityType = i;
+
+ DateTime next = periodicRollingCalendar.nextTriggeringTime(epoch);
+ string r1 = next.ToString(_format);
+
+ if (r0 != null && r1 != null && !r0.Equals(r1))
+ {
+ return i;
+ }
+ }
+ return PeriodicityType.ERRONEOUS;
+ }
+
+ private DateTime nextTriggeringTime(DateTime input)
+ {
+ DateTime output;
+ switch (_periodicityType)
+ {
+ case PeriodicityType.TOP_OF_MILLISECOND:
+ output = new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond);
+ output = output.AddMilliseconds(_period);
+ return output;
+ case PeriodicityType.TOP_OF_SECOND:
+ output = new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second);
+ output = output.AddSeconds(_period);
+ return output;
+ case PeriodicityType.TOP_OF_MINUTE:
+ output = new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, 0);
+ output = output.AddMinutes(_period);
+ return output;
+ case PeriodicityType.TOP_OF_HOUR:
+ output = new DateTime(input.Year, input.Month, input.Day, input.Hour, 0, 0);
+ output = output.AddHours(_period);
+ return output;
+ case PeriodicityType.TOP_OF_DAY:
+ output = new DateTime(input.Year, input.Month, input.Day);
+ output = output.AddDays(_period);
+ return output;
+ default:
+ throw new Exception("invalid periodicity type: " + _periodicityType);
+ }
+ }
+
+ public PeriodicityType periodicityType
+ {
+ set
+ {
+ this._periodicityType = value;
+ }
+ }
+
+ public Boolean shouldRoll
+ {
+ get
+ {
+ DateTime now = DateTime.Now;
+ if (now > this._nextRoll)
+ {
+ this._currentRoll = now;
+ this._nextRoll = nextTriggeringTime(now);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public string format
+ {
+ get
+ {
+ return this._currentRoll.ToString(this._format);
+ }
+ }
+
+ }
+}
View
89 ServiceDescriptor.cs
@@ -76,6 +76,20 @@ private string SingleElement(string tagName)
return Environment.ExpandEnvironmentVariables(n.InnerText);
}
+ private int SingleIntElement(XmlNode parent, string tagName, int defaultValue)
+ {
+ var e = parent.SelectSingleNode(tagName);
+
+ if (e == null)
+ {
+ return defaultValue;
+ }
+ else
+ {
+ return int.Parse(e.InnerText);
+ }
+ }
+
/// <summary>
/// Path to the executable.
/// </summary>
@@ -215,25 +229,54 @@ public string LogDirectory
}
}
-
- /// <summary>
- /// Logmode to 'reset', 'rotate' once or 'append' [default] the out.log and err.log files.
- /// </summary>
- public string Logmode
+ public LogHandler LogHandler
{
get
{
- XmlNode logmodeNode = dom.SelectSingleNode("//logmode");
-
- if (logmodeNode == null)
- {
- return "append";
+ string mode;
+
+ // first, backward compatibility with older configuration
+ XmlElement e = (XmlElement)dom.SelectSingleNode("//logmode");
+ if (e!=null) {
+ mode = e.InnerText;
+ } else {
+ // this is more modern way, to support nested elements as configuration
+ e = (XmlElement)dom.SelectSingleNode("//log");
+ mode = e.GetAttribute("mode");
}
- else
+
+ switch (mode)
{
- return logmodeNode.InnerText;
+ case "rotate":
+ return new SizeBasedRollingLogAppender(LogDirectory, BaseName);
+
+ case "reset":
+ return new ResetLogAppender(LogDirectory, BaseName);
+
+ case "roll":
+ return new RollingLogAppender(LogDirectory, BaseName);
+
+ case "roll-by-time":
+ XmlNode patternNode = e.SelectSingleNode("pattern");
+ if (patternNode == null)
+ {
+ throw new InvalidDataException("Time Based rolling policy is specified but no pattern can be found in configuration XML.");
+ }
+ string pattern = patternNode.InnerText;
+ int period = SingleIntElement(e,"period",1);
+ return new TimeBasedRollingLogAppender(LogDirectory, BaseName, pattern, period);
+
+ case "roll-by-size":
+ int 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);
+
+ case "append":
+ default:
+ return new DefaultLogAppender(LogDirectory, BaseName);
}
}
+
}
/// <summary>
@@ -299,16 +342,7 @@ public int WaitHint
{
get
{
- XmlNode waithintNode = dom.SelectSingleNode("//waithint");
-
- if (waithintNode == null)
- {
- return 15000;
- }
- else
- {
- return int.Parse(waithintNode.InnerText);
- }
+ return SingleIntElement(dom, "waithint", 15000);
}
}
@@ -322,16 +356,7 @@ public int SleepTime
{
get
{
- XmlNode sleeptimeNode = dom.SelectSingleNode("//sleeptime");
-
- if (sleeptimeNode == null)
- {
- return 1000;
- }
- else
- {
- return int.Parse(sleeptimeNode.InnerText);
- }
+ return SingleIntElement(dom, "sleeptime", 15000);
}
}
View
3  winsw.csproj
@@ -48,9 +48,11 @@
<ItemGroup>
<Compile Include="Download.cs" />
<Compile Include="DynamicProxy.cs" />
+ <Compile Include="LogAppenders.cs" />
<Compile Include="Main.cs">
<SubType>Component</SubType>
</Compile>
+ <Compile Include="PeriodicRollingCalendar.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceDescriptor.cs" />
<Compile Include="Wmi.cs" />
@@ -58,6 +60,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="manifest.xml" />
+ <Content Include="pom.xml" />
<Content Include="winsw.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Please sign in to comment.
Something went wrong with that request. Please try again.