diff --git a/src/Essential.Diagnostics.RollingFileTraceListener/Diagnostics/RollingFileTraceListener.cs b/src/Essential.Diagnostics.RollingFileTraceListener/Diagnostics/RollingFileTraceListener.cs index 1417dd8..b86f701 100644 --- a/src/Essential.Diagnostics.RollingFileTraceListener/Diagnostics/RollingFileTraceListener.cs +++ b/src/Essential.Diagnostics.RollingFileTraceListener/Diagnostics/RollingFileTraceListener.cs @@ -33,13 +33,33 @@ public class RollingFileTraceListener : TraceListenerBase private const string _defaultFilePathTemplate = "{ApplicationName}-{DateTime:yyyy-MM-dd}.log"; // Default format matches Microsoft.VisualBasic.Logging.FileLogTraceListener private const string _defaultTemplate = "{DateTime:u} [{Thread}] {EventType} {Source} {Id}: {Message}{Data}"; + private readonly string _filePathTemplate; private static string[] _supportedAttributes = new string[] { "template", "Template", "convertWriteToEvent", "ConvertWriteToEvent", + "newStreamOnError", "NewStreamOnError" }; TraceFormatter traceFormatter = new TraceFormatter(); - private RollingTextWriter rollingTextWriter; + private object _rollingTextWriterLock = new object(); + private RollingTextWriter _rollingTextWriter = null; + private RollingTextWriter RollingTextWriter + { + get + { + if (_rollingTextWriter == null) + { + lock (_rollingTextWriterLock) + { + if (_rollingTextWriter == null) + { + _rollingTextWriter = RollingTextWriter.Create(_filePathTemplate, NewStreamOnError); + } + } + } + return _rollingTextWriter; + } + } /// /// Constructor. Writes to a rolling text file using the default name. @@ -78,11 +98,11 @@ public RollingFileTraceListener(string filePathTemplate) { if (string.IsNullOrEmpty(filePathTemplate)) { - rollingTextWriter = new RollingTextWriter(_defaultFilePathTemplate); + _filePathTemplate = _defaultFilePathTemplate; } else { - rollingTextWriter = RollingTextWriter.Create(filePathTemplate); + _filePathTemplate = filePathTemplate; } } @@ -111,13 +131,47 @@ public bool ConvertWriteToEvent } } + private bool? _newStreamOnError = null; + /// + /// Gets or sets whether errors writing to the file should cause a new file stream to be instantiated. + /// Useful when the drive containing the file has a transient fault (usb stick removed and reinserted, network outage of mapped drive, etc.) + /// + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Boolean.TryParse(System.String,System.Boolean@)", Justification = "Default value is acceptable if conversion fails.")] + public bool NewStreamOnError + { + get + { + if (_newStreamOnError.HasValue) + { + return _newStreamOnError.Value; + } + + // Default behaviour is to create a new stream on error + var newStreamOnError = true; + if (Attributes.ContainsKey("newStreamOnError")) + { + if(bool.TryParse(Attributes["newStreamOnError"], out newStreamOnError) == false) + { + newStreamOnError = true; + } + } + _newStreamOnError = newStreamOnError; + return newStreamOnError; + } + set + { + Attributes["newStreamOnError"] = value.ToString(CultureInfo.InvariantCulture); + _newStreamOnError = value; + } + } + /// /// Gets or sets the file system to use; this defaults to an adapter for System.IO.File. /// public IFileSystem FileSystem { - get { return rollingTextWriter.FileSystem; } - set { rollingTextWriter.FileSystem = value; } + get { return RollingTextWriter.FileSystem; } + set { RollingTextWriter.FileSystem = value; } } /// @@ -186,7 +240,7 @@ public string Template /// public string FilePathTemplate { - get { return rollingTextWriter.FilePathTemplate; } + get { return RollingTextWriter.FilePathTemplate; } } /// @@ -194,7 +248,7 @@ public string FilePathTemplate /// public override void Flush() { - rollingTextWriter.Flush(); + RollingTextWriter.Flush(); } /// @@ -218,7 +272,7 @@ protected override void Write(string category, string message, object data) } else { - rollingTextWriter.Write(null, message); + RollingTextWriter.Write(null, message); } } @@ -235,7 +289,7 @@ protected override void WriteLine(string category, string message, object data) } else { - rollingTextWriter.WriteLine(null, message); + RollingTextWriter.WriteLine(null, message); } } @@ -266,16 +320,16 @@ protected override void WriteTrace(TraceEventCache eventCache, string source, Tr relatedActivityId, data ); - rollingTextWriter.WriteLine(eventCache, output); + RollingTextWriter.WriteLine(eventCache, output); } protected override void Dispose(bool disposing) { if (disposing) { - if (rollingTextWriter != null) + if (RollingTextWriter != null) { - rollingTextWriter.Dispose(); + RollingTextWriter.Dispose(); } } base.Dispose(disposing); diff --git a/src/Essential.Diagnostics.RollingFileTraceListener/Diagnostics/RollingTextWriter.cs b/src/Essential.Diagnostics.RollingFileTraceListener/Diagnostics/RollingTextWriter.cs index f0c6bb4..f4a9bef 100644 --- a/src/Essential.Diagnostics.RollingFileTraceListener/Diagnostics/RollingTextWriter.cs +++ b/src/Essential.Diagnostics.RollingFileTraceListener/Diagnostics/RollingTextWriter.cs @@ -16,10 +16,12 @@ class RollingTextWriter : IDisposable private string _filePathTemplate; private IFileSystem _fileSystem = new FileSystem(); TraceFormatter traceFormatter = new TraceFormatter(); + private bool _newStreamOnError; - public RollingTextWriter(string filePathTemplate) + public RollingTextWriter(string filePathTemplate, bool newStreamOnError) { _filePathTemplate = filePathTemplate; + _newStreamOnError = newStreamOnError; } /// @@ -27,7 +29,7 @@ public RollingTextWriter(string filePathTemplate) /// /// /// - public static RollingTextWriter Create(string filePathTemplate) + public static RollingTextWriter Create(string filePathTemplate, bool newStreamOnError) { var segments = filePathTemplate.Split('%'); if (segments.Length > 3) @@ -50,10 +52,10 @@ public static RollingTextWriter Create(string filePathTemplate) } } var filePath = rootFolder + segments[2]; - return new RollingTextWriter(filePath); + return new RollingTextWriter(filePath, newStreamOnError); } - return new RollingTextWriter(filePathTemplate); + return new RollingTextWriter(filePathTemplate, newStreamOnError); } @@ -80,7 +82,18 @@ public void Flush() { if (_currentWriter != null) { - _currentWriter.Flush(); + try + { + _currentWriter.Flush(); + } + catch + { + if (_newStreamOnError) + { + DestroyCurrentWriter(); + } + throw; + } } } } @@ -91,7 +104,18 @@ public void Write(TraceEventCache eventCache, string value) lock (_fileLock) { EnsureCurrentWriter(filePath); - _currentWriter.Write(value); + try + { + _currentWriter.Write(value); + } + catch + { + if(_newStreamOnError) + { + DestroyCurrentWriter(); + } + throw; + } } } @@ -101,7 +125,18 @@ public void WriteLine(TraceEventCache eventCache, string value) lock (_fileLock) { EnsureCurrentWriter(filePath); - _currentWriter.WriteLine(value); + try + { + _currentWriter.WriteLine(value); + } + catch + { + if (_newStreamOnError) + { + DestroyCurrentWriter(); + } + throw; + } } } @@ -148,6 +183,18 @@ private void EnsureCurrentWriter(string path) } } + private void DestroyCurrentWriter() + { + // NOTE: This is called inside lock(_fileLock) + if (_currentWriter != null) + { + _currentWriter.Close(); + _currentWriter.Dispose(); + _currentWriter = null; + _currentPath = null; + } + } + static string getFullPath(string path, int num) { var extension = Path.GetExtension(path); diff --git a/src/Essential.Diagnostics.RollingXmlTraceListener/Diagnostics/RollingXmlTraceListener.cs b/src/Essential.Diagnostics.RollingXmlTraceListener/Diagnostics/RollingXmlTraceListener.cs index 90f1e66..a754a2d 100644 --- a/src/Essential.Diagnostics.RollingXmlTraceListener/Diagnostics/RollingXmlTraceListener.cs +++ b/src/Essential.Diagnostics.RollingXmlTraceListener/Diagnostics/RollingXmlTraceListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text; @@ -40,13 +41,32 @@ public class RollingXmlTraceListener : TraceListenerBase private readonly string machineName = Environment.MachineName; // Default format matches Microsoft.VisualBasic.Logging.FileLogTraceListener private const string _defaultFilePathTemplate = "{ApplicationName}-{DateTime:yyyy-MM-dd}.svclog"; + private readonly string _filePathTemplate; private static string[] _supportedAttributes = new string[] - { + { + "newStreamOnError", "NewStreamOnError" }; TraceFormatter traceFormatter = new TraceFormatter(); - private RollingTextWriter rollingTextWriter; - + private object _rollingTextWriterLock = new object(); + private RollingTextWriter _rollingTextWriter = null; + private RollingTextWriter RollingTextWriter + { + get + { + if (_rollingTextWriter == null) + { + lock (_rollingTextWriterLock) + { + if (_rollingTextWriter == null) + { + _rollingTextWriter = RollingTextWriter.Create(_filePathTemplate, NewStreamOnError); + } + } + } + return _rollingTextWriter; + } + } /// /// Constructor. Writes to a rolling text file using the default name. /// @@ -84,11 +104,11 @@ public RollingXmlTraceListener(string filePathTemplate) { if (string.IsNullOrEmpty(filePathTemplate)) { - rollingTextWriter = new RollingTextWriter(_defaultFilePathTemplate); + _filePathTemplate = _defaultFilePathTemplate; } else { - rollingTextWriter = RollingTextWriter.Create(filePathTemplate); + _filePathTemplate = filePathTemplate; } } @@ -97,8 +117,8 @@ public RollingXmlTraceListener(string filePathTemplate) /// public IFileSystem FileSystem { - get { return rollingTextWriter.FileSystem; } - set { rollingTextWriter.FileSystem = value; } + get { return RollingTextWriter.FileSystem; } + set { RollingTextWriter.FileSystem = value; } } /// @@ -122,7 +142,40 @@ public override bool IsThreadSafe /// public string FilePathTemplate { - get { return rollingTextWriter.FilePathTemplate; } + get { return RollingTextWriter.FilePathTemplate; } + } + + private bool? _newStreamOnError = null; + /// + /// Gets or sets whether errors writing to the file should cause a new file stream to be instantiated. + /// Useful when the drive containing the file has a transient fault (usb stick removed and reinserted, network outage of mapped drive, etc.) + /// + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Boolean.TryParse(System.String,System.Boolean@)", Justification = "Default value is acceptable if conversion fails.")] + public bool NewStreamOnError + { + get + { + if(_newStreamOnError.HasValue) + { + return _newStreamOnError.Value; + } + + // Default behaviour is to create a new stream on error + var newStreamOnError = true; + if (Attributes.ContainsKey("newStreamOnError")) + { + if (bool.TryParse(Attributes["newStreamOnError"], out newStreamOnError) == false) + { + newStreamOnError = true; + } + } + _newStreamOnError = newStreamOnError; + return newStreamOnError; + } + set + { + Attributes["newStreamOnError"] = value.ToString(CultureInfo.InvariantCulture); + } } /// @@ -130,7 +183,7 @@ public string FilePathTemplate /// public override void Flush() { - rollingTextWriter.Flush(); + RollingTextWriter.Flush(); } /// @@ -170,7 +223,7 @@ protected override void WriteTrace(TraceEventCache eventCache, string source, Tr AppendFooter(output, eventCache); - rollingTextWriter.WriteLine(eventCache, output.ToString()); + RollingTextWriter.WriteLine(eventCache, output.ToString()); } private static void AppendData(StringBuilder output, object data)