LogManager.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  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 = 0;
  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 question;
  526. if (q is null)
  527. question = "MISSING QUESTION!";
  528. else
  529. question = "QNAME: " + q.Name + "; QTYPE: " + q.Type.ToString() + "; QCLASS: " + q.Class;
  530. string responseInfo;
  531. if (response is null)
  532. {
  533. responseInfo = "; NO RESPONSE FROM SERVER!";
  534. }
  535. else
  536. {
  537. responseInfo = "; RCODE: " + response.RCODE.ToString();
  538. if ((response.Additional.Count > 0) && (response.Additional[response.Additional.Count - 1].Type == DnsResourceRecordType.TSIG))
  539. {
  540. if (response.Additional[response.Additional.Count - 1].RDATA is DnsTSIGRecordData tsig)
  541. responseInfo += "; TSIG: " + tsig.Error.ToString();
  542. }
  543. string answer;
  544. if (response.Answer.Count == 0)
  545. {
  546. if (response.Truncation)
  547. answer = "[TRUNCATED]";
  548. else
  549. answer = "[]";
  550. }
  551. else if ((response.Answer.Count > 2) && response.IsZoneTransfer)
  552. {
  553. answer = "[ZONE TRANSFER]";
  554. }
  555. else
  556. {
  557. answer = "[";
  558. for (int i = 0; i < response.Answer.Count; i++)
  559. {
  560. if (i > 0)
  561. answer += ", ";
  562. answer += response.Answer[i].RDATA.ToString();
  563. }
  564. answer += "]";
  565. if (response.Additional.Count > 0)
  566. {
  567. switch (q.Type)
  568. {
  569. case DnsResourceRecordType.NS:
  570. case DnsResourceRecordType.MX:
  571. case DnsResourceRecordType.SRV:
  572. answer += "; ADDITIONAL: [";
  573. for (int i = 0; i < response.Additional.Count; i++)
  574. {
  575. DnsResourceRecord additional = response.Additional[i];
  576. switch (additional.Type)
  577. {
  578. case DnsResourceRecordType.A:
  579. case DnsResourceRecordType.AAAA:
  580. if (i > 0)
  581. answer += ", ";
  582. answer += additional.Name + " (" + additional.RDATA.ToString() + ")";
  583. break;
  584. }
  585. }
  586. answer += "]";
  587. break;
  588. }
  589. }
  590. }
  591. EDnsClientSubnetOptionData responseECS = response.GetEDnsClientSubnetOption();
  592. if (responseECS is not null)
  593. answer += "; ECS: " + responseECS.Address.ToString() + "/" + responseECS.ScopePrefixLength;
  594. responseInfo += "; ANSWER: " + answer;
  595. }
  596. Write(ep, protocol, question + responseInfo);
  597. }
  598. public void Write(IPEndPoint ep, DnsTransportProtocol protocol, string message)
  599. {
  600. Write(ep, protocol.ToString(), message);
  601. }
  602. public void Write(IPEndPoint ep, string protocol, string message)
  603. {
  604. string ipInfo;
  605. if (ep == null)
  606. ipInfo = "";
  607. else if (ep.Address.IsIPv4MappedToIPv6)
  608. ipInfo = "[" + ep.Address.MapToIPv4().ToString() + ":" + ep.Port + "] ";
  609. else
  610. ipInfo = "[" + ep.ToString() + "] ";
  611. Write(ipInfo + "[" + protocol.ToUpper() + "] " + message);
  612. }
  613. public void Write(string message)
  614. {
  615. if (_enableLogging)
  616. _queue.Add(new LogQueueItem(message));
  617. }
  618. public void DeleteCurrentLogFile()
  619. {
  620. lock (_queueLock)
  621. {
  622. try
  623. {
  624. if (_logOut != null)
  625. _queueCancellationTokenSource.Cancel();
  626. lock (_logFileLock)
  627. {
  628. if (_logOut != null)
  629. _logOut.Dispose();
  630. File.Delete(_logFile);
  631. if (_enableLogging)
  632. StartNewLog();
  633. }
  634. }
  635. finally
  636. {
  637. _queueWait.Set();
  638. }
  639. }
  640. }
  641. public void SaveConfig()
  642. {
  643. lock (_saveLock)
  644. {
  645. if (_pendingSave)
  646. return;
  647. _pendingSave = true;
  648. _saveTimer.Change(SAVE_TIMER_INITIAL_INTERVAL, Timeout.Infinite);
  649. }
  650. }
  651. #endregion
  652. #region properties
  653. public bool EnableLogging
  654. {
  655. get { return _enableLogging; }
  656. set { _enableLogging = value; }
  657. }
  658. public string LogFolder
  659. {
  660. get { return _logFolder; }
  661. set
  662. {
  663. string logFolder;
  664. if (string.IsNullOrEmpty(value))
  665. logFolder = "logs";
  666. else
  667. logFolder = value;
  668. Directory.CreateDirectory(ConvertToAbsolutePath(logFolder));
  669. _logFolder = ConvertToRelativePath(logFolder);
  670. }
  671. }
  672. public int MaxLogFileDays
  673. {
  674. get { return _maxLogFileDays; }
  675. set
  676. {
  677. if (value < 0)
  678. throw new ArgumentOutOfRangeException(nameof(MaxLogFileDays), "MaxLogFileDays must be greater than or equal to 0.");
  679. _maxLogFileDays = value;
  680. if (_maxLogFileDays == 0)
  681. _logCleanupTimer.Change(Timeout.Infinite, Timeout.Infinite);
  682. else
  683. _logCleanupTimer.Change(LOG_CLEANUP_TIMER_INITIAL_INTERVAL, LOG_CLEANUP_TIMER_PERIODIC_INTERVAL);
  684. }
  685. }
  686. public bool UseLocalTime
  687. {
  688. get { return _useLocalTime; }
  689. set { _useLocalTime = value; }
  690. }
  691. public string CurrentLogFile
  692. { get { return _logFile; } }
  693. public string LogFolderAbsolutePath
  694. { get { return ConvertToAbsolutePath(_logFolder); } }
  695. #endregion
  696. class LogQueueItem
  697. {
  698. #region variables
  699. public readonly DateTime _dateTime;
  700. public readonly string _message;
  701. #endregion
  702. #region constructor
  703. public LogQueueItem(string message)
  704. {
  705. _dateTime = DateTime.UtcNow;
  706. _message = message;
  707. }
  708. #endregion
  709. }
  710. }
  711. }