//****************************************************************************************************** // LogSubscriptionFileWriter.cs - Gbtc // // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/eclipse-1.0.php // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 10/24/2016 - Steven E. Chisholm // Generated original version of source code. // // //****************************************************************************************************** using System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using GSF.IO; using GSF.Threading; namespace GSF.Diagnostics { /// /// A log subscription that will write messages to a file /// public sealed class LogSubscriptionFileWriter : IDisposable { /// /// When a new file has been completed. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")] public event Action NewFileComplete; private readonly object m_syncRoot; private readonly ConcurrentQueue m_messageQueue; // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable private readonly LogSubscriber m_subscriber; private readonly int m_maxQueue; private string m_path; private VerboseLevel m_verbose; private LogFileWriter m_writer; private int m_maxFileCount; private Stopwatch m_fileAge = new Stopwatch(); private ScheduledTask m_flushTask; //A number that increments every time the service has restarted. This will help to identify order and when the service last restarted private int m_fileSequenceNumber; private string m_processName; /// /// Creates a LogFileWriter that initially queues message /// /// the number of messages to maintain public LogSubscriptionFileWriter(int messageLimit) { m_processName = Process.GetCurrentProcess().ProcessName; m_fileSequenceNumber = 1; m_syncRoot = new object(); m_maxQueue = messageLimit; m_messageQueue = new ConcurrentQueue(); m_flushTask = new ScheduledTask(); m_flushTask.Running += m_flushTask_Running; m_verbose = VerboseLevel.High; m_subscriber = Logger.CreateSubscriber(m_verbose); m_subscriber.SubscribeToAll(m_verbose); m_subscriber.NewLogMessage += SubscriberNewLogMessage; } private void m_flushTask_Running(object sender, EventArgs e) { if (m_path == null) return; LogMessage message; bool hasWrittenLogMessage = false; while (m_messageQueue.TryDequeue(out message)) { WriteLogMessage(message); hasWrittenLogMessage = true; } if (hasWrittenLogMessage) m_writer?.Flush(); } /// /// Gets or sets verbosity level for this . /// public VerboseLevel Verbose { get { return m_verbose; } set { m_verbose = value; m_subscriber.SubscribeToAll(m_verbose); } } /// /// Sets the maximum number of log files before a new one will delete the oldest one. /// /// public void SetLoggingFileCount(int maxFileCount) { lock (m_syncRoot) { m_maxFileCount = maxFileCount; } } /// /// Sets the path of the log files. /// /// public void SetPath(string logDirectory) { FilePath.ValidatePathName(logDirectory); lock (m_syncRoot) { m_path = logDirectory; m_flushTask.Start(1000); } } /// /// Sets log file writer path and optionally its verbosity level. /// public void SetPath(string logDirectory, VerboseLevel level) { FilePath.ValidatePathName(logDirectory); lock (m_syncRoot) { Verbose = level; m_path = logDirectory; m_flushTask.Start(1000); } } private void SubscriberNewLogMessage(LogMessage logMessage) { lock (m_syncRoot) { if (m_path == null) { LogMessage message; if (m_messageQueue.Count >= m_maxQueue) m_messageQueue.TryDequeue(out message); m_messageQueue.Enqueue(logMessage); } else { m_messageQueue.Enqueue(logMessage); m_flushTask.Start(1000); } } } private void WriteLogMessage(LogMessage log) { //Rollover a file if over 500KB in size, over 50000 records, or older than 12 hours. //Ideally, 500KB will always come first. if (m_writer != null && (m_writer.LogSize > 500 * 1024 || m_writer.LogCount >= 50000 || m_fileAge.Elapsed.TotalHours > 12)) { string fileName = m_writer.FileName; m_writer.Dispose(); m_writer = null; OnNewFileComplete(fileName); } if (m_writer == null) { m_fileAge.Restart(); try { if (m_maxFileCount > 0) { string[] files = Directory.GetFiles(m_path, "*.logz"); if (files.Length > m_maxFileCount) { Array.Sort(files); for (int x = 0; x < files.Length - m_maxFileCount; x++) { File.Delete(files[x]); } } } } catch (Exception) { } string file = $"{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-ffffff")} - {m_processName} - {m_fileSequenceNumber}.logz"; m_fileSequenceNumber++; var fileName = Path.Combine(m_path, file); m_writer = new LogFileWriter(fileName); } m_writer.Write(log, false); } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// 2 public void Dispose() { m_flushTask.Dispose(); lock (m_syncRoot) { if (m_writer != null) { string fileName = m_writer.FileName; m_writer.Dispose(); m_writer = null; OnNewFileComplete(fileName); } } } private void OnNewFileComplete(string fileName) { var e = NewFileComplete; if (e != null) { try { e(fileName); } catch (Exception) { } } } } }