using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Xml; using System.Xml.Schema; namespace Wayne.Lib.Log { /// /// The Log config handles the application-wide setup for the logging. It is read from a /// XML file that should be in accordance with the LogConfig.xsd schema. /// internal class LogConfig : IDisposable { #region Private Class LogConfigWrapper private class LogConfigWrapper { public LogConfigWrapper(LogConfig logConfig, XmlNode logConfigNode, string ns) { LogConfigBuilder = new LogConfigBuilder(logConfigNode, ns); foreach (LogConfigOutput output in LogConfigBuilder.Outputs) LogWriters.Add(logConfig.GetLogWriter(LogConfigBuilder.Name, output)); } public readonly LogConfigBuilder LogConfigBuilder; public readonly List LogWriters = new List(); public override string ToString() { return LogConfigBuilder.ToString(); } } #endregion #region Fields private bool disposed; private readonly string ns; private readonly System.Xml.Schema.XmlSchema schema; private readonly object configReadingLock = new object(); private readonly Dictionary logWriterPreparedDict = new Dictionary(); private readonly Dictionary> cachedEntityCategoryDict = new Dictionary>(); private readonly List createdLogWriters = new List(); private readonly List logConfigWrappers = new List(); private readonly System.Threading.Timer cleaningTimer; #endregion #region Construction & Finalizer /// /// Initializes a new instance of the LogConfig class. /// internal LogConfig() { // Read the LogConfig.xsd file from an embedded resource stream and validate it. bool schemaValid = true; StringBuilder errors = new StringBuilder(); using(var logConfigSchemaStream = Assemblies.GetManifestResourceStreamWithPartialName("LogConfig.xsd", System.Reflection.Assembly.GetExecutingAssembly())) { schema = XmlSchema.Read(logConfigSchemaStream, ( delegate(object sender, ValidationEventArgs args) { Debug.WriteLine(args.Message); if (args.Severity == XmlSeverityType.Error) { schemaValid = false; errors.Append(args.Message); errors.Append("\r\n"); } } )); } if ((schema == null) || !schemaValid) { Debug.WriteLine("Schema LogConfig was not valid!!!!"); Debug.WriteLine(errors.ToString()); throw new LogException(LogExceptionType.CorruptConfigSchemaFile, errors.ToString()); } ns = schema.TargetNamespace; cleaningTimer = new System.Threading.Timer(PerformListCleaning, null, 120000, 120000); // Every second minute. } /// /// Finalizer. /// ~LogConfig() { Dispose(false); } #endregion #region IDisposable Members /// /// Disposes the LogConfig's resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Internal dispose method. /// /// private void Dispose(bool disposing) { if (!disposed) { disposed = true; if (disposing) { lock (configReadingLock) { CloseAllLogWritersLocked("Shutting down logger."); } cleaningTimer.Dispose(); } } } #endregion #region Properties public bool IsConfigured { get { return logConfigWrappers.Count > 0; } } #endregion #region Methods: Refresh /// /// Refreshes the log configuration from the configuration xml file. /// /// A list of config files (the file is in the form of a string[]). /// /// /// If no log configuration file is specified. /// If the specified log configuration file is missing. /// If the log configuration file is invalid. internal void Refresh(List configFiles, LogConfigOutput[] leftoverLogLinesConfigOutputList, LogConfigOutput[] leftoverLogEntitiesConfigOutputList) { if (disposed) return; lock (configReadingLock) { CloseAllLogWritersLocked("Configuration Refreshed."); logConfigWrappers.Clear(); foreach (string[] configFile in configFiles) { XmlDocument logConfigXml = new XmlDocument(); // Load and validate the config file. bool valid = true; try { logConfigXml.Schemas.Add(schema); logConfigXml.LoadXml(string.Join("\r\n", configFile)); if (logConfigXml.DocumentElement != null) { XmlAttribute namespaceAttribute = logConfigXml.DocumentElement.Attributes["xmlns"]; if ((namespaceAttribute == null) || !namespaceAttribute.Value.Equals(schema.TargetNamespace, StringComparison.InvariantCultureIgnoreCase)) { System.Diagnostics.Debug.WriteLine(string.Concat("The log config XML doesn't have the correct namespace. Wanted: \"", schema.TargetNamespace, "\", was: \"", namespaceAttribute.Value, "\"")); valid = false; } else { logConfigXml.Validate(( delegate(object sender, System.Xml.Schema.ValidationEventArgs args) { System.Diagnostics.Debug.WriteLine(args.Message); valid = false; })); } } } catch (XmlException) { } if (!valid) throw new LogException(LogExceptionType.InvalidLogConfigFile, string.Join("\r\n", configFile)); XmlNode logConfigFileNode = logConfigXml["LogConfigFile", ns]; if (logConfigFileNode != null) { foreach (XmlNode logConfigNode in logConfigFileNode.ChildNodes) { if ((logConfigNode.NodeType == XmlNodeType.Element) && (logConfigNode.LocalName == "LogConfig")) { XmlAttribute enabledAttribute = logConfigNode.Attributes["Enabled"]; if ((enabledAttribute != null) && !XmlConvert.ToBoolean(enabledAttribute.Value)) continue; logConfigWrappers.Add(new LogConfigWrapper(this, logConfigNode, ns)); } } } } } } /// /// Closes all LogWriters. NOTE! Must be called under the lock of configReadingLock! /// /// private void CloseAllLogWritersLocked(string reason) { // Empty the buffered log writers. foreach (LogWriter logWriter in createdLogWriters) logWriter.Dispose(reason); // Clear all lists and dictionaries. createdLogWriters.Clear(); logWriterPreparedDict.Clear(); cachedEntityCategoryDict.Clear(); } #endregion #region Methods: GetLogWriters /// /// Returns a list of log writers to be used with the specified identifiable entity. /// /// /// internal LogWriter[] GetLogWriters(EntityCategory entityCategory) { if (disposed) return new LogWriter[0]; LogWriter[] cachedLogWriters; lock (configReadingLock) { if (!logWriterPreparedDict.TryGetValue(entityCategory, out cachedLogWriters)) { List logWriterList = new List(); foreach (LogConfigWrapper logConfigWrapper in logConfigWrappers) { DebugLogLevel debugLogLevel; if (logConfigWrapper.LogConfigBuilder.MatchFilter(entityCategory, out debugLogLevel)) { foreach (LogWriter logWriter in logConfigWrapper.LogWriters) { if (!logWriterList.Contains(logWriter)) { logWriter.CacheFilterData(entityCategory, debugLogLevel); logWriterList.Add(logWriter); } } } } cachedLogWriters = logWriterList.ToArray(); if(!logWriterPreparedDict.ContainsKey(entityCategory)) { logWriterPreparedDict.Add(entityCategory, cachedLogWriters); } } } return cachedLogWriters; } /// /// Get all active log writers. /// /// internal LogWriter[] GetLogWriters() { if (disposed) return new LogWriter[0]; lock (configReadingLock) { return createdLogWriters.ToArray(); } } #endregion #region Methods: GetLogWriter /// /// Get (or creates) the appropriate logwriter depending on the output type and the output parameters. /// private LogWriter GetLogWriter(string logName, LogConfigOutput output) { if (disposed) return null; LogConfigTextFileOutput logConfigTextFileOutput = output as LogConfigTextFileOutput; if (logConfigTextFileOutput != null) { foreach (LogWriter createdLogWriter in createdLogWriters) { TextFileLogWriter textFileLogWriter = createdLogWriter as TextFileLogWriter; if ((textFileLogWriter != null) && textFileLogWriter.LogConfigTextFilePath.Equals(logConfigTextFileOutput.LogConfigTextFilePath)) return textFileLogWriter; } TextFileLogWriter newLogWriter = new TextFileLogWriter(logName, logConfigTextFileOutput); createdLogWriters.Add(newLogWriter); return newLogWriter; } LogConfigEventLogSubscriptionOutput logConfigEventLogSubscriptionOutput = output as LogConfigEventLogSubscriptionOutput; if (logConfigEventLogSubscriptionOutput != null) { foreach (LogWriter createdLogWriter in createdLogWriters) { EventLogSubscriptionLogWriter eventSubscriberSupplierLogWriter = createdLogWriter as EventLogSubscriptionLogWriter; if ((eventSubscriberSupplierLogWriter != null) && (eventSubscriberSupplierLogWriter.SubscriberId.Equals(logConfigEventLogSubscriptionOutput.SubscriberId)) && (eventSubscriberSupplierLogWriter.StorageType.Equals(logConfigEventLogSubscriptionOutput.StorageType)) ) { return eventSubscriberSupplierLogWriter; } } EventLogSubscriptionLogWriter newLogWriter = new EventLogSubscriptionLogWriter(logName, logConfigEventLogSubscriptionOutput); createdLogWriters.Add(newLogWriter); return newLogWriter; } LogConfigExternalLogWriterOutput logConfigExternalLogWriterOutput = output as LogConfigExternalLogWriterOutput; if (logConfigExternalLogWriterOutput != null) { foreach (LogWriter createdLogWriter in createdLogWriters) { ExternalLogWriterWrapper externalLogWriter = createdLogWriter as ExternalLogWriterWrapper; if ((externalLogWriter != null) && externalLogWriter.ExternalLogType.Equals(logConfigExternalLogWriterOutput.ExternalLogType, StringComparison.CurrentCultureIgnoreCase) && externalLogWriter.ExternalLogName.Equals(logConfigExternalLogWriterOutput.ExternalLogName, StringComparison.CurrentCultureIgnoreCase)) { return externalLogWriter; } } ExternalLogWriterWrapper newLogWriter = new ExternalLogWriterWrapper(logName, logConfigExternalLogWriterOutput); createdLogWriters.Add(newLogWriter); return newLogWriter; } return null; } #endregion #region Methods: Find Filter Nodes #endregion #region Methods: GetEntityCategory internal EntityCategory GetEntityCategory(IIdentifiableEntity entity, object category) { EntityCategory result = null; if (entity == null) entity = IdentifiableEntity.Empty; if (category == null) category = string.Empty; lock (configReadingLock) { //Dictionary entityCategoriesDict; //if (cachedEntityCategoryDict.TryGetValue(entity, out entityCategoriesDict)) //{ //if (!entityCategoriesDict.TryGetValue(category, out result)) //{ result = new EntityCategory(entity, category); // entityCategoriesDict[category] = result; //} //} //else //{ // result = new EntityCategory(entity, category); // entityCategoriesDict = new Dictionary(); // entityCategoriesDict[category] = result; // cachedEntityCategoryDict[entity] = entityCategoriesDict; //} } result.Touch(); return result; } #endregion #region Methods: Cleaning of internal lists private void PerformListCleaning(object o) { lock (configReadingLock) { DateTime oldestAllowedTouch = DateTime.Now.AddMinutes(-5); // 5 minutes is the longest time an untouched EntityCategory is cached in the log library. #region Clean the logWriterPreparedDict and the cachedEntityCategoryDict. List entityCategoriesToRemove = new List(); foreach (EntityCategory entityCategory in logWriterPreparedDict.Keys) { if (entityCategory.LastTouched < oldestAllowedTouch) entityCategoriesToRemove.Add(entityCategory); } //Remove from the dict. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove) { logWriterPreparedDict.Remove(entityCategoryToRemove); Dictionary entityCategoriesDict; if (cachedEntityCategoryDict.TryGetValue(entityCategoryToRemove.Entity, out entityCategoriesDict)) { entityCategoriesDict.Remove(entityCategoryToRemove.Category); if (entityCategoriesDict.Count == 0) cachedEntityCategoryDict.Remove(entityCategoryToRemove.Entity); } } #endregion foreach (LogWriter logWriter in createdLogWriters) logWriter.PerformListCleaning(oldestAllowedTouch); } } #endregion #region Methods: UnregisterEntity internal void UnregisterEntity(IIdentifiableEntity entity) { lock (configReadingLock) { #region Clean the logWriterPreparedDict List entityCategoriesToRemove = new List(); foreach (EntityCategory entityCategory in logWriterPreparedDict.Keys) { if (IdentifiableEntity.Equals(entityCategory.Entity, entity)) entityCategoriesToRemove.Add(entityCategory); } //Remove from the list. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove) logWriterPreparedDict.Remove(entityCategoryToRemove); #endregion foreach (LogWriter logWriter in createdLogWriters) logWriter.RemoveEntity(entity); } } #endregion } }