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 List leftOverLinesOutputs = new List(); private readonly List leftOverEntitiesOutput = new List(); private readonly List leftoverEntities = new List(); // List containing leftover entities. 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; CloseAllLogWritersLocked("Configuration Refreshed."); lock (configReadingLock) { logConfigWrappers.Clear(); leftOverLinesOutputs.Clear(); leftOverEntitiesOutput.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)); } } XmlNode leftOversNode = logConfigFileNode["LeftOvers", ns]; if (leftOversNode != null) leftOverLinesOutputs.Add(LogConfigOutput.Create(leftOversNode, ns)); XmlNode leftOverEntitiesNode = logConfigXml["LeftoverEntities", ns]; if (leftOverEntitiesNode != null) leftOverEntitiesOutput.Add(LogConfigOutput.Create(leftOverEntitiesNode, ns)); } } foreach (LogConfigOutput output in leftoverLogLinesConfigOutputList) leftOverLinesOutputs.Add(output); foreach (LogConfigOutput output in leftoverLogEntitiesConfigOutputList) leftOverEntitiesOutput.Add(output); } } /// /// 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(); leftoverEntities.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(); bool anyExcluded = false; 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); } } if (debugLogLevel == DebugLogLevel.Excluded) anyExcluded = true; } } if (!anyExcluded && (logWriterList.Count == 0)) { foreach (LogConfigOutput output in leftOverLinesOutputs) logWriterList.Add(GetLogWriter("Leftovers", output)); if (leftOverEntitiesOutput.Count > 0) { if (!leftoverEntities.Contains(entityCategory)) { leftoverEntities.Add(entityCategory); foreach (LogConfigOutput output in leftOverEntitiesOutput) { LogWriter leftoverEntitiesLogWriter = GetLogWriter("LeftoverEntities", output); if (leftoverEntitiesLogWriter != null) leftoverEntitiesLogWriter.PerformWrite(new DebugLogEntry(entityCategory.Entity, IdentifiableEntity.ToString(entityCategory.Entity, true), "", DebugLogLevel.Normal)); } } } } 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 /// /// Checks whether a given filterNode matches the entity. /// /// /// /// private static bool FilterNodeMatches(IIdentifiableEntity entity, XmlNode filterNode) { bool enabled; XmlAttribute enabledAttribute = filterNode.Attributes["Enabled"]; if (enabledAttribute != null) enabled = XmlConvert.ToBoolean(enabledAttribute.Value); else enabled = true; if (enabled) { string entityType = ""; XmlAttribute entityTypeAttribute = filterNode.Attributes["EntityType"]; if (entityTypeAttribute != null) entityType = entityTypeAttribute.Value; string entitySubType = ""; XmlAttribute entitySubTypeAttribute = filterNode.Attributes["EntitySubType"]; if (entitySubTypeAttribute != null) entitySubType = entitySubTypeAttribute.Value; string id = ""; XmlAttribute idAttribute = filterNode.Attributes["Id"]; if (idAttribute != null) id = idAttribute.Value; return System.Text.RegularExpressions.Regex.IsMatch(entity.EntityType, entityType, System.Text.RegularExpressions.RegexOptions.IgnoreCase) && System.Text.RegularExpressions.Regex.IsMatch(entity.Id.ToString(System.Globalization.CultureInfo.InvariantCulture), id) && System.Text.RegularExpressions.Regex.IsMatch(entity.EntitySubType, entitySubType, System.Text.RegularExpressions.RegexOptions.IgnoreCase); } return false; } #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 #region Clean the leftoverEntities entityCategoriesToRemove.Clear(); foreach (EntityCategory entityCategory in leftoverEntities) { if (entityCategory.LastTouched < oldestAllowedTouch) entityCategoriesToRemove.Add(entityCategory); } //Remove from the list. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove) leftoverEntities.Remove(entityCategoryToRemove); #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 #region Clean the leftoverEntities entityCategoriesToRemove.Clear(); foreach (EntityCategory entityCategory in leftoverEntities) { if (IdentifiableEntity.Equals(entityCategory.Entity, entity)) entityCategoriesToRemove.Add(entityCategory); } //Remove from the list. foreach (EntityCategory entityCategoryToRemove in entityCategoriesToRemove) leftoverEntities.Remove(entityCategoryToRemove); #endregion foreach (LogWriter logWriter in createdLogWriters) logWriter.RemoveEntity(entity); } } #endregion } }