using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace VCommon.Logging { public class TextLogPool : IDisposable { private const int IntervalToCheckCloseInSec = 1; private bool _isDisposed; private readonly Dictionary _dictionary = new Dictionary(); private class Entry { public FileStream FileStream { get; set; } public DateTime LastWriteTime { get; set; } } public TextLogPool(int idleToCloseInMin = 10) { Init(idleToCloseInMin); } public bool AppendText(string path, string content) { if (_isDisposed) throw new ObjectDisposedException("Cannot access a disposed object."); Entry inst = null; try { inst = GetInstance(path); if (inst == null) return false; lock (inst) { inst.LastWriteTime = DateTime.Now; var buf = Encoding.UTF8.GetBytes(content); inst.FileStream.Write(buf, 0, buf.Length); inst.FileStream.Flush(); return true; } } catch (Exception exception) { Logger.Warn("TextLogPool write fail", new { path, FileStreamInstance = inst?.FileStream.GetHashCode().ToString("X4"), content, exception }); return false; } } public void Dispose() { _isDisposed = true; foreach (var entry in _dictionary.Values) entry.FileStream.Dispose(); _dictionary.Clear(); } private void Init(int idleToCloseInMin) { Task.Factory.StartNew(() => { while (false == _isDisposed) { lock (_dictionary) { var now = DateTime.Now; var toFree = _dictionary.Where(p => (now - p.Value.LastWriteTime).TotalMinutes > idleToCloseInMin) .ToArray(); foreach (var pair in toFree) FreeInstance(pair.Key); } Thread.Sleep(1000 * IntervalToCheckCloseInSec); } }); } private void FreeInstance(string path) { if (!_dictionary.TryGetValue(path, out var inst)) return; try { lock (inst) inst.FileStream.Dispose(); _dictionary.Remove(path); Logger.Info("TextLogPool entry freed", new { path, FileStreamInstance = inst.FileStream.GetHashCode().ToString("X4") }); } catch (Exception exception) { Logger.Warn($"TextLogPool on freeing entry", new { path, exception }); } } private Entry GetInstance(string path) { lock (_dictionary) { if (_dictionary.TryGetValue(path, out var inst)) return inst; try { var fs = File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read); var entry = new Entry { FileStream = fs, LastWriteTime = DateTime.Now }; Logger.Info("TextLogPool entry created", new { path, FileStreamInstance = fs.GetHashCode().ToString("X4") }); _dictionary[path] = entry; return entry; } catch (Exception exception) { Logger.Info("TextLogPool error on creating entry", new { path, exception }); return null; } } } } }