protected override void OnEventSourceCreated(EventSource eventSource) { // There is a possible race where we can get notified of the creation of our own internal logger during // construction of the log manager. The LogManager constructor helps handle this by explicitly calling // this function once it is safe to do so. if (eventSource is InternalLogger && InternalLogger.Write == null) { return; } lock (this.eventSourceInfosLock) { if (!this.eventSourceInfos.ContainsKey(eventSource)) { var updatedData = new EventSourceInfo(eventSource); this.eventSourceInfos[eventSource] = updatedData; InternalLogger.Write.NewEventSource(eventSource.Name, eventSource.Guid); } else { return; // we should have already done the work for this source. } } lock (this.loggersLock) { if (this.logConfigurations != null) { foreach (var kvp in this.logConfigurations) { LogConfiguration config = kvp.Value; // We need to update our loggers any time a config shows up where they had a dependency // that probably wasn't resolved. This will be the case either when it was a named source // or it was a GUID source on a type that can't directly subscribe to GUIDs (i.e. not an ETW // trace session) IEventLogger logger = null; switch (config.FileType) { case LoggerType.Console: logger = this.consoleLogger; break; case LoggerType.Network: logger = this.GetNamedNetworkLogger(kvp.Key); break; default: logger = this.GetNamedFileLogger(kvp.Key).Logger; break; } LogSourceLevels levels; if (config.NamedSources.TryGetValue(eventSource.Name, out levels) || (!config.HasFeature(LogConfiguration.Features.GuidSubscription) && config.GuidSources.TryGetValue(eventSource.Guid, out levels))) { ApplyConfigForEventSource(config, logger, eventSource, levels); } } } } }
private static bool ParseLogFilters(XmlNode xmlNode, LogConfiguration config) { bool clean = true; foreach (XmlNode source in xmlNode.SelectNodes(LogFilterTag)) { string filterValue = source.InnerText.Trim(); if (string.IsNullOrEmpty(filterValue)) { InternalLogger.Write.InvalidConfiguration("empty/invalid filter value"); clean = false; continue; } if (config.Filters.Contains(filterValue)) { InternalLogger.Write.InvalidConfiguration("duplicate filter value " + filterValue); clean = false; continue; } config.Filters.Add(filterValue); } return(clean); }
internal void Merge(LogConfiguration otherLog) { foreach (var sub in otherLog.subscriptions) { this.AddSubscription(sub); } foreach (var filter in otherLog.Filters) { this.AddFilter(filter); } }
private static void ApplyConfigForEventSource(LogConfiguration config, IEventLogger logger, EventSource source, LogSourceLevels levels) { if (config.HasFeature(LogConfiguration.Features.EventSourceSubscription)) { logger.SubscribeToEvents(source, levels.Level, levels.Keywords); } else if (config.HasFeature(LogConfiguration.Features.GuidSubscription)) { logger.SubscribeToEvents(source.Guid, levels.Level, levels.Keywords); } }
private static bool ParseLogNode(XmlNode xmlNode, LogConfiguration config) { var clean = true; foreach (XmlAttribute logAttribute in xmlNode.Attributes) { try { switch (logAttribute.Name.ToLower(CultureInfo.InvariantCulture)) { case LogBufferSizeAttribute: config.BufferSizeMB = int.Parse(logAttribute.Value); break; case LogDirectoryAttribute: config.Directory = logAttribute.Value; break; case LogFilenameTemplateAttribute: config.FilenameTemplate = logAttribute.Value; break; case LogTimestampLocal: config.TimestampLocal = bool.Parse(logAttribute.Value); break; case LogRotationAttribute: config.RotationInterval = int.Parse(logAttribute.Value); break; case LogHostnameAttribute: config.Hostname = logAttribute.Value; break; case LogPortAttribute: config.Port = ushort.Parse(logAttribute.Value); break; } } catch (Exception e) when(e is FormatException || e is OverflowException) { InternalLogger.Write.InvalidConfiguration($"Attribute {logAttribute.Name} has invalid value {logAttribute.Value} ({e.GetType()}: {e.Message})"); clean = false; } } return(clean); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var jObject = JObject.Load(reader); JToken token; string name = null; LogType type = LogType.None; if (jObject.TryGetValue(NameProperty, StringComparison.OrdinalIgnoreCase, out token)) { name = token.Value <string>(); } if (jObject.TryGetValue(TypeProperty, StringComparison.OrdinalIgnoreCase, out token)) { type = token.Value <string>().ToLogType(); } var sourcesArray = jObject.GetValue(SourcesProperty, StringComparison.OrdinalIgnoreCase); var sources = serializer.Deserialize <List <EventProviderSubscription> >(sourcesArray.CreateReader()); var filtersArray = jObject.GetValue(FiltersProperty, StringComparison.OrdinalIgnoreCase); var filters = filtersArray != null && filtersArray.HasValues ? serializer.Deserialize <List <string> >(filtersArray.CreateReader()) : new List <string>(); var log = new LogConfiguration(name, type, sources, filters); if (jObject.TryGetValue(BufferSizeMBProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.BufferSizeMB = token.Value <int>(); } if (jObject.TryGetValue(DirectoryProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.Directory = token.Value <string>(); } if (jObject.TryGetValue(FilenameTemplateProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.FilenameTemplate = token.Value <string>(); } if (jObject.TryGetValue(TimestampLocalProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.TimestampLocal = token.Value <bool>(); } if (jObject.TryGetValue(RotationIntervalProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.RotationInterval = token.Value <int>(); } if (jObject.TryGetValue(MaximumAgeProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.MaximumAge = token.Value <TimeSpan>(); } if (jObject.TryGetValue(MaximumSizeProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.MaximumSize = token.Value <int>(); } if (jObject.TryGetValue(HostnameProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.Hostname = token.Value <string>(); } if (jObject.TryGetValue(PortProperty, StringComparison.OrdinalIgnoreCase, out token)) { log.Port = token.Value <ushort>(); } return(log); }
private bool Equals(LogConfiguration other) { return(string.Equals(this.Name, other.Name, StringComparison.OrdinalIgnoreCase)); }
private static bool ParseLogSources(XmlNode xmlNode, LogConfiguration config) { bool clean = true; foreach (XmlNode source in xmlNode.SelectNodes(SourceTag)) { string sourceName = null; Guid sourceProvider = Guid.Empty; var sourceLevel = EventLevel.Informational; var sourceKeywords = (long)EventKeywords.None; foreach (XmlAttribute sourceAttribute in source.Attributes) { switch (sourceAttribute.Name.ToLower(CultureInfo.InvariantCulture)) { case SourceKeywordsAttribute: // Yes, really. The .NET integer TryParse methods will get PISSED if they see 0x in front of // hex digits. Dumb hack is dumb. string value = sourceAttribute.Value.Trim(); if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { value = value.Substring(2); } if (!long.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out sourceKeywords)) { InternalLogger.Write.InvalidConfiguration("invalid keywords value " + sourceAttribute.Value); clean = false; } break; case SourceMinSeverityAttribute: if (!Enum.TryParse(sourceAttribute.Value, true, out sourceLevel)) { InternalLogger.Write.InvalidConfiguration("invalid severity value " + sourceAttribute.Value); clean = false; } break; case SourceProviderIDAttribute: if (!Guid.TryParse(sourceAttribute.Value, out sourceProvider)) { InternalLogger.Write.InvalidConfiguration("invalid providerID GUID " + sourceAttribute.Value); clean = false; } break; case SourceProviderNameAttribute: sourceName = sourceAttribute.Value.Trim(); break; } } var levels = new LogSourceLevels(sourceLevel, (EventKeywords)sourceKeywords); if (sourceProvider != Guid.Empty) { config.GuidSources[sourceProvider] = levels; } else if (!string.IsNullOrEmpty(sourceName)) { config.NamedSources[sourceName] = levels; } else { InternalLogger.Write.InvalidConfiguration("source has neither name nor guid"); clean = false; } } return(clean); }
private static bool ParseLogNode(XmlNode xmlNode, LogConfiguration config) { bool clean = true; foreach (XmlAttribute logAttribute in xmlNode.Attributes) { switch (logAttribute.Name.ToLower(CultureInfo.InvariantCulture)) { case LogBufferSizeAttribute: if (!int.TryParse(logAttribute.Value, out config.BufferSize) || !IsValidFileBufferSize(config.BufferSize)) { InternalLogger.Write.InvalidConfiguration("invalid buffer size " + logAttribute.Value); config.BufferSize = DefaultFileBufferSizeMB; clean = false; } break; case LogDirectoryAttribute: if (!IsValidDirectory(logAttribute.Value)) { InternalLogger.Write.InvalidConfiguration("invalid directory name " + logAttribute.Value); clean = false; } else { config.Directory = logAttribute.Value; } break; case LogFilenameTemplateAttribute: if (!FileBackedLogger.IsValidFilenameTemplate(logAttribute.Value)) { InternalLogger.Write.InvalidConfiguration("invalid filename template " + logAttribute.Value); clean = false; } else { config.FilenameTemplate = logAttribute.Value; } break; case LogTimestampLocal: if (!bool.TryParse(logAttribute.Value, out config.TimestampLocal)) { InternalLogger.Write.InvalidConfiguration("invalid timestamplocal value " + logAttribute.Value); config.TimestampLocal = false; clean = false; } break; case LogRotationAttribute: if (!int.TryParse(logAttribute.Value, out config.RotationInterval) || !IsValidRotationInterval(config.RotationInterval)) { InternalLogger.Write.InvalidConfiguration("invalid rotation interval " + logAttribute.Value); config.RotationInterval = DefaultRotationInterval; clean = false; } break; case LogHostnameAttribute: if (Uri.CheckHostName(logAttribute.Value) == UriHostNameType.Unknown) { InternalLogger.Write.InvalidConfiguration("invalid hostname name " + logAttribute.Value); clean = false; } else { config.Hostname = logAttribute.Value; } break; case LogPortAttribute: if (!int.TryParse(logAttribute.Value, out config.Port) || config.Port <= 0) { InternalLogger.Write.InvalidConfiguration("invalid port " + logAttribute.Value); config.Port = 80; clean = false; } break; } } clean &= ParseLogSources(xmlNode, config); clean &= ParseLogFilters(xmlNode, config); return(clean); }
private bool ApplyConfiguration() { var newConfig = new Dictionary <string, LogConfiguration>(StringComparer.OrdinalIgnoreCase); if (!ParseConfiguration(this.configurationFileData, newConfig) || !ParseConfiguration(this.configurationData, newConfig)) { return(false); } lock (this.loggersLock) { foreach (var logger in this.fileLoggers.Values) { logger.Dispose(); } foreach (var logger in this.networkLoggers.Values) { logger.Dispose(); } this.fileLoggers.Clear(); this.networkLoggers.Clear(); this.logConfigurations = newConfig; foreach (var kvp in this.logConfigurations) { string loggerName = kvp.Key; LogConfiguration loggerConfig = kvp.Value; IEventLogger logger; if (loggerConfig.FileType == LoggerType.Console) { // We re-create the console logger to clear its config (since we don't have a better way // to do that right now). this.CreateConsoleLogger(); logger = this.consoleLogger; } else if (loggerConfig.FileType == LoggerType.Network) { logger = this.CreateNetLogger(loggerName, loggerConfig.Hostname, loggerConfig.Port); } else { logger = this.CreateFileLogger(loggerConfig.FileType, loggerName, loggerConfig.Directory, loggerConfig.BufferSize, loggerConfig.RotationInterval, loggerConfig.FilenameTemplate, loggerConfig.TimestampLocal); } foreach (var f in loggerConfig.Filters) { logger.AddRegexFilter(f); } // Build a collection of all desired subscriptions so that we can subscribe in bulk at the end. // We do this because ordering may matter to specific types of loggers and they are best suited to // manage that internally. var subscriptions = new List <EventProviderSubscription>(); foreach (var ns in loggerConfig.NamedSources) { EventSourceInfo sourceInfo; string name = ns.Key; LogSourceLevels levels = ns.Value; if ((sourceInfo = GetEventSourceInfo(name)) != null) { subscriptions.Add(new EventProviderSubscription(sourceInfo.Source) { MinimumLevel = levels.Level, Keywords = levels.Keywords }); } } foreach (var gs in loggerConfig.GuidSources) { EventSourceInfo sourceInfo; Guid guid = gs.Key; LogSourceLevels levels = gs.Value; if (loggerConfig.HasFeature(LogConfiguration.Features.GuidSubscription)) { subscriptions.Add(new EventProviderSubscription(guid) { MinimumLevel = levels.Level, Keywords = levels.Keywords }); } else if (loggerConfig.HasFeature(LogConfiguration.Features.EventSourceSubscription) && (sourceInfo = GetEventSourceInfo(guid)) != null) { subscriptions.Add(new EventProviderSubscription(sourceInfo.Source) { MinimumLevel = levels.Level, Keywords = levels.Keywords }); } } logger.SubscribeToEvents(subscriptions); } } return(true); }
private static bool ParseConfiguration(string configurationXml, Dictionary <string, LogConfiguration> loggers) { bool clean = true; // used to track whether any errors were encountered if (string.IsNullOrEmpty(configurationXml)) { return(true); // it's okay to have nothing at all } var configuration = new XmlDocument(); try { configuration.LoadXml(configurationXml); } catch (XmlException) { InternalLogger.Write.InvalidConfiguration("Configuration was not valid XML"); return(false); } XmlNode node = configuration.SelectSingleNode(EtwOverrideXpath); if (node != null) { XmlNode setting = node.Attributes.GetNamedItem(EtwOverrideEnabledAttribute); bool isEnabled = (AllowEtwLogging == AllowEtwLoggingValues.Enabled); if (setting == null || !bool.TryParse(setting.Value, out isEnabled)) { InternalLogger.Write.InvalidConfiguration(EtwOverrideXpath + " tag has invalid " + EtwOverrideEnabledAttribute + " attribute"); clean = false; } AllowEtwLogging = isEnabled ? AllowEtwLoggingValues.Enabled : AllowEtwLoggingValues.Disabled; } foreach (XmlNode log in configuration.SelectNodes(LogTagXpath)) { string name = GetLogNameFromNode(log); LoggerType type = GetLogTypeFromNode(log); if (type == LoggerType.None) { // GetLogTypeFromNode logs this particular error. clean = false; continue; } if (type == LoggerType.Console) { if (name != null) { InternalLogger.Write.InvalidConfiguration("console log should not have a name"); clean = false; } // We use a special name for the console logger that is invalid for file loggers so we can track // it along with them. name = ConsoleLoggerName; } else { if (string.IsNullOrEmpty(name)) { InternalLogger.Write.InvalidConfiguration("cannot configure a log with no name"); clean = false; continue; } if (name.IndexOfAny(Path.GetInvalidFileNameChars()) != -1) { InternalLogger.Write.InvalidConfiguration("base name of log is invalid " + name); clean = false; continue; } if (type == LoggerType.ETLFile && AllowEtwLogging == AllowEtwLoggingValues.Disabled) { InternalLogger.Write.OverridingEtwLogging(name); type = LoggerType.TextLogFile; } } // We wish to update existing configuration where possible. LogConfiguration config; if (!loggers.TryGetValue(name, out config)) { config = new LogConfiguration(); } config.FileType = type; clean &= ParseLogNode(log, config); if (config.NamedSources.Count + config.GuidSources.Count == 0) { InternalLogger.Write.InvalidConfiguration("log destination " + name + " has no valid sources"); clean = false; continue; } // Ensure what we got has been sanitized. We currently don't do stringent checks on the console logger // to see if useless stuff like a rotation interval or buffer size is set, but could in the future get // more picky. if (config.Filters.Count > 0 && !config.HasFeature(LogConfiguration.Features.RegexFilter)) { InternalLogger.Write.InvalidConfiguration("log destination " + name + " has filters but type " + type + " does not support this feature."); clean = false; config.Filters.Clear(); } loggers[name] = config; } return(clean); }
private static bool ParseXmlConfiguration(XmlDocument xDocument, out Configuration configuration) { var clean = true; var allowEtwLogging = AllowEtwLoggingValues.None; var logs = new HashSet <LogConfiguration>(); XmlNode node = xDocument.SelectSingleNode(EtwOverrideXpath); if (node != null) { XmlNode setting = node.Attributes.GetNamedItem(EtwOverrideEnabledAttribute); bool isEnabled; if (setting == null || !bool.TryParse(setting.Value, out isEnabled)) { InternalLogger.Write.InvalidConfiguration(EtwOverrideXpath + " tag has invalid " + EtwOverrideEnabledAttribute + " attribute"); clean = false; } else { allowEtwLogging = isEnabled ? AllowEtwLoggingValues.Enabled : AllowEtwLoggingValues.Disabled; } } foreach (XmlNode log in xDocument.SelectNodes(LogTagXpath)) { string name = null; LogType type; if (log.Attributes[LogNameAttribute] != null) { name = log.Attributes[LogNameAttribute].Value.Trim(); } // If no type is provided we currently default to text. if (log.Attributes[LogTypeAttribute] == null) { type = LogType.Text; } else { type = log.Attributes[LogTypeAttribute].Value.ToLogType(); } if (type == LogType.None) { InternalLogger.Write.InvalidConfiguration("invalid log type " + log.Attributes[LogTypeAttribute].Value); clean = false; continue; } if (type == LogType.Console) { if (name != null) { InternalLogger.Write.InvalidConfiguration("console log should not have a name"); clean = false; } } // If a log is listed in duplicates we will discard the previous data entirely. This is a change from historic // (pre OSS-release) behavior which was... quasi-intentional shall we say. The author is unaware of anybody // using this capability and, since it confusing at best, would like for it to go away. try { List <EventProviderSubscription> subscriptions; List <string> filters; clean &= ParseLogSources(log, out subscriptions); ParseLogFilters(log, out filters); var config = new LogConfiguration(name, type, subscriptions, filters); clean &= ParseLogNode(log, config); config.Validate(); if (!logs.Add(config)) { InternalLogger.Write.InvalidConfiguration($"duplicate log {log.Name} discarded."); clean = false; } } catch (InvalidConfigurationException e) { InternalLogger.Write.InvalidConfiguration(e.Message); clean = false; } } configuration = logs.Count > 0 ? new Configuration(logs, allowEtwLogging) : null; return(clean); }