mirror of https://github.com/winsw/winsw
Tweak stdout/stderr redirections
parent
d865678b80
commit
62f13e64cf
|
@ -30,12 +30,12 @@ namespace WinSW
|
||||||
|
|
||||||
protected override Task LogOutput(StreamReader outputReader)
|
protected override Task LogOutput(StreamReader outputReader)
|
||||||
{
|
{
|
||||||
return this.CopyStreamAsync(outputReader, this.CreateWriter(new FileStream(this.outputPath!, FileMode.OpenOrCreate)));
|
return this.CopyStreamAsync(outputReader.BaseStream, new FileStream(this.outputPath!, FileMode.OpenOrCreate));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task LogError(StreamReader errorReader)
|
protected override Task LogError(StreamReader errorReader)
|
||||||
{
|
{
|
||||||
return this.CopyStreamAsync(errorReader, this.CreateWriter(new FileStream(this.errorPath!, FileMode.OpenOrCreate)));
|
return this.CopyStreamAsync(errorReader.BaseStream, new FileStream(this.errorPath!, FileMode.OpenOrCreate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,12 +66,11 @@ namespace WinSW
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convenience method to copy stuff from StreamReader to StreamWriter
|
/// Convenience method to copy stuff from StreamReader to StreamWriter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected async Task CopyStreamAsync(StreamReader reader, StreamWriter writer)
|
protected async Task CopyStreamAsync(Stream reader, Stream writer)
|
||||||
{
|
{
|
||||||
string? line;
|
var copy = new StreamCopyOperation(reader, writer);
|
||||||
while ((line = await reader.ReadLineAsync()) != null)
|
while (await copy.CopyLineAsync() != 0)
|
||||||
{
|
{
|
||||||
writer.WriteLine(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.Dispose();
|
reader.Dispose();
|
||||||
|
@ -126,8 +125,6 @@ namespace WinSW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected StreamWriter CreateWriter(FileStream stream) => new StreamWriter(stream) { AutoFlush = true };
|
|
||||||
|
|
||||||
protected abstract Task LogOutput(StreamReader outputReader);
|
protected abstract Task LogOutput(StreamReader outputReader);
|
||||||
|
|
||||||
protected abstract Task LogError(StreamReader errorReader);
|
protected abstract Task LogError(StreamReader errorReader);
|
||||||
|
@ -175,12 +172,12 @@ namespace WinSW
|
||||||
|
|
||||||
protected override Task LogOutput(StreamReader outputReader)
|
protected override Task LogOutput(StreamReader outputReader)
|
||||||
{
|
{
|
||||||
return this.CopyStreamAsync(outputReader, this.CreateWriter(new FileStream(this.OutputLogFileName, this.FileMode)));
|
return this.CopyStreamAsync(outputReader.BaseStream, new FileStream(this.OutputLogFileName, this.FileMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task LogError(StreamReader errorReader)
|
protected override Task LogError(StreamReader errorReader)
|
||||||
{
|
{
|
||||||
return this.CopyStreamAsync(errorReader, this.CreateWriter(new FileStream(this.ErrorLogFileName, this.FileMode)));
|
return this.CopyStreamAsync(errorReader.BaseStream, new FileStream(this.ErrorLogFileName, this.FileMode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,17 +243,15 @@ namespace WinSW
|
||||||
var periodicRollingCalendar = new PeriodicRollingCalendar(this.Pattern, this.Period);
|
var periodicRollingCalendar = new PeriodicRollingCalendar(this.Pattern, this.Period);
|
||||||
periodicRollingCalendar.Init();
|
periodicRollingCalendar.Init();
|
||||||
|
|
||||||
var writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Append));
|
var writer = new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Append);
|
||||||
string? line;
|
var copy = new StreamCopyOperation(reader.BaseStream, writer);
|
||||||
while ((line = await reader.ReadLineAsync()) != null)
|
while (await copy.CopyLineAsync() != 0)
|
||||||
{
|
{
|
||||||
if (periodicRollingCalendar.ShouldRoll)
|
if (periodicRollingCalendar.ShouldRoll)
|
||||||
{
|
{
|
||||||
writer.Dispose();
|
writer.Dispose();
|
||||||
writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Create));
|
copy.Writer = writer = new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Create);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteLine(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.Dispose();
|
reader.Dispose();
|
||||||
|
@ -302,14 +297,15 @@ namespace WinSW
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task CopyStreamWithRotationAsync(StreamReader reader, string ext)
|
private async Task CopyStreamWithRotationAsync(StreamReader reader, string ext)
|
||||||
{
|
{
|
||||||
var writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Append));
|
var writer = new FileStream(this.BaseLogFileName + ext, FileMode.Append);
|
||||||
|
var copy = new StreamCopyOperation(reader.BaseStream, writer);
|
||||||
long fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
|
long fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
|
||||||
|
|
||||||
string? line;
|
int written;
|
||||||
while ((line = await reader.ReadLineAsync()) != null)
|
while ((written = await copy.CopyLineAsync()) != 0)
|
||||||
{
|
{
|
||||||
int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char);
|
fileLength += written;
|
||||||
if (fileLength + lengthToWrite > this.SizeThreshold)
|
if (fileLength > this.SizeThreshold)
|
||||||
{
|
{
|
||||||
writer.Dispose();
|
writer.Dispose();
|
||||||
|
|
||||||
|
@ -339,12 +335,9 @@ namespace WinSW
|
||||||
|
|
||||||
// even if the log rotation fails, create a new one, or else
|
// even if the log rotation fails, create a new one, or else
|
||||||
// we'll infinitely try to roll.
|
// we'll infinitely try to roll.
|
||||||
writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Create));
|
copy.Writer = writer = new FileStream(this.BaseLogFileName + ext, FileMode.Create);
|
||||||
fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
|
fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteLine(line);
|
|
||||||
fileLength += lengthToWrite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.Dispose();
|
reader.Dispose();
|
||||||
|
@ -432,7 +425,8 @@ namespace WinSW
|
||||||
string? baseFileName = Path.GetFileName(this.BaseLogFileName);
|
string? baseFileName = Path.GetFileName(this.BaseLogFileName);
|
||||||
string? logFile = this.BaseLogFileName + extension;
|
string? logFile = this.BaseLogFileName + extension;
|
||||||
|
|
||||||
var writer = this.CreateWriter(new FileStream(logFile, FileMode.Append));
|
var writer = new FileStream(logFile, FileMode.Append);
|
||||||
|
var copy = new StreamCopyOperation(reader.BaseStream, writer);
|
||||||
long fileLength = new FileInfo(logFile).Length;
|
long fileLength = 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
|
// 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
|
||||||
|
@ -455,7 +449,7 @@ namespace WinSW
|
||||||
string? nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension));
|
string? nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension));
|
||||||
File.Move(logFile, nextFileName);
|
File.Move(logFile, nextFileName);
|
||||||
|
|
||||||
writer = this.CreateWriter(new FileStream(logFile, FileMode.Create));
|
copy.Writer = writer = new FileStream(logFile, FileMode.Create);
|
||||||
fileLength = new FileInfo(logFile).Length;
|
fileLength = new FileInfo(logFile).Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,13 +470,13 @@ namespace WinSW
|
||||||
timer.Start();
|
timer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
string? line;
|
int written;
|
||||||
while ((line = await reader.ReadLineAsync()) != null)
|
while ((written = await copy.CopyLineAsync()) != 0)
|
||||||
{
|
{
|
||||||
lock (fileLock)
|
lock (fileLock)
|
||||||
{
|
{
|
||||||
int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char);
|
fileLength += written;
|
||||||
if (fileLength + lengthToWrite > this.SizeThreshold)
|
if (fileLength > this.SizeThreshold)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -496,7 +490,7 @@ namespace WinSW
|
||||||
|
|
||||||
// even if the log rotation fails, create a new one, or else
|
// even if the log rotation fails, create a new one, or else
|
||||||
// we'll infinitely try to roll.
|
// we'll infinitely try to roll.
|
||||||
writer = this.CreateWriter(new FileStream(logFile, FileMode.Create));
|
copy.Writer = writer = new FileStream(logFile, FileMode.Create);
|
||||||
fileLength = new FileInfo(logFile).Length;
|
fileLength = new FileInfo(logFile).Length;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -504,9 +498,6 @@ namespace WinSW
|
||||||
this.EventLogger.WriteEntry($"Failed to roll size time log: {e.Message}");
|
this.EventLogger.WriteEntry($"Failed to roll size time log: {e.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteLine(line);
|
|
||||||
fileLength += lengthToWrite;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,4 +624,70 @@ namespace WinSW
|
||||||
return nextFileNumber;
|
return nextFileNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class StreamCopyOperation
|
||||||
|
{
|
||||||
|
private const int BufferSize = 1024;
|
||||||
|
|
||||||
|
private readonly byte[] buffer;
|
||||||
|
private readonly Stream reader;
|
||||||
|
|
||||||
|
private int startIndex;
|
||||||
|
private int endIndex;
|
||||||
|
|
||||||
|
internal Stream Writer;
|
||||||
|
|
||||||
|
internal StreamCopyOperation(Stream reader, Stream writer)
|
||||||
|
{
|
||||||
|
this.buffer = new byte[BufferSize];
|
||||||
|
this.reader = reader;
|
||||||
|
this.startIndex = 0;
|
||||||
|
this.endIndex = 0;
|
||||||
|
this.Writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<int> CopyLineAsync()
|
||||||
|
{
|
||||||
|
byte[] buffer = this.buffer;
|
||||||
|
var source = this.reader;
|
||||||
|
int startIndex = this.startIndex;
|
||||||
|
int endIndex = this.endIndex;
|
||||||
|
var destination = this.Writer;
|
||||||
|
|
||||||
|
int total = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (startIndex == 0)
|
||||||
|
{
|
||||||
|
if ((endIndex = await source.ReadAsync(buffer, 0, BufferSize)) == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int buffered = endIndex - startIndex;
|
||||||
|
|
||||||
|
int newLineIndex = Array.IndexOf(buffer, (byte)'\n', startIndex, buffered);
|
||||||
|
if (newLineIndex >= 0)
|
||||||
|
{
|
||||||
|
int count = newLineIndex - startIndex + 1;
|
||||||
|
total += count;
|
||||||
|
destination.Write(buffer, startIndex, count);
|
||||||
|
destination.Flush();
|
||||||
|
startIndex = (newLineIndex + 1) % BufferSize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += buffered;
|
||||||
|
destination.Write(buffer, startIndex, buffered);
|
||||||
|
destination.Flush();
|
||||||
|
startIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startIndex = startIndex;
|
||||||
|
this.endIndex = endIndex;
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Xunit;
|
||||||
|
using static System.IO.File;
|
||||||
|
|
||||||
|
namespace WinSW.Tests
|
||||||
|
{
|
||||||
|
public class LogAppenderTests
|
||||||
|
{
|
||||||
|
private const byte CR = 0x0d;
|
||||||
|
private const byte LF = 0x0a;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DefaultLogAppender()
|
||||||
|
{
|
||||||
|
byte[] stdout = { 0x4e, 0x65, 0x78, 0x74 };
|
||||||
|
byte[] stderr = { 0x54, 0x75, 0x72, 0x6e };
|
||||||
|
|
||||||
|
using var data = TestData.Create();
|
||||||
|
|
||||||
|
string baseName = data.name;
|
||||||
|
string outFileExt = ".out.log";
|
||||||
|
string errFileExt = ".err.log";
|
||||||
|
string outFileName = baseName + outFileExt;
|
||||||
|
string errFileName = baseName + errFileExt;
|
||||||
|
string outFilePath = Path.Combine(data.path, outFileName);
|
||||||
|
string errFilePath = Path.Combine(data.path, errFileName);
|
||||||
|
|
||||||
|
WriteAllBytes(outFilePath, stdout);
|
||||||
|
WriteAllBytes(errFilePath, stderr);
|
||||||
|
|
||||||
|
var appender = new DefaultLogAppender(data.path, data.name, false, false, outFileExt, errFileExt);
|
||||||
|
appender.Log(new(new MemoryStream(stdout)), new(new MemoryStream(stderr)));
|
||||||
|
|
||||||
|
Assert.True(Exists(outFilePath));
|
||||||
|
Assert.True(Exists(errFilePath));
|
||||||
|
|
||||||
|
Assert.Equal(stdout.Concat(stdout), ReadAllBytes(outFilePath));
|
||||||
|
Assert.Equal(stderr.Concat(stderr), ReadAllBytes(errFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResetLogAppender()
|
||||||
|
{
|
||||||
|
byte[] stdout = { 0x4e, 0x65, 0x78, 0x74 };
|
||||||
|
byte[] stderr = { 0x54, 0x75, 0x72, 0x6e };
|
||||||
|
|
||||||
|
using var data = TestData.Create();
|
||||||
|
|
||||||
|
string baseName = data.name;
|
||||||
|
string outFileExt = ".out.log";
|
||||||
|
string errFileExt = ".err.log";
|
||||||
|
string outFileName = baseName + outFileExt;
|
||||||
|
string errFileName = baseName + errFileExt;
|
||||||
|
string outFilePath = Path.Combine(data.path, outFileName);
|
||||||
|
string errFilePath = Path.Combine(data.path, errFileName);
|
||||||
|
|
||||||
|
WriteAllBytes(outFilePath, stderr);
|
||||||
|
WriteAllBytes(errFilePath, stdout);
|
||||||
|
|
||||||
|
var appender = new ResetLogAppender(data.path, data.name, false, false, outFileExt, errFileExt);
|
||||||
|
appender.Log(new(new MemoryStream(stdout)), new(new MemoryStream(stderr)));
|
||||||
|
|
||||||
|
Assert.True(Exists(outFilePath));
|
||||||
|
Assert.True(Exists(errFilePath));
|
||||||
|
|
||||||
|
Assert.Equal(stdout, ReadAllBytes(outFilePath));
|
||||||
|
Assert.Equal(stderr, ReadAllBytes(errFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IgnoreLogAppender()
|
||||||
|
{
|
||||||
|
byte[] stdout = { 0x4e, 0x65, 0x78, 0x74 };
|
||||||
|
byte[] stderr = { 0x54, 0x75, 0x72, 0x6e };
|
||||||
|
|
||||||
|
using var data = TestData.Create();
|
||||||
|
|
||||||
|
string baseName = data.name;
|
||||||
|
string outFileExt = ".out.log";
|
||||||
|
string errFileExt = ".err.log";
|
||||||
|
string outFileName = baseName + outFileExt;
|
||||||
|
string errFileName = baseName + errFileExt;
|
||||||
|
string outFilePath = Path.Combine(data.path, outFileName);
|
||||||
|
string errFilePath = Path.Combine(data.path, errFileName);
|
||||||
|
|
||||||
|
var appender = new IgnoreLogAppender();
|
||||||
|
appender.Log(new(new MemoryStream(stdout)), new(new MemoryStream(stderr)));
|
||||||
|
|
||||||
|
Assert.False(Exists(outFilePath));
|
||||||
|
Assert.False(Exists(errFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SizeBasedRollingLogAppender()
|
||||||
|
{
|
||||||
|
byte[] stdout = { 0x4e, 0x65, CR, LF, 0x78, 0x74 };
|
||||||
|
byte[] stderr = { 0x54, 0x75, CR, LF, 0x72, 0x6e };
|
||||||
|
|
||||||
|
using var data = TestData.Create();
|
||||||
|
|
||||||
|
string baseName = data.name;
|
||||||
|
string outFileExt = ".out.log";
|
||||||
|
string errFileExt = ".err.log";
|
||||||
|
|
||||||
|
var appender = new SizeBasedRollingLogAppender(data.path, data.name, false, false, outFileExt, errFileExt, 3, 2);
|
||||||
|
appender.Log(new(new MemoryStream(stdout)), new(new MemoryStream(stderr)));
|
||||||
|
|
||||||
|
Assert.Equal(stdout.Take(4), ReadAllBytes(Path.Combine(data.path, baseName + ".0" + outFileExt)));
|
||||||
|
Assert.Equal(stdout.Skip(4), ReadAllBytes(Path.Combine(data.path, baseName + outFileExt)));
|
||||||
|
Assert.Equal(stderr.Take(4), ReadAllBytes(Path.Combine(data.path, baseName + ".0" + errFileExt)));
|
||||||
|
Assert.Equal(stderr.Skip(4), ReadAllBytes(Path.Combine(data.path, baseName + errFileExt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ref struct TestData
|
||||||
|
{
|
||||||
|
internal readonly string name;
|
||||||
|
internal readonly string path;
|
||||||
|
|
||||||
|
private TestData(string name, string path)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static TestData Create([CallerMemberName] string name = null)
|
||||||
|
{
|
||||||
|
string path = Path.Combine(Path.GetTempPath(), name);
|
||||||
|
_ = Directory.CreateDirectory(path);
|
||||||
|
|
||||||
|
return new(name, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Directory.Delete(this.path, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue