LogManager.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using Microsoft.AspNetCore.Http;
  16. using System;
  17. using System.Collections.Concurrent;
  18. using System.Globalization;
  19. using System.IO;
  20. using System.IO.Compression;
  21. using System.Linq;
  22. using System.Net;
  23. using System.Text;
  24. using System.Threading;
  25. using System.Threading.Tasks;
  26. using TechnitiumLibrary.IO;
  27. using TechnitiumLibrary.Net.Dns;
  28. using TechnitiumLibrary.Net.Dns.EDnsOptions;
  29. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  30. namespace DnsServerCore
  31. {
  32. public sealed class LogManager : IDisposable
  33. {
  34. #region variables
  35. static readonly char[] commaSeparator = new char[] { ',' };
  36. readonly string _configFolder;
  37. bool _enableLogging;
  38. string _logFolder;
  39. int _maxLogFileDays;
  40. bool _useLocalTime;
  41. const string LOG_ENTRY_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
  42. const string LOG_FILE_DATE_TIME_FORMAT = "yyyy-MM-dd";
  43. string _logFile;
  44. StreamWriter _logOut;
  45. DateTime _logDate;
  46. readonly BlockingCollection<LogQueueItem> _queue = new BlockingCollection<LogQueueItem>();
  47. Thread _consumerThread;
  48. readonly object _logFileLock = new object();
  49. readonly object _queueLock = new object();
  50. readonly AutoResetEvent _queueWait = new AutoResetEvent(false);
  51. CancellationTokenSource _queueCancellationTokenSource = new CancellationTokenSource();
  52. readonly Timer _logCleanupTimer;
  53. const int LOG_CLEANUP_TIMER_INITIAL_INTERVAL = 60 * 1000;
  54. const int LOG_CLEANUP_TIMER_PERIODIC_INTERVAL = 60 * 60 * 1000;
  55. readonly object _saveLock = new object();
  56. bool _pendingSave;
  57. readonly Timer _saveTimer;
  58. const int SAVE_TIMER_INITIAL_INTERVAL = 10000;
  59. #endregion
  60. #region constructor
  61. public LogManager(string configFolder)
  62. {
  63. _configFolder = configFolder;
  64. AppDomain.CurrentDomain.UnhandledException += delegate (object sender, UnhandledExceptionEventArgs e)
  65. {
  66. if (!_enableLogging)
  67. {
  68. Console.WriteLine(e.ExceptionObject.ToString());
  69. return;
  70. }
  71. lock (_queueLock)
  72. {
  73. try
  74. {
  75. _queueCancellationTokenSource.Cancel();
  76. lock (_logFileLock)
  77. {
  78. if (_logOut != null)
  79. WriteLog(DateTime.UtcNow, e.ExceptionObject.ToString());
  80. }
  81. }
  82. catch (ObjectDisposedException)
  83. { }
  84. catch (Exception ex)
  85. {
  86. Console.WriteLine(e.ExceptionObject.ToString());
  87. Console.WriteLine(ex.ToString());
  88. }
  89. finally
  90. {
  91. _queueWait.Set();
  92. }
  93. }
  94. };
  95. _logCleanupTimer = new Timer(delegate (object state)
  96. {
  97. try
  98. {
  99. if (_maxLogFileDays < 1)
  100. return;
  101. DateTime cutoffDate = DateTime.UtcNow.AddDays(_maxLogFileDays * -1).Date;
  102. DateTimeStyles dateTimeStyles;
  103. if (_useLocalTime)
  104. dateTimeStyles = DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal;
  105. else
  106. dateTimeStyles = DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal;
  107. foreach (string logFile in ListLogFiles())
  108. {
  109. string logFileName = Path.GetFileNameWithoutExtension(logFile);
  110. if (!DateTime.TryParseExact(logFileName, LOG_FILE_DATE_TIME_FORMAT, CultureInfo.InvariantCulture, dateTimeStyles, out DateTime logFileDate))
  111. continue;
  112. if (logFileDate < cutoffDate)
  113. {
  114. try
  115. {
  116. File.Delete(logFile);
  117. Write("LogManager cleanup deleted the log file: " + logFile);
  118. }
  119. catch (Exception ex)
  120. {
  121. Write(ex);
  122. }
  123. }
  124. }
  125. }
  126. catch (Exception ex)
  127. {
  128. Write(ex);
  129. }
  130. });
  131. LoadConfig();
  132. if (_enableLogging)
  133. StartLogging();
  134. _saveTimer = new Timer(delegate (object state)
  135. {
  136. lock (_saveLock)
  137. {
  138. if (_pendingSave)
  139. {
  140. try
  141. {
  142. SaveConfigInternal();
  143. _pendingSave = false;
  144. }
  145. catch (Exception ex)
  146. {
  147. Write(ex);
  148. //set timer to retry again
  149. _saveTimer.Change(SAVE_TIMER_INITIAL_INTERVAL, Timeout.Infinite);
  150. }
  151. }
  152. }
  153. });
  154. }
  155. #endregion
  156. #region IDisposable
  157. bool _disposed;
  158. private void Dispose(bool disposing)
  159. {
  160. lock (_saveLock)
  161. {
  162. _saveTimer?.Dispose();
  163. if (_pendingSave)
  164. {
  165. try
  166. {
  167. SaveConfigFileInternal();
  168. }
  169. catch (Exception ex)
  170. {
  171. Write(ex);
  172. }
  173. finally
  174. {
  175. _pendingSave = false;
  176. }
  177. }
  178. }
  179. lock (_queueLock)
  180. {
  181. try
  182. {
  183. _queueCancellationTokenSource.Cancel();
  184. lock (_logFileLock)
  185. {
  186. if (_disposed)
  187. return;
  188. if (disposing)
  189. {
  190. if (_logOut != null)
  191. {
  192. WriteLog(DateTime.UtcNow, "Logging stopped.");
  193. _logOut.Dispose();
  194. }
  195. _logCleanupTimer.Dispose();
  196. }
  197. _disposed = true;
  198. }
  199. }
  200. finally
  201. {
  202. _queueWait.Set();
  203. }
  204. }
  205. }
  206. public void Dispose()
  207. {
  208. Dispose(true);
  209. }
  210. #endregion
  211. #region private
  212. internal void StartLogging()
  213. {
  214. StartNewLog();
  215. _queueWait.Set();
  216. //start consumer thread
  217. _consumerThread = new Thread(delegate ()
  218. {
  219. while (true)
  220. {
  221. _queueWait.WaitOne();
  222. Monitor.Enter(_logFileLock);
  223. try
  224. {
  225. if (_disposed || (_logOut == null))
  226. break;
  227. foreach (LogQueueItem item in _queue.GetConsumingEnumerable(_queueCancellationTokenSource.Token))
  228. {
  229. if (_useLocalTime)
  230. {
  231. DateTime messageLocalDateTime = item._dateTime.ToLocalTime();
  232. if (messageLocalDateTime.Date > _logDate)
  233. {
  234. WriteLog(DateTime.UtcNow, "Logging stopped.");
  235. StartNewLog();
  236. }
  237. WriteLog(messageLocalDateTime, item._message);
  238. }
  239. else
  240. {
  241. if (item._dateTime.Date > _logDate)
  242. {
  243. WriteLog(DateTime.UtcNow, "Logging stopped.");
  244. StartNewLog();
  245. }
  246. WriteLog(item._dateTime, item._message);
  247. }
  248. }
  249. }
  250. catch (ObjectDisposedException)
  251. { }
  252. catch (OperationCanceledException)
  253. { }
  254. finally
  255. {
  256. Monitor.Exit(_logFileLock);
  257. }
  258. _queueCancellationTokenSource = new CancellationTokenSource();
  259. }
  260. });
  261. _consumerThread.Name = "Log";
  262. _consumerThread.IsBackground = true;
  263. _consumerThread.Start();
  264. }
  265. internal void StopLogging()
  266. {
  267. lock (_queueLock)
  268. {
  269. try
  270. {
  271. if (_logOut != null)
  272. _queueCancellationTokenSource.Cancel();
  273. lock (_logFileLock)
  274. {
  275. if (_logOut != null)
  276. {
  277. WriteLog(DateTime.UtcNow, "Logging stopped.");
  278. _logOut.Dispose();
  279. _logOut = null; //to stop consumer thread
  280. }
  281. }
  282. }
  283. finally
  284. {
  285. _queueWait.Set();
  286. }
  287. }
  288. }
  289. internal void LoadConfig()
  290. {
  291. string logConfigFile = Path.Combine(_configFolder, "log.config");
  292. try
  293. {
  294. using (FileStream fS = new FileStream(logConfigFile, FileMode.Open, FileAccess.Read))
  295. {
  296. BinaryReader bR = new BinaryReader(fS);
  297. if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "LS") //format
  298. throw new InvalidDataException("DnsServer log config file format is invalid.");
  299. byte version = bR.ReadByte();
  300. switch (version)
  301. {
  302. case 1:
  303. _enableLogging = bR.ReadBoolean();
  304. _logFolder = bR.ReadShortString();
  305. _maxLogFileDays = bR.ReadInt32();
  306. _useLocalTime = bR.ReadBoolean();
  307. break;
  308. default:
  309. throw new InvalidDataException("DnsServer log config version not supported.");
  310. }
  311. }
  312. }
  313. catch (FileNotFoundException)
  314. {
  315. _enableLogging = true;
  316. _logFolder = "logs";
  317. _maxLogFileDays = 365;
  318. _useLocalTime = false;
  319. SaveConfigFileInternal();
  320. }
  321. catch (Exception ex)
  322. {
  323. Console.Write(ex.ToString());
  324. SaveConfigFileInternal();
  325. }
  326. if (_maxLogFileDays == 0)
  327. _logCleanupTimer.Change(Timeout.Infinite, Timeout.Infinite);
  328. else
  329. _logCleanupTimer.Change(LOG_CLEANUP_TIMER_INITIAL_INTERVAL, LOG_CLEANUP_TIMER_PERIODIC_INTERVAL);
  330. }
  331. private string ConvertToRelativePath(string path)
  332. {
  333. if (path.StartsWith(_configFolder, Environment.OSVersion.Platform == PlatformID.Win32NT ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
  334. path = path.Substring(_configFolder.Length).TrimStart(Path.DirectorySeparatorChar);
  335. return path;
  336. }
  337. private string ConvertToAbsolutePath(string path)
  338. {
  339. if (Path.IsPathRooted(path))
  340. return path;
  341. return Path.Combine(_configFolder, path);
  342. }
  343. private void SaveConfigFileInternal()
  344. {
  345. string logConfigFile = Path.Combine(_configFolder, "log.config");
  346. using (MemoryStream mS = new MemoryStream())
  347. {
  348. //serialize config
  349. BinaryWriter bW = new BinaryWriter(mS);
  350. bW.Write(Encoding.ASCII.GetBytes("LS")); //format
  351. bW.Write((byte)1); //version
  352. bW.Write(_enableLogging);
  353. bW.WriteShortString(_logFolder);
  354. bW.Write(_maxLogFileDays);
  355. bW.Write(_useLocalTime);
  356. //write config
  357. mS.Position = 0;
  358. using (FileStream fS = new FileStream(logConfigFile, FileMode.Create, FileAccess.Write))
  359. {
  360. mS.CopyTo(fS);
  361. }
  362. }
  363. }
  364. private void SaveConfigInternal()
  365. {
  366. SaveConfigFileInternal();
  367. if (_logOut is null)
  368. {
  369. //stopped
  370. if (_enableLogging)
  371. StartLogging();
  372. }
  373. else
  374. {
  375. //running
  376. if (!_enableLogging)
  377. {
  378. StopLogging();
  379. }
  380. else if (!_logFile.StartsWith(ConvertToAbsolutePath(_logFolder)))
  381. {
  382. //log folder changed; restart logging to new folder
  383. StopLogging();
  384. StartLogging();
  385. }
  386. }
  387. }
  388. private void StartNewLog()
  389. {
  390. if (_logOut != null)
  391. _logOut.Dispose();
  392. string logFolder = ConvertToAbsolutePath(_logFolder);
  393. if (!Directory.Exists(logFolder))
  394. Directory.CreateDirectory(logFolder);
  395. DateTime logStartDateTime;
  396. if (_useLocalTime)
  397. logStartDateTime = DateTime.Now;
  398. else
  399. logStartDateTime = DateTime.UtcNow;
  400. _logFile = Path.Combine(logFolder, logStartDateTime.ToString(LOG_FILE_DATE_TIME_FORMAT) + ".log");
  401. _logOut = new StreamWriter(new FileStream(_logFile, FileMode.Append, FileAccess.Write, FileShare.Read));
  402. _logDate = logStartDateTime.Date;
  403. WriteLog(logStartDateTime, "Logging started.");
  404. }
  405. private void WriteLog(DateTime dateTime, string message)
  406. {
  407. if (_useLocalTime)
  408. {
  409. if (dateTime.Kind == DateTimeKind.Local)
  410. _logOut.WriteLine("[" + dateTime.ToString(LOG_ENTRY_DATE_TIME_FORMAT) + " Local] " + message);
  411. else
  412. _logOut.WriteLine("[" + dateTime.ToLocalTime().ToString(LOG_ENTRY_DATE_TIME_FORMAT) + " Local] " + message);
  413. }
  414. else
  415. {
  416. if (dateTime.Kind == DateTimeKind.Utc)
  417. _logOut.WriteLine("[" + dateTime.ToString(LOG_ENTRY_DATE_TIME_FORMAT) + " UTC] " + message);
  418. else
  419. _logOut.WriteLine("[" + dateTime.ToUniversalTime().ToString(LOG_ENTRY_DATE_TIME_FORMAT) + " UTC] " + message);
  420. }
  421. _logOut.Flush();
  422. }
  423. #endregion
  424. #region public
  425. public string[] ListLogFiles()
  426. {
  427. return Directory.GetFiles(ConvertToAbsolutePath(_logFolder), "*.log", SearchOption.TopDirectoryOnly);
  428. }
  429. public async Task DownloadLogAsync(HttpContext context, string logName, long limit)
  430. {
  431. string logFileName = logName + ".log";
  432. using (FileStream fS = new FileStream(Path.Combine(ConvertToAbsolutePath(_logFolder), logFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 64 * 1024, true))
  433. {
  434. HttpResponse response = context.Response;
  435. response.ContentType = "text/plain";
  436. response.Headers.ContentDisposition = "attachment;filename=" + logFileName;
  437. if ((limit > fS.Length) || (limit < 1))
  438. limit = fS.Length;
  439. OffsetStream oFS = new OffsetStream(fS, 0, limit);
  440. HttpRequest request = context.Request;
  441. Stream s;
  442. string acceptEncoding = request.Headers.AcceptEncoding;
  443. if (string.IsNullOrEmpty(acceptEncoding))
  444. {
  445. s = response.Body;
  446. }
  447. else
  448. {
  449. string[] acceptEncodingParts = acceptEncoding.Split(commaSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  450. if (acceptEncodingParts.Contains("br"))
  451. {
  452. response.Headers.ContentEncoding = "br";
  453. s = new BrotliStream(response.Body, CompressionMode.Compress);
  454. }
  455. else if (acceptEncodingParts.Contains("gzip"))
  456. {
  457. response.Headers.ContentEncoding = "gzip";
  458. s = new GZipStream(response.Body, CompressionMode.Compress);
  459. }
  460. else if (acceptEncodingParts.Contains("deflate"))
  461. {
  462. response.Headers.ContentEncoding = "deflate";
  463. s = new DeflateStream(response.Body, CompressionMode.Compress);
  464. }
  465. else
  466. {
  467. s = response.Body;
  468. }
  469. }
  470. await using (s)
  471. {
  472. await oFS.CopyToAsync(s);
  473. if (fS.Length > limit)
  474. await s.WriteAsync(Encoding.UTF8.GetBytes("\r\n####___TRUNCATED___####"));
  475. }
  476. }
  477. }
  478. public void DeleteLog(string logName)
  479. {
  480. string logFile = Path.Combine(ConvertToAbsolutePath(_logFolder), logName + ".log");
  481. if (logFile.Equals(_logFile, StringComparison.OrdinalIgnoreCase))
  482. DeleteCurrentLogFile();
  483. else
  484. File.Delete(logFile);
  485. }
  486. public void DeleteAllLogs()
  487. {
  488. string[] logFiles = ListLogFiles();
  489. foreach (string logFile in logFiles)
  490. {
  491. if (logFile.Equals(_logFile, StringComparison.OrdinalIgnoreCase))
  492. DeleteCurrentLogFile();
  493. else
  494. File.Delete(logFile);
  495. }
  496. }
  497. public void Write(Exception ex)
  498. {
  499. Write(ex.ToString());
  500. }
  501. public void Write(IPEndPoint ep, Exception ex)
  502. {
  503. Write(ep, ex.ToString());
  504. }
  505. public void Write(IPEndPoint ep, string message)
  506. {
  507. string ipInfo;
  508. if (ep == null)
  509. ipInfo = "";
  510. else if (ep.Address.IsIPv4MappedToIPv6)
  511. ipInfo = "[" + ep.Address.MapToIPv4().ToString() + ":" + ep.Port + "] ";
  512. else
  513. ipInfo = "[" + ep.ToString() + "] ";
  514. Write(ipInfo + message);
  515. }
  516. public void Write(IPEndPoint ep, DnsTransportProtocol protocol, Exception ex)
  517. {
  518. Write(ep, protocol, ex.ToString());
  519. }
  520. public void Write(IPEndPoint ep, DnsTransportProtocol protocol, DnsDatagram request, DnsDatagram response)
  521. {
  522. DnsQuestionRecord q = null;
  523. if (request.Question.Count > 0)
  524. q = request.Question[0];
  525. string requestInfo;
  526. if (q is null)
  527. requestInfo = "MISSING QUESTION!";
  528. else
  529. requestInfo = "QNAME: " + q.Name + "; QTYPE: " + q.Type.ToString() + "; QCLASS: " + q.Class;
  530. if (request.Additional.Count > 0)
  531. {
  532. DnsResourceRecord lastRR = request.Additional[request.Additional.Count - 1];
  533. if ((lastRR.Type == DnsResourceRecordType.TSIG) && (lastRR.RDATA is DnsTSIGRecordData tsig))
  534. requestInfo += "; TSIG KeyName: " + lastRR.Name.ToLowerInvariant() + "; TSIG Algo: " + tsig.AlgorithmName + "; TSIG Error: " + tsig.Error.ToString();
  535. }
  536. string responseInfo;
  537. if (response is null)
  538. {
  539. responseInfo = "; NO RESPONSE FROM SERVER!";
  540. }
  541. else
  542. {
  543. responseInfo = "; RCODE: " + response.RCODE.ToString();
  544. string answer;
  545. if (response.Answer.Count == 0)
  546. {
  547. if (response.Truncation)
  548. answer = "[TRUNCATED]";
  549. else
  550. answer = "[]";
  551. }
  552. else if ((response.Answer.Count > 2) && response.IsZoneTransfer)
  553. {
  554. answer = "[ZONE TRANSFER]";
  555. }
  556. else
  557. {
  558. answer = "[";
  559. for (int i = 0; i < response.Answer.Count; i++)
  560. {
  561. if (i > 0)
  562. answer += ", ";
  563. answer += response.Answer[i].RDATA.ToString();
  564. }
  565. answer += "]";
  566. if (response.Additional.Count > 0)
  567. {
  568. switch (q.Type)
  569. {
  570. case DnsResourceRecordType.NS:
  571. case DnsResourceRecordType.MX:
  572. case DnsResourceRecordType.SRV:
  573. answer += "; ADDITIONAL: [";
  574. for (int i = 0; i < response.Additional.Count; i++)
  575. {
  576. DnsResourceRecord additional = response.Additional[i];
  577. switch (additional.Type)
  578. {
  579. case DnsResourceRecordType.A:
  580. case DnsResourceRecordType.AAAA:
  581. if (i > 0)
  582. answer += ", ";
  583. answer += additional.Name + " (" + additional.RDATA.ToString() + ")";
  584. break;
  585. }
  586. }
  587. answer += "]";
  588. break;
  589. }
  590. }
  591. }
  592. EDnsClientSubnetOptionData responseECS = response.GetEDnsClientSubnetOption();
  593. if (responseECS is not null)
  594. answer += "; ECS: " + responseECS.Address.ToString() + "/" + responseECS.ScopePrefixLength;
  595. responseInfo += "; ANSWER: " + answer;
  596. }
  597. Write(ep, protocol, requestInfo + responseInfo);
  598. }
  599. public void Write(IPEndPoint ep, DnsTransportProtocol protocol, string message)
  600. {
  601. Write(ep, protocol.ToString(), message);
  602. }
  603. public void Write(IPEndPoint ep, string protocol, string message)
  604. {
  605. string ipInfo;
  606. if (ep == null)
  607. ipInfo = "";
  608. else if (ep.Address.IsIPv4MappedToIPv6)
  609. ipInfo = "[" + ep.Address.MapToIPv4().ToString() + ":" + ep.Port + "] ";
  610. else
  611. ipInfo = "[" + ep.ToString() + "] ";
  612. Write(ipInfo + "[" + protocol.ToUpper() + "] " + message);
  613. }
  614. public void Write(string message)
  615. {
  616. if (_enableLogging)
  617. _queue.Add(new LogQueueItem(message));
  618. }
  619. public void DeleteCurrentLogFile()
  620. {
  621. lock (_queueLock)
  622. {
  623. try
  624. {
  625. if (_logOut != null)
  626. _queueCancellationTokenSource.Cancel();
  627. lock (_logFileLock)
  628. {
  629. if (_logOut != null)
  630. _logOut.Dispose();
  631. File.Delete(_logFile);
  632. if (_enableLogging)
  633. StartNewLog();
  634. }
  635. }
  636. finally
  637. {
  638. _queueWait.Set();
  639. }
  640. }
  641. }
  642. public void SaveConfig()
  643. {
  644. lock (_saveLock)
  645. {
  646. if (_pendingSave)
  647. return;
  648. _pendingSave = true;
  649. _saveTimer.Change(SAVE_TIMER_INITIAL_INTERVAL, Timeout.Infinite);
  650. }
  651. }
  652. #endregion
  653. #region properties
  654. public bool EnableLogging
  655. {
  656. get { return _enableLogging; }
  657. set { _enableLogging = value; }
  658. }
  659. public string LogFolder
  660. {
  661. get { return _logFolder; }
  662. set
  663. {
  664. string logFolder;
  665. if (string.IsNullOrEmpty(value))
  666. logFolder = "logs";
  667. else if (value.Length > 255)
  668. throw new ArgumentException("Log folder path length cannot exceed 255 characters.", nameof(LogFolder));
  669. else
  670. logFolder = value;
  671. Directory.CreateDirectory(ConvertToAbsolutePath(logFolder));
  672. _logFolder = ConvertToRelativePath(logFolder);
  673. }
  674. }
  675. public int MaxLogFileDays
  676. {
  677. get { return _maxLogFileDays; }
  678. set
  679. {
  680. if (value < 0)
  681. throw new ArgumentOutOfRangeException(nameof(MaxLogFileDays), "MaxLogFileDays must be greater than or equal to 0.");
  682. _maxLogFileDays = value;
  683. if (_maxLogFileDays == 0)
  684. _logCleanupTimer.Change(Timeout.Infinite, Timeout.Infinite);
  685. else
  686. _logCleanupTimer.Change(LOG_CLEANUP_TIMER_INITIAL_INTERVAL, LOG_CLEANUP_TIMER_PERIODIC_INTERVAL);
  687. }
  688. }
  689. public bool UseLocalTime
  690. {
  691. get { return _useLocalTime; }
  692. set { _useLocalTime = value; }
  693. }
  694. public string CurrentLogFile
  695. { get { return _logFile; } }
  696. public string LogFolderAbsolutePath
  697. { get { return ConvertToAbsolutePath(_logFolder); } }
  698. #endregion
  699. class LogQueueItem
  700. {
  701. #region variables
  702. public readonly DateTime _dateTime;
  703. public readonly string _message;
  704. #endregion
  705. #region constructor
  706. public LogQueueItem(string message)
  707. {
  708. _dateTime = DateTime.UtcNow;
  709. _message = message;
  710. }
  711. #endregion
  712. }
  713. }
  714. }