// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // The log service. // For now, this is just a simple logging service but we could provide support for a formal logging library later. // Also, we can consider providing the UI layer with more functional logging. (i.e levels, time/date, highlighting etc) // The Interop Classes are not very OO friendly, so this is going to be a static class. // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrakeWPF.Services.Logging { using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using HandBrake.ApplicationServices.Interop; using HandBrake.ApplicationServices.Interop.EventArgs; using ILog = Interfaces.ILog; using LogEventArgs = EventArgs.LogEventArgs; using LogLevel = Model.LogLevel; using LogMessage = Model.LogMessage; using LogMessageType = Model.LogMessageType; /// /// The log helper. /// public class LogService : ILog { // TODO List. // Maybe make the event weak? // Make this class Thread Safe. private static ILog loggerInstance; private readonly object lockObject = new object(); private readonly object fileWriterLock = new object(); private readonly StringBuilder logBuilder = new StringBuilder(); private LogLevel currentLogLevel = LogLevel.Error; private bool isLoggingEnabled; private List logMessages = new List(); private long messageIndex; private string diskLogPath; private bool deleteLogFirst; private bool isDiskLoggingEnabled; private StreamWriter fileWriter; private string logHeader; public LogService() { HandBrakeUtils.MessageLogged += this.HandBrakeUtils_MessageLogged; HandBrakeUtils.ErrorLogged += this.HandBrakeUtils_ErrorLogged; } /// /// Fires when a new QueueTask starts /// public event EventHandler MessageLogged; /// /// The log reset event /// public event EventHandler LogReset; /// /// Gets the log messages. /// public IEnumerable LogMessages { get { lock (this.lockObject) { return this.logMessages.ToList(); } } } /// /// Gets the Activity Log as a string. /// public string ActivityLog { get { lock (this.lockObject) { return this.logBuilder.ToString(); } } } /// /// Log message. /// /// /// The content. /// /// /// The type. /// /// /// The level. /// public void LogMessage(string content, LogMessageType type, LogLevel level) { if (!this.isLoggingEnabled) { return; } if (level > this.currentLogLevel) { return; } LogMessage msg = new LogMessage(content, type, level, this.messageIndex); lock (this.lockObject) { this.messageIndex = this.messageIndex + 1; this.logMessages.Add(msg); this.logBuilder.AppendLine(msg.Content); this.LogMessageToDisk(msg); if (this.logMessages.Count > 50000) { this.messageIndex = this.messageIndex + 1; msg = new LogMessage( "Log Service Pausing. Too Many Log messages. This may indicate a problem with your encode.", LogMessageType.Application, LogLevel.Error, this.messageIndex); this.logMessages.Add(msg); this.logBuilder.AppendLine(msg.Content); this.LogMessageToDisk(msg); this.Disable(); } } this.OnMessageLogged(msg); // Must be outside lock to be thread safe. } /// /// Gets an shared instance of the logger. Logging is enabled by default /// You can turn it off by calling Disable() if you don't want it. /// /// /// An instance of this logger. /// public static ILog GetLogger() { return loggerInstance ?? (loggerInstance = new LogService()); } /// /// The set log level. Default: Info. /// /// /// The level. /// public void SetLogLevel(LogLevel level) { this.currentLogLevel = level; } /// /// The enable. /// public void Enable() { this.isLoggingEnabled = true; } /// /// Enable Logging to Disk /// /// /// The log file to write to. /// /// /// Delete the current log file if it exists. /// public void EnableLoggingToDisk(string logFile, bool deleteCurrentLogFirst) { if (this.isDiskLoggingEnabled) { throw new Exception("Disk Logging already enabled!"); } try { if (!Directory.Exists(Path.GetDirectoryName(logFile))) { throw new Exception("Log Directory does not exist. This service will not create it for you!"); } if (deleteCurrentLogFirst && File.Exists(logFile)) { File.Delete(logFile); } this.diskLogPath = logFile; this.isDiskLoggingEnabled = true; this.deleteLogFirst = deleteCurrentLogFirst; lock (this.fileWriterLock) { this.fileWriter = new StreamWriter(logFile) { AutoFlush = true }; } } catch (Exception exc) { this.LogMessage("Failed to Initialise Disk Logging. " + Environment.NewLine + exc, LogMessageType.Application, LogLevel.Error); if (this.fileWriter != null) { lock (this.fileWriterLock) { this.fileWriter.Flush(); this.fileWriter.Close(); this.fileWriter.Dispose(); } } } } /// /// The setup log header. /// /// /// The header. /// public void SetupLogHeader(string header) { this.logHeader = header; this.LogMessage(header, LogMessageType.Application, LogLevel.Info); } /// /// The disable. /// public void Disable() { this.isLoggingEnabled = false; } /// /// Clear the log messages collection. /// public void Reset() { lock (this.lockObject) { this.logMessages.Clear(); this.logBuilder.Clear(); this.messageIndex = 0; try { lock (this.fileWriterLock) { if (this.fileWriter != null) { this.fileWriter.Flush(); this.fileWriter.Close(); this.fileWriter.Dispose(); } this.fileWriter = null; } } catch (Exception exc) { Debug.WriteLine(exc); } if (this.fileWriter == null) { this.isDiskLoggingEnabled = false; this.EnableLoggingToDisk(this.diskLogPath, this.deleteLogFirst); } if (!string.IsNullOrEmpty(this.logHeader)) { this.SetupLogHeader(this.logHeader); } this.OnLogReset(); } } /// /// Called when a log message is created. /// /// /// The Log Message /// protected virtual void OnMessageLogged(LogMessage msg) { var onMessageLogged = this.MessageLogged; if (onMessageLogged != null) { onMessageLogged.Invoke(this, new LogEventArgs(msg)); } } /// /// Shutdown and Dispose of the File Writer. /// protected void ShutdownFileWriter() { try { lock (this.fileWriterLock) { if (this.fileWriter != null) { this.fileWriter.Flush(); this.fileWriter.Close(); this.fileWriter.Dispose(); } this.fileWriter = null; } } catch (Exception exc) { Debug.WriteLine(exc); // This exception doesn't warrant user interaction, but it should be logged } } /// /// Trigger the Event to notify any subscribers that the log has been reset. /// protected virtual void OnLogReset() { this.LogReset?.Invoke(this, System.EventArgs.Empty); } /// /// Helper method for logging content to disk /// /// /// Log message to write. /// private void LogMessageToDisk(LogMessage msg) { if (!this.isDiskLoggingEnabled) { return; } try { lock (this.fileWriterLock) { if (this.fileWriter != null && this.fileWriter.BaseStream.CanWrite) { this.fileWriter.WriteLine(msg.Content); } } } catch (Exception exc) { Debug.WriteLine(exc); // This exception doesn't warrant user interaction, but it should be logged } } private void HandBrakeUtils_ErrorLogged(object sender, MessageLoggedEventArgs e) { if (e == null || string.IsNullOrEmpty(e.Message)) { return; } this.LogMessage(e.Message, LogMessageType.ScanOrEncode, LogLevel.Error); } private void HandBrakeUtils_MessageLogged(object sender, MessageLoggedEventArgs e) { if (e == null || string.IsNullOrEmpty(e.Message)) { return; } this.LogMessage(e.Message, LogMessageType.ScanOrEncode, LogLevel.Info); } } }