From ad5f78b3c594d80a5a931ec41dfabd9b3f6968e0 Mon Sep 17 00:00:00 2001 From: Next Turn <45985406+nxtn@users.noreply.github.com> Date: Fri, 1 Jan 2021 22:37:31 +0800 Subject: [PATCH] Backport redirection updates (#770) --- src/WinSW.Core/LogAppenders.cs | 135 +++++++++++++------ src/WinSW.Tests/ServiceDescriptorTests.cs | 4 +- src/WinSW.Tests/ServiceDescriptorYamlTest.cs | 4 +- 3 files changed, 100 insertions(+), 43 deletions(-) diff --git a/src/WinSW.Core/LogAppenders.cs b/src/WinSW.Core/LogAppenders.cs index eaf6dd6..13b4d87 100644 --- a/src/WinSW.Core/LogAppenders.cs +++ b/src/WinSW.Core/LogAppenders.cs @@ -47,12 +47,11 @@ namespace WinSW /// /// Convenience method to copy stuff from StreamReader to StreamWriter /// - protected void CopyStream(StreamReader reader, StreamWriter writer) + protected void CopyStream(Stream reader, Stream writer) { - string? line; - while ((line = reader.ReadLine()) != null) + var copy = new StreamCopyOperation(reader, writer); + while (copy.CopyLine() != 0) { - writer.WriteLine(line); } reader.Dispose(); @@ -107,8 +106,6 @@ namespace WinSW } } - protected StreamWriter CreateWriter(FileStream stream) => new(stream) { AutoFlush = true }; - protected abstract void LogOutput(StreamReader outputReader); protected abstract void LogError(StreamReader errorReader); @@ -132,12 +129,12 @@ namespace WinSW protected override void LogOutput(StreamReader outputReader) { - new Thread(() => this.CopyStream(outputReader, this.CreateWriter(new FileStream(this.OutputLogFileName, this.FileMode)))).Start(); + new Thread(() => this.CopyStream(outputReader.BaseStream, new FileStream(this.OutputLogFileName, this.FileMode))).Start(); } protected override void LogError(StreamReader errorReader) { - new Thread(() => this.CopyStream(errorReader, this.CreateWriter(new FileStream(this.ErrorLogFileName, this.FileMode)))).Start(); + new Thread(() => this.CopyStream(errorReader.BaseStream, new FileStream(this.ErrorLogFileName, this.FileMode))).Start(); } } @@ -203,17 +200,15 @@ namespace WinSW var periodicRollingCalendar = new PeriodicRollingCalendar(this.Pattern, this.Period); periodicRollingCalendar.Init(); - var writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Append)); - string? line; - while ((line = reader.ReadLine()) != null) + var writer = new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Append); + var copy = new StreamCopyOperation(reader.BaseStream, writer); + while (copy.CopyLine() != 0) { if (periodicRollingCalendar.ShouldRoll) { 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(); @@ -228,14 +223,14 @@ namespace WinSW public static int DefaultSizeThreshold = 10 * BytesPerMB; // roll every 10MB. public static int DefaultFilesToKeep = 8; - public int SizeTheshold { get; private set; } + public int SizeThreshold { get; private set; } public int FilesToKeep { get; private set; } 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) { - this.SizeTheshold = sizeThreshold; + this.SizeThreshold = sizeThreshold; this.FilesToKeep = filesToKeep; } @@ -259,20 +254,21 @@ namespace WinSW /// private void CopyStreamWithRotation(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; - string? line; - while ((line = reader.ReadLine()) != null) + int written; + while ((written = copy.CopyLine()) != 0) { - int lengthToWrite = Encoding.UTF8.GetByteCount(line) + 2; // CRLF - if (fileLength + lengthToWrite > this.SizeTheshold) + fileLength += written; + if (fileLength > this.SizeThreshold) { writer.Dispose(); try { - for (int j = this.FilesToKeep; j >= 1; j--) + for (int j = this.FilesToKeep; j >= 2; j--) { string dst = this.BaseLogFileName + "." + (j - 1) + ext; string src = this.BaseLogFileName + "." + (j - 2) + ext; @@ -296,12 +292,9 @@ namespace WinSW // even if the log rotation fails, create a new one, or else // 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; } - - writer.WriteLine(line); - fileLength += lengthToWrite; } reader.Dispose(); @@ -339,7 +332,7 @@ namespace WinSW { public static int BytesPerKB = 1024; - public int SizeTheshold { get; private set; } + public int SizeThreshold { get; private set; } public string FilePattern { get; private set; } @@ -363,7 +356,7 @@ namespace WinSW string zipdateformat) : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) { - this.SizeTheshold = sizeThreshold; + this.SizeThreshold = sizeThreshold; this.FilePattern = filePattern; this.AutoRollAtTime = autoRollAtTime; this.ZipOlderThanNumDays = zipolderthannumdays; @@ -383,13 +376,14 @@ namespace WinSW private void CopyStreamWithRotation(StreamReader reader, string extension) { // 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 - object fileLock = new object(); + object fileLock = new(); string baseDirectory = Path.GetDirectoryName(this.BaseLogFileName)!; string baseFileName = Path.GetFileName(this.BaseLogFileName); 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; // 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 @@ -412,7 +406,7 @@ namespace WinSW string nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension)); 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; } @@ -433,13 +427,13 @@ namespace WinSW timer.Start(); } - string? line; - while ((line = reader.ReadLine()) != null) + int written; + while ((written = copy.CopyLine()) != 0) { lock (fileLock) { - int lengthToWrite = Encoding.UTF8.GetByteCount(line) + 2; // CRLF - if (fileLength + lengthToWrite > this.SizeTheshold) + fileLength += written; + if (fileLength > this.SizeThreshold) { try { @@ -453,7 +447,7 @@ namespace WinSW // even if the log rotation fails, create a new one, or else // 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; } catch (Exception e) @@ -461,9 +455,6 @@ namespace WinSW this.EventLogger.LogEvent($"Failed to roll size time log: {e.Message}"); } } - - writer.WriteLine(line); - fileLength += lengthToWrite; } } @@ -618,4 +609,70 @@ namespace WinSW 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 int CopyLine() + { + 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 = source.Read(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; + } + } } diff --git a/src/WinSW.Tests/ServiceDescriptorTests.cs b/src/WinSW.Tests/ServiceDescriptorTests.cs index 385798e..bb221ec 100644 --- a/src/WinSW.Tests/ServiceDescriptorTests.cs +++ b/src/WinSW.Tests/ServiceDescriptorTests.cs @@ -246,7 +246,7 @@ $@" var logHandler = serviceDescriptor.Log.CreateLogHandler() as SizeBasedRollingLogAppender; Assert.That(logHandler, Is.Not.Null); - Assert.That(logHandler.SizeTheshold, Is.EqualTo(112 * 1024)); + Assert.That(logHandler.SizeThreshold, Is.EqualTo(112 * 1024)); Assert.That(logHandler.FilesToKeep, Is.EqualTo(113)); } @@ -287,7 +287,7 @@ $@" var logHandler = serviceDescriptor.Log.CreateLogHandler() as RollingSizeTimeLogAppender; Assert.That(logHandler, Is.Not.Null); - Assert.That(logHandler.SizeTheshold, Is.EqualTo(10240 * 1024)); + Assert.That(logHandler.SizeThreshold, 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))); } diff --git a/src/WinSW.Tests/ServiceDescriptorYamlTest.cs b/src/WinSW.Tests/ServiceDescriptorYamlTest.cs index 5bbbdca..f78fdb6 100644 --- a/src/WinSW.Tests/ServiceDescriptorYamlTest.cs +++ b/src/WinSW.Tests/ServiceDescriptorYamlTest.cs @@ -276,7 +276,7 @@ log: var logHandler = serviceDescriptor.Log.CreateLogHandler() as SizeBasedRollingLogAppender; Assert.That(logHandler, Is.Not.Null); - Assert.That(logHandler.SizeTheshold, Is.EqualTo(112 * 1024)); + Assert.That(logHandler.SizeThreshold, Is.EqualTo(112 * 1024)); Assert.That(logHandler.FilesToKeep, Is.EqualTo(113)); } @@ -325,7 +325,7 @@ log: var logHandler = serviceDescriptor.Log.CreateLogHandler() as RollingSizeTimeLogAppender; Assert.That(logHandler, Is.Not.Null); - Assert.That(logHandler.SizeTheshold, Is.EqualTo(10240 * 1024)); + Assert.That(logHandler.SizeThreshold, 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))); }