LogConfig.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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 System.Threading.Timer cleaningTimer;
  42. #endregion
  43. #region Construction & Finalizer
  44. /// <summary>
  45. /// Initializes a new instance of the LogConfig class.
  46. /// </summary>
  47. internal LogConfig()
  48. {
  49. // Read the LogConfig.xsd file from an embedded resource stream and validate it.
  50. bool schemaValid = true;
  51. StringBuilder errors = new StringBuilder();
  52. using(var logConfigSchemaStream = Assemblies.GetManifestResourceStreamWithPartialName("LogConfig.xsd", System.Reflection.Assembly.GetExecutingAssembly()))
  53. {
  54. schema = XmlSchema.Read(logConfigSchemaStream,
  55. (
  56. delegate(object sender, ValidationEventArgs args)
  57. {
  58. Debug.WriteLine(args.Message);
  59. if (args.Severity == XmlSeverityType.Error)
  60. {
  61. schemaValid = false;
  62. errors.Append(args.Message);
  63. errors.Append("\r\n");
  64. }
  65. }
  66. ));
  67. }
  68. if ((schema == null) || !schemaValid)
  69. {
  70. Debug.WriteLine("Schema LogConfig was not valid!!!!");
  71. Debug.WriteLine(errors.ToString());
  72. throw new LogException(LogExceptionType.CorruptConfigSchemaFile, errors.ToString());
  73. }
  74. ns = schema.TargetNamespace;
  75. cleaningTimer = new System.Threading.Timer(PerformListCleaning, null, 120000, 120000); // Every second minute.
  76. }
  77. /// <summary>
  78. /// Finalizer.
  79. /// </summary>
  80. ~LogConfig()
  81. {
  82. Dispose(false);
  83. }
  84. #endregion
  85. #region IDisposable Members
  86. /// <summary>
  87. /// Disposes the LogConfig's resources.
  88. /// </summary>
  89. public void Dispose()
  90. {
  91. Dispose(true);
  92. GC.SuppressFinalize(this);
  93. }
  94. /// <summary>
  95. /// Internal dispose method.
  96. /// </summary>
  97. /// <param name="disposing"></param>
  98. private void Dispose(bool disposing)
  99. {
  100. if (!disposed)
  101. {
  102. disposed = true;
  103. if (disposing)
  104. {
  105. lock (configReadingLock)
  106. {
  107. CloseAllLogWritersLocked("Shutting down logger.");
  108. }
  109. cleaningTimer.Dispose();
  110. }
  111. }
  112. }
  113. #endregion
  114. #region Properties
  115. public bool IsConfigured
  116. {
  117. get { return logConfigWrappers.Count > 0; }
  118. }
  119. #endregion
  120. #region Methods: Refresh
  121. /// <summary>
  122. /// Refreshes the log configuration from the configuration xml file.
  123. /// </summary>
  124. /// <param name="configFiles">A list of config files (the file is in the form of a string[]).</param>
  125. /// <param name="leftoverLogLinesConfigOutputList"></param>
  126. /// <param name="leftoverLogEntitiesConfigOutputList"></param>
  127. /// <exception cref="Exception">If no log configuration file is specified.</exception>
  128. /// <exception cref="Exception">If the specified log configuration file is missing.</exception>
  129. /// <exception cref="Exception">If the log configuration file is invalid.</exception>
  130. internal void Refresh(List<string[]> configFiles, LogConfigOutput[] leftoverLogLinesConfigOutputList, LogConfigOutput[] leftoverLogEntitiesConfigOutputList)
  131. {
  132. if (disposed)
  133. return;
  134. lock (configReadingLock)
  135. {
  136. CloseAllLogWritersLocked("Configuration Refreshed.");
  137. logConfigWrappers.Clear();
  138. foreach (string[] configFile in configFiles)
  139. {
  140. XmlDocument logConfigXml = new XmlDocument();
  141. // Load and validate the config file.
  142. bool valid = true;
  143. try
  144. {
  145. logConfigXml.Schemas.Add(schema);
  146. logConfigXml.LoadXml(string.Join("\r\n", configFile));
  147. if (logConfigXml.DocumentElement != null)
  148. {
  149. XmlAttribute namespaceAttribute = logConfigXml.DocumentElement.Attributes["xmlns"];
  150. if ((namespaceAttribute == null) || !namespaceAttribute.Value.Equals(schema.TargetNamespace, StringComparison.InvariantCultureIgnoreCase))
  151. {
  152. System.Diagnostics.Debug.WriteLine(string.Concat("The log config XML doesn't have the correct namespace. Wanted: \"", schema.TargetNamespace, "\", was: \"", namespaceAttribute.Value, "\""));
  153. valid = false;
  154. }
  155. else
  156. {
  157. logConfigXml.Validate((
  158. delegate(object sender, System.Xml.Schema.ValidationEventArgs args)
  159. {
  160. System.Diagnostics.Debug.WriteLine(args.Message);
  161. valid = false;
  162. }));
  163. }
  164. }
  165. }
  166. catch (XmlException) { }
  167. if (!valid)
  168. throw new LogException(LogExceptionType.InvalidLogConfigFile, string.Join("\r\n", configFile));
  169. XmlNode logConfigFileNode = logConfigXml["LogConfigFile", ns];
  170. if (logConfigFileNode != null)
  171. {
  172. foreach (XmlNode logConfigNode in logConfigFileNode.ChildNodes)
  173. {
  174. if ((logConfigNode.NodeType == XmlNodeType.Element) && (logConfigNode.LocalName == "LogConfig"))
  175. {
  176. XmlAttribute enabledAttribute = logConfigNode.Attributes["Enabled"];
  177. if ((enabledAttribute != null) && !XmlConvert.ToBoolean(enabledAttribute.Value))
  178. continue;
  179. logConfigWrappers.Add(new LogConfigWrapper(this, logConfigNode, ns));
  180. }
  181. }
  182. }
  183. }
  184. }
  185. }
  186. /// <summary>
  187. /// Closes all LogWriters. NOTE! Must be called under the lock of configReadingLock!
  188. /// </summary>
  189. /// <param name="reason"></param>
  190. private void CloseAllLogWritersLocked(string reason)
  191. {
  192. // Empty the buffered log writers.
  193. foreach (LogWriter logWriter in createdLogWriters)
  194. logWriter.Dispose(reason);
  195. // Clear all lists and dictionaries.
  196. createdLogWriters.Clear();
  197. logWriterPreparedDict.Clear();
  198. cachedEntityCategoryDict.Clear();
  199. }
  200. #endregion
  201. #region Methods: GetLogWriters
  202. /// <summary>
  203. /// Returns a list of log writers to be used with the specified identifiable entity.
  204. /// </summary>
  205. /// <param name="entityCategory"></param>
  206. /// <returns></returns>
  207. internal LogWriter[] GetLogWriters(EntityCategory entityCategory)
  208. {
  209. if (disposed)
  210. return new LogWriter[0];
  211. LogWriter[] cachedLogWriters;
  212. lock (configReadingLock)
  213. {
  214. if (!logWriterPreparedDict.TryGetValue(entityCategory, out cachedLogWriters))
  215. {
  216. List<LogWriter> logWriterList = new List<LogWriter>();
  217. foreach (LogConfigWrapper logConfigWrapper in logConfigWrappers)
  218. {
  219. DebugLogLevel debugLogLevel;
  220. if (logConfigWrapper.LogConfigBuilder.MatchFilter(entityCategory, out debugLogLevel))
  221. {
  222. foreach (LogWriter logWriter in logConfigWrapper.LogWriters)
  223. {
  224. if (!logWriterList.Contains(logWriter))
  225. {
  226. logWriter.CacheFilterData(entityCategory, debugLogLevel);
  227. logWriterList.Add(logWriter);
  228. }
  229. }
  230. }
  231. }
  232. cachedLogWriters = logWriterList.ToArray();
  233. if(!logWriterPreparedDict.ContainsKey(entityCategory))
  234. {
  235. logWriterPreparedDict.Add(entityCategory, cachedLogWriters);
  236. }
  237. }
  238. }
  239. return cachedLogWriters;
  240. }
  241. /// <summary>
  242. /// Get all active log writers.
  243. /// </summary>
  244. /// <returns></returns>
  245. internal LogWriter[] GetLogWriters()
  246. {
  247. if (disposed)
  248. return new LogWriter[0];
  249. lock (configReadingLock)
  250. {
  251. return createdLogWriters.ToArray();
  252. }
  253. }
  254. #endregion
  255. #region Methods: GetLogWriter
  256. /// <summary>
  257. /// Get (or creates) the appropriate logwriter depending on the output type and the output parameters.
  258. /// </summary>
  259. private LogWriter GetLogWriter(string logName, LogConfigOutput output)
  260. {
  261. if (disposed)
  262. return null;
  263. LogConfigTextFileOutput logConfigTextFileOutput = output as LogConfigTextFileOutput;
  264. if (logConfigTextFileOutput != null)
  265. {
  266. foreach (LogWriter createdLogWriter in createdLogWriters)
  267. {
  268. TextFileLogWriter textFileLogWriter = createdLogWriter as TextFileLogWriter;
  269. if ((textFileLogWriter != null) && textFileLogWriter.LogConfigTextFilePath.Equals(logConfigTextFileOutput.LogConfigTextFilePath))
  270. return textFileLogWriter;
  271. }
  272. TextFileLogWriter newLogWriter = new TextFileLogWriter(logName, logConfigTextFileOutput);
  273. createdLogWriters.Add(newLogWriter);
  274. return newLogWriter;
  275. }
  276. LogConfigEventLogSubscriptionOutput logConfigEventLogSubscriptionOutput = output as LogConfigEventLogSubscriptionOutput;
  277. if (logConfigEventLogSubscriptionOutput != null)
  278. {
  279. foreach (LogWriter createdLogWriter in createdLogWriters)
  280. {
  281. EventLogSubscriptionLogWriter eventSubscriberSupplierLogWriter = createdLogWriter as EventLogSubscriptionLogWriter;
  282. if ((eventSubscriberSupplierLogWriter != null) &&
  283. (eventSubscriberSupplierLogWriter.SubscriberId.Equals(logConfigEventLogSubscriptionOutput.SubscriberId)) &&
  284. (eventSubscriberSupplierLogWriter.StorageType.Equals(logConfigEventLogSubscriptionOutput.StorageType))
  285. )
  286. {
  287. return eventSubscriberSupplierLogWriter;
  288. }
  289. }
  290. EventLogSubscriptionLogWriter newLogWriter = new EventLogSubscriptionLogWriter(logName, logConfigEventLogSubscriptionOutput);
  291. createdLogWriters.Add(newLogWriter);
  292. return newLogWriter;
  293. }
  294. LogConfigExternalLogWriterOutput logConfigExternalLogWriterOutput = output as LogConfigExternalLogWriterOutput;
  295. if (logConfigExternalLogWriterOutput != null)
  296. {
  297. foreach (LogWriter createdLogWriter in createdLogWriters)
  298. {
  299. ExternalLogWriterWrapper externalLogWriter = createdLogWriter as ExternalLogWriterWrapper;
  300. if ((externalLogWriter != null) &&
  301. externalLogWriter.ExternalLogType.Equals(logConfigExternalLogWriterOutput.ExternalLogType, StringComparison.CurrentCultureIgnoreCase) &&
  302. externalLogWriter.ExternalLogName.Equals(logConfigExternalLogWriterOutput.ExternalLogName, StringComparison.CurrentCultureIgnoreCase))
  303. {
  304. return externalLogWriter;
  305. }
  306. }
  307. ExternalLogWriterWrapper newLogWriter = new ExternalLogWriterWrapper(logName, logConfigExternalLogWriterOutput);
  308. createdLogWriters.Add(newLogWriter);
  309. return newLogWriter;
  310. }
  311. return null;
  312. }
  313. #endregion
  314. #region Methods: Find Filter Nodes
  315. #endregion
  316. #region Methods: GetEntityCategory
  317. internal EntityCategory GetEntityCategory(IIdentifiableEntity entity, object category)
  318. {
  319. EntityCategory result = null;
  320. if (entity == null)
  321. entity = IdentifiableEntity.Empty;
  322. if (category == null)
  323. category = string.Empty;
  324. lock (configReadingLock)
  325. {
  326. //Dictionary<object, EntityCategory> entityCategoriesDict;
  327. //if (cachedEntityCategoryDict.TryGetValue(entity, out entityCategoriesDict))
  328. //{
  329. //if (!entityCategoriesDict.TryGetValue(category, out result))
  330. //{
  331. result = new EntityCategory(entity, category);
  332. // entityCategoriesDict[category] = result;
  333. //}
  334. //}
  335. //else
  336. //{
  337. // result = new EntityCategory(entity, category);
  338. // entityCategoriesDict = new Dictionary<object, EntityCategory>();
  339. // entityCategoriesDict[category] = result;
  340. // cachedEntityCategoryDict[entity] = entityCategoriesDict;
  341. //}
  342. }
  343. result.Touch();
  344. return result;
  345. }
  346. #endregion
  347. #region Methods: Cleaning of internal lists
  348. private void PerformListCleaning(object o)
  349. {
  350. lock (configReadingLock)
  351. {
  352. DateTime oldestAllowedTouch = DateTime.Now.AddMinutes(-5); // 5 minutes is the longest time an untouched EntityCategory is cached in the log library.
  353. #region Clean the logWriterPreparedDict and the cachedEntityCategoryDict.
  354. List<EntityCategory> entityCategoriesToRemove = new List<EntityCategory>();
  355. foreach (EntityCategory entityCategory in logWriterPreparedDict.Keys)
  356. {
  357. if (entityCategory.LastTouched < oldestAllowedTouch)
  358. entityCategoriesToRemove.Add(entityCategory);
  359. }
  360. //Remove from the dict.
  361. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove)
  362. {
  363. logWriterPreparedDict.Remove(entityCategoryToRemove);
  364. Dictionary<object, EntityCategory> entityCategoriesDict;
  365. if (cachedEntityCategoryDict.TryGetValue(entityCategoryToRemove.Entity, out entityCategoriesDict))
  366. {
  367. entityCategoriesDict.Remove(entityCategoryToRemove.Category);
  368. if (entityCategoriesDict.Count == 0)
  369. cachedEntityCategoryDict.Remove(entityCategoryToRemove.Entity);
  370. }
  371. }
  372. #endregion
  373. foreach (LogWriter logWriter in createdLogWriters)
  374. logWriter.PerformListCleaning(oldestAllowedTouch);
  375. }
  376. }
  377. #endregion
  378. #region Methods: UnregisterEntity
  379. internal void UnregisterEntity(IIdentifiableEntity entity)
  380. {
  381. lock (configReadingLock)
  382. {
  383. #region Clean the logWriterPreparedDict
  384. List<EntityCategory> entityCategoriesToRemove = new List<EntityCategory>();
  385. foreach (EntityCategory entityCategory in logWriterPreparedDict.Keys)
  386. {
  387. if (IdentifiableEntity.Equals(entityCategory.Entity, entity))
  388. entityCategoriesToRemove.Add(entityCategory);
  389. }
  390. //Remove from the list.
  391. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove)
  392. logWriterPreparedDict.Remove(entityCategoryToRemove);
  393. #endregion
  394. foreach (LogWriter logWriter in createdLogWriters)
  395. logWriter.RemoveEntity(entity);
  396. }
  397. }
  398. #endregion
  399. }
  400. }