using System; using System.Collections.Concurrent; using System.IO; using System.Text.RegularExpressions; using System.Threading; namespace DiIiS_NA.Core.Logging { public class FileTarget : LogTarget, IDisposable { private readonly string _fileName; private readonly string _filePath; private readonly string _fileTimestamp; private FileStream _fileStream; private StreamWriter _logStream; private int _fileIndex; private ConcurrentQueue TaskQueue; private Thread LoggerThread; /// Filename of the logfile. /// Minimum level of messages to emit /// Maximum level of messages to emit /// Include timestamps in log? /// Reset log file on application startup? public FileTarget(string fileName, Logger.Level minLevel, Logger.Level maxLevel, bool includeTimeStamps, string timeStampFormat, bool reset = false) { MinimumLevel = minLevel; MaximumLevel = maxLevel; IncludeTimeStamps = includeTimeStamps; TimeStampFormat = timeStampFormat; _fileName = fileName; _fileTimestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm"); _filePath = $"{LogConfig.Instance.LoggingRoot}/{_fileTimestamp}/{_fileName}"; _fileIndex = 0; if (!Directory.Exists(LogConfig.Instance.LoggingRoot)) // create logging directory if it does not exist yet. Directory.CreateDirectory(LogConfig.Instance.LoggingRoot); if (!Directory.Exists($"{LogConfig.Instance.LoggingRoot}/{_fileTimestamp}")) // create logging directory if it does not exist yet. Directory.CreateDirectory($"{LogConfig.Instance.LoggingRoot}/{_fileTimestamp}"); _fileStream = new FileStream(_filePath, reset ? FileMode.Create : FileMode.Append, FileAccess.Write, FileShare.Read); // init the file stream. _logStream = new StreamWriter(_fileStream) { AutoFlush = true }; // init the stream writer. TaskQueue = new ConcurrentQueue(); LoggerThread = new Thread(CheckQueue) { Name = "Logger", IsBackground = true }; LoggerThread.Start(); } public void CheckQueue() { while (true) { if (TaskQueue.TryDequeue(out var action)) action.Invoke(); Thread.Sleep(1); } } /// /// Replace the colors from AnsiColor so they do not appear in the log file. /// /// /// private string NoColors(string message) => Regex.Replace(message, @"\$\[\[?([\w\W\d\s_\-\/]+)\]\]?\$", "$1"); /// Log level. /// Source of the log message. /// Log message. public override void LogMessage(Logger.Level level, string logger, string message) { TaskQueue.Enqueue(() => { message = NoColors(message); var timeStamp = IncludeTimeStamps ? "[" + DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff") + "] " : ""; if (!_disposed) // make sure we're not disposed. { /* if (this._fileStream.Length >= 20971520) //20 MB limit { //System.IO.File.Move(this._filePath, string.Format("{0}.{1}", this._filePath, this._fileIndex)); //this._fileIndex++; this._fileStream = new FileStream(_filePath, FileMode.Create, FileAccess.Write, FileShare.Read); // init the file stream. this._logStream = new StreamWriter(this._fileStream) { AutoFlush = true }; // init the stream writer. } //*/ _logStream.WriteLine(level > Logger.Level.ChatMessage ? $"{timeStamp}[{level.ToString(),5}] [{logger}]: {message}" : $"{timeStamp}{message}"); } }); } /// Log level. /// Source of the log message. /// Log message. /// Exception to be included with log message. public override void LogException(Logger.Level level, string logger, string message, Exception exception) { TaskQueue.Enqueue(() => { message = NoColors(message); var timeStamp = IncludeTimeStamps ? "[" + DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff") + "] " : ""; if (!_disposed) // make sure we're not disposed. { _logStream.WriteLine( $"{timeStamp}[{level.ToString(),5}] [{logger}]: {message} - [Exception] {exception}"); } }); } #region de-ctor // IDisposable pattern: http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx private bool _disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); // Take object out the finalization queue to prevent finalization code for it from executing (~FileTarget). } private void Dispose(bool disposing) { if (_disposed) return; // if already disposed, just return if (disposing) // only dispose managed resources if we're called from directly or in-directly from user code. { _logStream.Close(); _logStream.Dispose(); _fileStream.Close(); _fileStream.Dispose(); } _logStream = null; _fileStream = null; _disposed = true; } ~FileTarget() { Dispose(false); } // finalizer called by the runtime. we should only dispose unmanaged objects and should NOT reference managed ones. #endregion } }