LogConfig.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Text;
  5. using System.Xml;
  6. using System.Xml.Schema;
  7. namespace Wayne.Lib.Log
  8. {
  9. /// <summary>
  10. /// The Log config handles the application-wide setup for the logging. It is read from a
  11. /// XML file that should be in accordance with the LogConfig.xsd schema.
  12. /// </summary>
  13. internal class LogConfig : IDisposable
  14. {
  15. #region Private Class LogConfigWrapper
  16. private class LogConfigWrapper
  17. {
  18. public LogConfigWrapper(LogConfig logConfig, XmlNode logConfigNode, string ns)
  19. {
  20. LogConfigBuilder = new LogConfigBuilder(logConfigNode, ns);
  21. foreach (LogConfigOutput output in LogConfigBuilder.Outputs)
  22. LogWriters.Add(logConfig.GetLogWriter(LogConfigBuilder.Name, output));
  23. }
  24. public readonly LogConfigBuilder LogConfigBuilder;
  25. public readonly List<LogWriter> LogWriters = new List<LogWriter>();
  26. public override string ToString()
  27. {
  28. return LogConfigBuilder.ToString();
  29. }
  30. }
  31. #endregion
  32. #region Fields
  33. private bool disposed;
  34. private readonly string ns;
  35. private readonly System.Xml.Schema.XmlSchema schema;
  36. private readonly object configReadingLock = new object();
  37. private readonly Dictionary<EntityCategory, LogWriter[]> logWriterPreparedDict = new Dictionary<EntityCategory, LogWriter[]>();
  38. private readonly Dictionary<IIdentifiableEntity, Dictionary<object, EntityCategory>> cachedEntityCategoryDict = new Dictionary<IIdentifiableEntity, Dictionary<object, EntityCategory>>();
  39. private readonly List<LogWriter> createdLogWriters = new List<LogWriter>();
  40. private readonly List<LogConfigWrapper> logConfigWrappers = new List<LogConfigWrapper>();
  41. private readonly List<LogConfigOutput> leftOverLinesOutputs = new List<LogConfigOutput>();
  42. private readonly List<LogConfigOutput> leftOverEntitiesOutput = new List<LogConfigOutput>();
  43. private readonly List<EntityCategory> leftoverEntities = new List<EntityCategory>(); // List containing leftover entities.
  44. private readonly System.Threading.Timer cleaningTimer;
  45. #endregion
  46. #region Construction & Finalizer
  47. /// <summary>
  48. /// Initializes a new instance of the LogConfig class.
  49. /// </summary>
  50. internal LogConfig()
  51. {
  52. // Read the LogConfig.xsd file from an embedded resource stream and validate it.
  53. bool schemaValid = true;
  54. StringBuilder errors = new StringBuilder();
  55. using(var logConfigSchemaStream = Assemblies.GetManifestResourceStreamWithPartialName("LogConfig.xsd", System.Reflection.Assembly.GetExecutingAssembly()))
  56. {
  57. schema = XmlSchema.Read(logConfigSchemaStream,
  58. (
  59. delegate(object sender, ValidationEventArgs args)
  60. {
  61. Debug.WriteLine(args.Message);
  62. if (args.Severity == XmlSeverityType.Error)
  63. {
  64. schemaValid = false;
  65. errors.Append(args.Message);
  66. errors.Append("\r\n");
  67. }
  68. }
  69. ));
  70. }
  71. if ((schema == null) || !schemaValid)
  72. {
  73. Debug.WriteLine("Schema LogConfig was not valid!!!!");
  74. Debug.WriteLine(errors.ToString());
  75. throw new LogException(LogExceptionType.CorruptConfigSchemaFile, errors.ToString());
  76. }
  77. ns = schema.TargetNamespace;
  78. cleaningTimer = new System.Threading.Timer(PerformListCleaning, null, 120000, 120000); // Every second minute.
  79. }
  80. /// <summary>
  81. /// Finalizer.
  82. /// </summary>
  83. ~LogConfig()
  84. {
  85. Dispose(false);
  86. }
  87. #endregion
  88. #region IDisposable Members
  89. /// <summary>
  90. /// Disposes the LogConfig's resources.
  91. /// </summary>
  92. public void Dispose()
  93. {
  94. Dispose(true);
  95. GC.SuppressFinalize(this);
  96. }
  97. /// <summary>
  98. /// Internal dispose method.
  99. /// </summary>
  100. /// <param name="disposing"></param>
  101. private void Dispose(bool disposing)
  102. {
  103. if (!disposed)
  104. {
  105. disposed = true;
  106. if (disposing)
  107. {
  108. lock (configReadingLock)
  109. {
  110. CloseAllLogWritersLocked("Shutting down logger.");
  111. }
  112. cleaningTimer.Dispose();
  113. }
  114. }
  115. }
  116. #endregion
  117. #region Properties
  118. public bool IsConfigured
  119. {
  120. get { return logConfigWrappers.Count > 0; }
  121. }
  122. #endregion
  123. #region Methods: Refresh
  124. /// <summary>
  125. /// Refreshes the log configuration from the configuration xml file.
  126. /// </summary>
  127. /// <param name="configFiles">A list of config files (the file is in the form of a string[]).</param>
  128. /// <param name="leftoverLogLinesConfigOutputList"></param>
  129. /// <param name="leftoverLogEntitiesConfigOutputList"></param>
  130. /// <exception cref="Exception">If no log configuration file is specified.</exception>
  131. /// <exception cref="Exception">If the specified log configuration file is missing.</exception>
  132. /// <exception cref="Exception">If the log configuration file is invalid.</exception>
  133. internal void Refresh(List<string[]> configFiles, LogConfigOutput[] leftoverLogLinesConfigOutputList, LogConfigOutput[] leftoverLogEntitiesConfigOutputList)
  134. {
  135. if (disposed)
  136. return;
  137. CloseAllLogWritersLocked("Configuration Refreshed.");
  138. lock (configReadingLock)
  139. {
  140. logConfigWrappers.Clear();
  141. leftOverLinesOutputs.Clear();
  142. leftOverEntitiesOutput.Clear();
  143. foreach (string[] configFile in configFiles)
  144. {
  145. XmlDocument logConfigXml = new XmlDocument();
  146. // Load and validate the config file.
  147. bool valid = true;
  148. try
  149. {
  150. logConfigXml.Schemas.Add(schema);
  151. logConfigXml.LoadXml(string.Join("\r\n", configFile));
  152. if (logConfigXml.DocumentElement != null)
  153. {
  154. XmlAttribute namespaceAttribute = logConfigXml.DocumentElement.Attributes["xmlns"];
  155. if ((namespaceAttribute == null) || !namespaceAttribute.Value.Equals(schema.TargetNamespace, StringComparison.InvariantCultureIgnoreCase))
  156. {
  157. System.Diagnostics.Debug.WriteLine(string.Concat("The log config XML doesn't have the correct namespace. Wanted: \"", schema.TargetNamespace, "\", was: \"", namespaceAttribute.Value, "\""));
  158. valid = false;
  159. }
  160. else
  161. {
  162. logConfigXml.Validate((
  163. delegate(object sender, System.Xml.Schema.ValidationEventArgs args)
  164. {
  165. System.Diagnostics.Debug.WriteLine(args.Message);
  166. valid = false;
  167. }));
  168. }
  169. }
  170. }
  171. catch (XmlException) { }
  172. if (!valid)
  173. throw new LogException(LogExceptionType.InvalidLogConfigFile, string.Join("\r\n", configFile));
  174. XmlNode logConfigFileNode = logConfigXml["LogConfigFile", ns];
  175. if (logConfigFileNode != null)
  176. {
  177. foreach (XmlNode logConfigNode in logConfigFileNode.ChildNodes)
  178. {
  179. if ((logConfigNode.NodeType == XmlNodeType.Element) && (logConfigNode.LocalName == "LogConfig"))
  180. {
  181. XmlAttribute enabledAttribute = logConfigNode.Attributes["Enabled"];
  182. if ((enabledAttribute != null) && !XmlConvert.ToBoolean(enabledAttribute.Value))
  183. continue;
  184. logConfigWrappers.Add(new LogConfigWrapper(this, logConfigNode, ns));
  185. }
  186. }
  187. XmlNode leftOversNode = logConfigFileNode["LeftOvers", ns];
  188. if (leftOversNode != null)
  189. leftOverLinesOutputs.Add(LogConfigOutput.Create(leftOversNode, ns));
  190. XmlNode leftOverEntitiesNode = logConfigXml["LeftoverEntities", ns];
  191. if (leftOverEntitiesNode != null)
  192. leftOverEntitiesOutput.Add(LogConfigOutput.Create(leftOverEntitiesNode, ns));
  193. }
  194. }
  195. foreach (LogConfigOutput output in leftoverLogLinesConfigOutputList)
  196. leftOverLinesOutputs.Add(output);
  197. foreach (LogConfigOutput output in leftoverLogEntitiesConfigOutputList)
  198. leftOverEntitiesOutput.Add(output);
  199. }
  200. }
  201. /// <summary>
  202. /// Closes all LogWriters. NOTE! Must be called under the lock of configReadingLock!
  203. /// </summary>
  204. /// <param name="reason"></param>
  205. private void CloseAllLogWritersLocked(string reason)
  206. {
  207. // Empty the buffered log writers.
  208. foreach (LogWriter logWriter in createdLogWriters)
  209. logWriter.Dispose(reason);
  210. // Clear all lists and dictionaries.
  211. createdLogWriters.Clear();
  212. logWriterPreparedDict.Clear();
  213. leftoverEntities.Clear();
  214. cachedEntityCategoryDict.Clear();
  215. }
  216. #endregion
  217. #region Methods: GetLogWriters
  218. /// <summary>
  219. /// Returns a list of log writers to be used with the specified identifiable entity.
  220. /// </summary>
  221. /// <param name="entityCategory"></param>
  222. /// <returns></returns>
  223. internal LogWriter[] GetLogWriters(EntityCategory entityCategory)
  224. {
  225. if (disposed)
  226. return new LogWriter[0];
  227. LogWriter[] cachedLogWriters;
  228. lock (configReadingLock)
  229. {
  230. if (!logWriterPreparedDict.TryGetValue(entityCategory, out cachedLogWriters))
  231. {
  232. List<LogWriter> logWriterList = new List<LogWriter>();
  233. bool anyExcluded = false;
  234. foreach (LogConfigWrapper logConfigWrapper in logConfigWrappers)
  235. {
  236. DebugLogLevel debugLogLevel;
  237. if (logConfigWrapper.LogConfigBuilder.MatchFilter(entityCategory, out debugLogLevel))
  238. {
  239. foreach (LogWriter logWriter in logConfigWrapper.LogWriters)
  240. {
  241. if (!logWriterList.Contains(logWriter))
  242. {
  243. logWriter.CacheFilterData(entityCategory, debugLogLevel);
  244. logWriterList.Add(logWriter);
  245. }
  246. }
  247. if (debugLogLevel == DebugLogLevel.Excluded)
  248. anyExcluded = true;
  249. }
  250. }
  251. if (!anyExcluded && (logWriterList.Count == 0))
  252. {
  253. foreach (LogConfigOutput output in leftOverLinesOutputs)
  254. logWriterList.Add(GetLogWriter("Leftovers", output));
  255. if (leftOverEntitiesOutput.Count > 0)
  256. {
  257. if (!leftoverEntities.Contains(entityCategory))
  258. {
  259. leftoverEntities.Add(entityCategory);
  260. foreach (LogConfigOutput output in leftOverEntitiesOutput)
  261. {
  262. LogWriter leftoverEntitiesLogWriter = GetLogWriter("LeftoverEntities", output);
  263. if (leftoverEntitiesLogWriter != null)
  264. leftoverEntitiesLogWriter.PerformWrite(new DebugLogEntry(entityCategory.Entity, IdentifiableEntity.ToString(entityCategory.Entity, true), "", DebugLogLevel.Normal));
  265. }
  266. }
  267. }
  268. }
  269. cachedLogWriters = logWriterList.ToArray();
  270. if(!logWriterPreparedDict.ContainsKey(entityCategory))
  271. {
  272. logWriterPreparedDict.Add(entityCategory, cachedLogWriters);
  273. }
  274. }
  275. }
  276. return cachedLogWriters;
  277. }
  278. /// <summary>
  279. /// Get all active log writers.
  280. /// </summary>
  281. /// <returns></returns>
  282. internal LogWriter[] GetLogWriters()
  283. {
  284. if (disposed)
  285. return new LogWriter[0];
  286. lock (configReadingLock)
  287. {
  288. return createdLogWriters.ToArray();
  289. }
  290. }
  291. #endregion
  292. #region Methods: GetLogWriter
  293. /// <summary>
  294. /// Get (or creates) the appropriate logwriter depending on the output type and the output parameters.
  295. /// </summary>
  296. private LogWriter GetLogWriter(string logName, LogConfigOutput output)
  297. {
  298. if (disposed)
  299. return null;
  300. LogConfigTextFileOutput logConfigTextFileOutput = output as LogConfigTextFileOutput;
  301. if (logConfigTextFileOutput != null)
  302. {
  303. foreach (LogWriter createdLogWriter in createdLogWriters)
  304. {
  305. TextFileLogWriter textFileLogWriter = createdLogWriter as TextFileLogWriter;
  306. if ((textFileLogWriter != null) && textFileLogWriter.LogConfigTextFilePath.Equals(logConfigTextFileOutput.LogConfigTextFilePath))
  307. return textFileLogWriter;
  308. }
  309. TextFileLogWriter newLogWriter = new TextFileLogWriter(logName, logConfigTextFileOutput);
  310. createdLogWriters.Add(newLogWriter);
  311. return newLogWriter;
  312. }
  313. LogConfigEventLogSubscriptionOutput logConfigEventLogSubscriptionOutput = output as LogConfigEventLogSubscriptionOutput;
  314. if (logConfigEventLogSubscriptionOutput != null)
  315. {
  316. foreach (LogWriter createdLogWriter in createdLogWriters)
  317. {
  318. EventLogSubscriptionLogWriter eventSubscriberSupplierLogWriter = createdLogWriter as EventLogSubscriptionLogWriter;
  319. if ((eventSubscriberSupplierLogWriter != null) &&
  320. (eventSubscriberSupplierLogWriter.SubscriberId.Equals(logConfigEventLogSubscriptionOutput.SubscriberId)) &&
  321. (eventSubscriberSupplierLogWriter.StorageType.Equals(logConfigEventLogSubscriptionOutput.StorageType))
  322. )
  323. {
  324. return eventSubscriberSupplierLogWriter;
  325. }
  326. }
  327. EventLogSubscriptionLogWriter newLogWriter = new EventLogSubscriptionLogWriter(logName, logConfigEventLogSubscriptionOutput);
  328. createdLogWriters.Add(newLogWriter);
  329. return newLogWriter;
  330. }
  331. LogConfigExternalLogWriterOutput logConfigExternalLogWriterOutput = output as LogConfigExternalLogWriterOutput;
  332. if (logConfigExternalLogWriterOutput != null)
  333. {
  334. foreach (LogWriter createdLogWriter in createdLogWriters)
  335. {
  336. ExternalLogWriterWrapper externalLogWriter = createdLogWriter as ExternalLogWriterWrapper;
  337. if ((externalLogWriter != null) &&
  338. externalLogWriter.ExternalLogType.Equals(logConfigExternalLogWriterOutput.ExternalLogType, StringComparison.CurrentCultureIgnoreCase) &&
  339. externalLogWriter.ExternalLogName.Equals(logConfigExternalLogWriterOutput.ExternalLogName, StringComparison.CurrentCultureIgnoreCase))
  340. {
  341. return externalLogWriter;
  342. }
  343. }
  344. ExternalLogWriterWrapper newLogWriter = new ExternalLogWriterWrapper(logName, logConfigExternalLogWriterOutput);
  345. createdLogWriters.Add(newLogWriter);
  346. return newLogWriter;
  347. }
  348. return null;
  349. }
  350. #endregion
  351. #region Methods: Find Filter Nodes
  352. /// <summary>
  353. /// Checks whether a given filterNode matches the entity.
  354. /// </summary>
  355. /// <param name="entity"></param>
  356. /// <param name="filterNode"></param>
  357. /// <returns></returns>
  358. private static bool FilterNodeMatches(IIdentifiableEntity entity, XmlNode filterNode)
  359. {
  360. bool enabled;
  361. XmlAttribute enabledAttribute = filterNode.Attributes["Enabled"];
  362. if (enabledAttribute != null)
  363. enabled = XmlConvert.ToBoolean(enabledAttribute.Value);
  364. else
  365. enabled = true;
  366. if (enabled)
  367. {
  368. string entityType = "";
  369. XmlAttribute entityTypeAttribute = filterNode.Attributes["EntityType"];
  370. if (entityTypeAttribute != null)
  371. entityType = entityTypeAttribute.Value;
  372. string entitySubType = "";
  373. XmlAttribute entitySubTypeAttribute = filterNode.Attributes["EntitySubType"];
  374. if (entitySubTypeAttribute != null)
  375. entitySubType = entitySubTypeAttribute.Value;
  376. string id = "";
  377. XmlAttribute idAttribute = filterNode.Attributes["Id"];
  378. if (idAttribute != null)
  379. id = idAttribute.Value;
  380. return System.Text.RegularExpressions.Regex.IsMatch(entity.EntityType, entityType, System.Text.RegularExpressions.RegexOptions.IgnoreCase) &&
  381. System.Text.RegularExpressions.Regex.IsMatch(entity.Id.ToString(System.Globalization.CultureInfo.InvariantCulture), id) &&
  382. System.Text.RegularExpressions.Regex.IsMatch(entity.EntitySubType, entitySubType, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
  383. }
  384. return false;
  385. }
  386. #endregion
  387. #region Methods: GetEntityCategory
  388. internal EntityCategory GetEntityCategory(IIdentifiableEntity entity, object category)
  389. {
  390. EntityCategory result = null;
  391. if (entity == null)
  392. entity = IdentifiableEntity.Empty;
  393. if (category == null)
  394. category = string.Empty;
  395. lock (configReadingLock)
  396. {
  397. //Dictionary<object, EntityCategory> entityCategoriesDict;
  398. //if (cachedEntityCategoryDict.TryGetValue(entity, out entityCategoriesDict))
  399. //{
  400. //if (!entityCategoriesDict.TryGetValue(category, out result))
  401. //{
  402. result = new EntityCategory(entity, category);
  403. // entityCategoriesDict[category] = result;
  404. //}
  405. //}
  406. //else
  407. //{
  408. // result = new EntityCategory(entity, category);
  409. // entityCategoriesDict = new Dictionary<object, EntityCategory>();
  410. // entityCategoriesDict[category] = result;
  411. // cachedEntityCategoryDict[entity] = entityCategoriesDict;
  412. //}
  413. }
  414. result.Touch();
  415. return result;
  416. }
  417. #endregion
  418. #region Methods: Cleaning of internal lists
  419. private void PerformListCleaning(object o)
  420. {
  421. lock (configReadingLock)
  422. {
  423. DateTime oldestAllowedTouch = DateTime.Now.AddMinutes(-5); // 5 minutes is the longest time an untouched EntityCategory is cached in the log library.
  424. #region Clean the logWriterPreparedDict and the cachedEntityCategoryDict.
  425. List<EntityCategory> entityCategoriesToRemove = new List<EntityCategory>();
  426. foreach (EntityCategory entityCategory in logWriterPreparedDict.Keys)
  427. {
  428. if (entityCategory.LastTouched < oldestAllowedTouch)
  429. entityCategoriesToRemove.Add(entityCategory);
  430. }
  431. //Remove from the dict.
  432. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove)
  433. {
  434. logWriterPreparedDict.Remove(entityCategoryToRemove);
  435. Dictionary<object, EntityCategory> entityCategoriesDict;
  436. if (cachedEntityCategoryDict.TryGetValue(entityCategoryToRemove.Entity, out entityCategoriesDict))
  437. {
  438. entityCategoriesDict.Remove(entityCategoryToRemove.Category);
  439. if (entityCategoriesDict.Count == 0)
  440. cachedEntityCategoryDict.Remove(entityCategoryToRemove.Entity);
  441. }
  442. }
  443. #endregion
  444. #region Clean the leftoverEntities
  445. entityCategoriesToRemove.Clear();
  446. foreach (EntityCategory entityCategory in leftoverEntities)
  447. {
  448. if (entityCategory.LastTouched < oldestAllowedTouch)
  449. entityCategoriesToRemove.Add(entityCategory);
  450. }
  451. //Remove from the list.
  452. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove)
  453. leftoverEntities.Remove(entityCategoryToRemove);
  454. #endregion
  455. foreach (LogWriter logWriter in createdLogWriters)
  456. logWriter.PerformListCleaning(oldestAllowedTouch);
  457. }
  458. }
  459. #endregion
  460. #region Methods: UnregisterEntity
  461. internal void UnregisterEntity(IIdentifiableEntity entity)
  462. {
  463. lock (configReadingLock)
  464. {
  465. #region Clean the logWriterPreparedDict
  466. List<EntityCategory> entityCategoriesToRemove = new List<EntityCategory>();
  467. foreach (EntityCategory entityCategory in logWriterPreparedDict.Keys)
  468. {
  469. if (IdentifiableEntity.Equals(entityCategory.Entity, entity))
  470. entityCategoriesToRemove.Add(entityCategory);
  471. }
  472. //Remove from the list.
  473. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove)
  474. logWriterPreparedDict.Remove(entityCategoryToRemove);
  475. #endregion
  476. #region Clean the leftoverEntities
  477. entityCategoriesToRemove.Clear();
  478. foreach (EntityCategory entityCategory in leftoverEntities)
  479. {
  480. if (IdentifiableEntity.Equals(entityCategory.Entity, entity))
  481. entityCategoriesToRemove.Add(entityCategory);
  482. }
  483. //Remove from the list.
  484. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove)
  485. leftoverEntities.Remove(entityCategoryToRemove);
  486. #endregion
  487. foreach (LogWriter logWriter in createdLogWriters)
  488. logWriter.RemoveEntity(entity);
  489. }
  490. }
  491. #endregion
  492. }
  493. }