TextLogPool.cs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. namespace VCommon.Logging
  9. {
  10. public class TextLogPool : IDisposable
  11. {
  12. private const int IntervalToCheckCloseInSec = 1;
  13. private bool _isDisposed;
  14. private readonly Dictionary<string, Entry> _dictionary = new Dictionary<string, Entry>();
  15. private class Entry
  16. {
  17. public FileStream FileStream { get; set; }
  18. public DateTime LastWriteTime { get; set; }
  19. }
  20. public TextLogPool(int idleToCloseInMin = 10)
  21. {
  22. Init(idleToCloseInMin);
  23. }
  24. public bool AppendText(string path, string content)
  25. {
  26. if (_isDisposed) throw new ObjectDisposedException("Cannot access a disposed object.");
  27. Entry inst = null;
  28. try
  29. {
  30. inst = GetInstance(path);
  31. if (inst == null) return false;
  32. lock (inst)
  33. {
  34. inst.LastWriteTime = DateTime.Now;
  35. var buf = Encoding.UTF8.GetBytes(content);
  36. inst.FileStream.Write(buf, 0, buf.Length);
  37. inst.FileStream.Flush();
  38. return true;
  39. }
  40. }
  41. catch (Exception exception)
  42. {
  43. Logger.Warn("TextLogPool write fail", new { path, FileStreamInstance = inst?.FileStream.GetHashCode().ToString("X4"), content, exception });
  44. return false;
  45. }
  46. }
  47. public void Dispose()
  48. {
  49. _isDisposed = true;
  50. foreach (var entry in _dictionary.Values) entry.FileStream.Dispose();
  51. _dictionary.Clear();
  52. }
  53. private void Init(int idleToCloseInMin)
  54. {
  55. Task.Factory.StartNew(() =>
  56. {
  57. while (false == _isDisposed)
  58. {
  59. lock (_dictionary)
  60. {
  61. var now = DateTime.Now;
  62. var toFree = _dictionary.Where(p => (now - p.Value.LastWriteTime).TotalMinutes > idleToCloseInMin)
  63. .ToArray();
  64. foreach (var pair in toFree) FreeInstance(pair.Key);
  65. }
  66. Thread.Sleep(1000 * IntervalToCheckCloseInSec);
  67. }
  68. });
  69. }
  70. private void FreeInstance(string path)
  71. {
  72. if (!_dictionary.TryGetValue(path, out var inst)) return;
  73. try
  74. {
  75. lock (inst) inst.FileStream.Dispose();
  76. _dictionary.Remove(path);
  77. Logger.Info("TextLogPool entry freed", new { path, FileStreamInstance = inst.FileStream.GetHashCode().ToString("X4") });
  78. }
  79. catch (Exception exception)
  80. {
  81. Logger.Warn($"TextLogPool on freeing entry", new { path, exception });
  82. }
  83. }
  84. private Entry GetInstance(string path)
  85. {
  86. lock (_dictionary)
  87. {
  88. if (_dictionary.TryGetValue(path, out var inst)) return inst;
  89. try
  90. {
  91. var fs = File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
  92. var entry = new Entry { FileStream = fs, LastWriteTime = DateTime.Now };
  93. Logger.Info("TextLogPool entry created", new { path, FileStreamInstance = fs.GetHashCode().ToString("X4") });
  94. _dictionary[path] = entry;
  95. return entry;
  96. }
  97. catch (Exception exception)
  98. {
  99. Logger.Info("TextLogPool error on creating entry", new { path, exception });
  100. return null;
  101. }
  102. }
  103. }
  104. }
  105. }