// --------------------------------------------------------------------------------------------------------------------
//
// 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);
}
}
}