diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.csproj b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.csproj index 32c177dd04b..1effc0581bf 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.csproj +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.csproj @@ -70,7 +70,7 @@ - + diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs index afb28d94448..36a604d4e1c 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs @@ -165,7 +165,8 @@ public virtual async Task StartAsync(string command, string[] args, Cancellation _adapter.Environment.Add("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", TestHostingStartupAssemblyName); // Set configuration via environment variables - var configurationViaEnvironment = ConfigurationFromEnvironment.ToEnvironmentConfiguration(useDotnetMonitorPrefix: true); + var optionsMapper = new CommonOptionsMapper(); + var configurationViaEnvironment = optionsMapper.ToEnvironmentConfiguration(ConfigurationFromEnvironment, useDotnetMonitorPrefix: true); if (configurationViaEnvironment.Count > 0) { // Set additional environment variables from configuration @@ -197,7 +198,8 @@ protected virtual void StandardOutputCallback(string line) public void WriteKeyPerValueConfiguration(RootOptions options) { - foreach (KeyValuePair entry in options.ToKeyPerFileConfiguration()) + CommonOptionsMapper optionsMapper = new(); + foreach (KeyValuePair entry in optionsMapper.ToKeyPerFileConfiguration(options)) { File.WriteAllText( Path.Combine(SharedConfigDirectoryPath, entry.Key), diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTestCommon/TestHostHelper.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTestCommon/TestHostHelper.cs index 02c48fa9433..bef5038156b 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTestCommon/TestHostHelper.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTestCommon/TestHostHelper.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using Xunit.Abstractions; @@ -73,7 +74,9 @@ public static IHost CreateHost( RootOptions options = new(); setup(options); - IDictionary configurationValues = options.ToConfigurationValues(); + CommonOptionsMapper optionsMapper = new(); + optionsMapper.AddActionSettings(nameof(PassThroughAction), MapPassThroughOptions); + IDictionary configurationValues = optionsMapper.ToConfigurationValues(options); outputHelper.WriteLine("Begin Configuration:"); foreach ((string key, string value) in configurationValues) { @@ -128,5 +131,21 @@ public static IHost CreateHost( }) .Build(); } + + private static void MapPassThroughOptions(PassThroughOptions obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = FormattableString.Invariant($"{valueName}{separator}"); + MapString(obj.Input1, FormattableString.Invariant($"{prefix}{nameof(obj.Input1)}")); + MapString(obj.Input2, FormattableString.Invariant($"{prefix}{nameof(obj.Input2)}")); + MapString(obj.Input3, FormattableString.Invariant($"{prefix}{nameof(obj.Input3)}")); + } + + void MapString(string value, string valueName) + { + map.Add(valueName, ConvertUtils.ToString(value, CultureInfo.InvariantCulture)); + } + } } } diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ActionDependencyAnalyzerTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ActionDependencyAnalyzerTests.cs index 5e55d62eecf..0644ea01ab5 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ActionDependencyAnalyzerTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ActionDependencyAnalyzerTests.cs @@ -142,14 +142,14 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => [Fact] public async Task ProcessInfoTest() { - PassThroughOptions settings = null; + CollectionRuleActionOptions actionOptions = null; await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => { CollectionRuleOptions options = rootOptions.CreateCollectionRule(DefaultRuleName) .AddPassThroughAction("a1", ConfigurationTokenParser.ProcessNameReference, ConfigurationTokenParser.ProcessIdReference, ConfigurationTokenParser.CommandLineReference) .SetStartupTrigger(); - settings = (PassThroughOptions)options.Actions.Last().Settings; + actionOptions = options.Actions.Last(); }, host => { using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeoutMs); @@ -157,6 +157,7 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => CollectionRuleOptions ruleOptions = host.Services.GetRequiredService>().Get(DefaultRuleName); ILogger logger = host.Services.GetRequiredService>(); TimeProvider timeProvider = host.Services.GetRequiredService(); + ICollectionRuleActionOperations actionOperations = host.Services.GetRequiredService(); const string processName = "actionProcess"; const int processId = 123; @@ -165,8 +166,8 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => Guid instanceId = Guid.NewGuid(); CollectionRuleContext context = new(DefaultRuleName, ruleOptions, new TestProcessInfo(instanceId, processId: processId, commandLine: commandLine), HostInfo.GetCurrent(timeProvider), logger); - ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context); - PassThroughOptions newSettings = (PassThroughOptions)analyzer.SubstituteOptionValues(new Dictionary(), 1, settings); + ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context, actionOperations); + PassThroughOptions newSettings = (PassThroughOptions)analyzer.SubstituteOptionValues(new Dictionary(), 1, actionOptions); Assert.Equal(processName, newSettings.Input1); Assert.Equal(processId.ToString(CultureInfo.InvariantCulture), newSettings.Input2); @@ -181,14 +182,14 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => [Fact] public async Task HostInfoTest() { - PassThroughOptions settings = null; + CollectionRuleActionOptions actionOptions = null; await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => { CollectionRuleOptions options = rootOptions.CreateCollectionRule(DefaultRuleName) .AddPassThroughAction("a1", ConfigurationTokenParser.HostNameReference, ConfigurationTokenParser.UnixTimeReference, "test") .SetStartupTrigger(); - settings = (PassThroughOptions)options.Actions.Last().Settings; + actionOptions = options.Actions.Last(); }, host => { using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeoutMs); @@ -196,14 +197,15 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => CollectionRuleOptions ruleOptions = host.Services.GetRequiredService>().Get(DefaultRuleName); ILogger logger = host.Services.GetRequiredService>(); MockTimeProvider timeProvider = host.Services.GetRequiredService() as MockTimeProvider; + ICollectionRuleActionOperations actionOperations = host.Services.GetRequiredService(); const string hostName = "exampleHost"; Guid instanceId = Guid.NewGuid(); HostInfo hostInfo = new HostInfo(hostName, timeProvider); CollectionRuleContext context = new(DefaultRuleName, ruleOptions, new TestProcessInfo(instanceId), hostInfo, logger); - ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context); - PassThroughOptions newSettings = (PassThroughOptions)analyzer.SubstituteOptionValues(new Dictionary(), 1, settings); + ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context, actionOperations); + PassThroughOptions newSettings = (PassThroughOptions)analyzer.SubstituteOptionValues(new Dictionary(), 1, actionOptions); Assert.Equal(hostName, newSettings.Input1); Assert.Equal(hostInfo.TimeProvider.GetUtcNow().ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture), newSettings.Input2); @@ -223,7 +225,7 @@ public async Task InvalidTokenReferenceTest() string a2input3 = "$(Actions.a1.MissingResult)"; LogRecord record = new LogRecord(); - PassThroughOptions settings = null; + CollectionRuleActionOptions actionOptions = null; await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => { CollectionRuleOptions options = rootOptions.CreateCollectionRule(DefaultRuleName) @@ -231,7 +233,7 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => .AddPassThroughAction("a2", a2input1, a2input2, a2input3) .SetStartupTrigger(); - settings = (PassThroughOptions)options.Actions.Last().Settings; + actionOptions = options.Actions.Last(); }, host => { using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeoutMs); @@ -239,13 +241,14 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => CollectionRuleOptions ruleOptions = host.Services.GetRequiredService>().Get(DefaultRuleName); ILogger logger = host.Services.GetRequiredService>(); TimeProvider timeProvider = host.Services.GetRequiredService(); + ICollectionRuleActionOperations actionOperations = host.Services.GetRequiredService(); Guid instanceId = Guid.NewGuid(); CollectionRuleContext context = new(DefaultRuleName, ruleOptions, new TestProcessInfo(instanceId), HostInfo.GetCurrent(timeProvider), logger); - ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context); + ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context, actionOperations); analyzer.GetActionDependencies(1); - analyzer.SubstituteOptionValues(new Dictionary(), 1, settings); + analyzer.SubstituteOptionValues(new Dictionary(), 1, actionOptions); Assert.Equal(3, record.Events.Count); Assert.Equal(LoggingEventIds.InvalidActionReferenceToken.Id(), record.Events[0].EventId.Id); @@ -264,27 +267,28 @@ await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => [Fact] public async Task RuntimeIdReferenceTest() { - PassThroughOptions settings = null; + CollectionRuleActionOptions actionOptions = null; await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => { CollectionRuleOptions options = rootOptions.CreateCollectionRule(DefaultRuleName) .AddPassThroughAction("a1", ConfigurationTokenParser.RuntimeIdReference, "test", "test") .SetStartupTrigger(); - settings = (PassThroughOptions)options.Actions.Last().Settings; - }, host => + actionOptions = options.Actions.Last(); + }, host => { using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeoutMs); CollectionRuleOptions ruleOptions = host.Services.GetRequiredService>().Get(DefaultRuleName); ILogger logger = host.Services.GetRequiredService>(); TimeProvider timeProvider = host.Services.GetRequiredService(); + ICollectionRuleActionOperations actionOperations = host.Services.GetRequiredService(); Guid instanceId = Guid.NewGuid(); CollectionRuleContext context = new(DefaultRuleName, ruleOptions, new TestProcessInfo(instanceId), HostInfo.GetCurrent(timeProvider), logger); - ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context); - PassThroughOptions newSettings = (PassThroughOptions)analyzer.SubstituteOptionValues(new Dictionary(), 1, settings); + ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context, actionOperations); + PassThroughOptions newSettings = (PassThroughOptions)analyzer.SubstituteOptionValues(new Dictionary(), 1, actionOptions); Assert.Equal(instanceId.ToString("D"), newSettings.Input1); diff --git a/src/Tools/dotnet-monitor/CommonOptionsExtensions.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CommonOptionsExtensions.cs similarity index 95% rename from src/Tools/dotnet-monitor/CommonOptionsExtensions.cs rename to src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CommonOptionsExtensions.cs index 718ffa4f27a..0a4fe8dfbce 100644 --- a/src/Tools/dotnet-monitor/CommonOptionsExtensions.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/CommonOptionsExtensions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable @@ -6,6 +6,7 @@ #if UNITTEST using Microsoft.Diagnostics.Monitoring.TestCommon; #endif +using Microsoft.Diagnostics.Tools.Monitor; using Microsoft.Extensions.Configuration; using System; using System.Collections; @@ -15,7 +16,7 @@ using System.Reflection; using System.Text; -namespace Microsoft.Diagnostics.Tools.Monitor +namespace Microsoft.Diagnostics.Monitoring.Tool.UnitTests { internal static class CommonOptionsExtensions { @@ -118,7 +119,8 @@ private static void MapValue(object? value, string valueName, string separator, valueType.IsEnum || typeof(Guid) == valueType || typeof(string) == valueType || - typeof(TimeSpan) == valueType) + typeof(TimeSpan) == valueType || + typeof(Uri) == valueType) { map.Add( valueName, diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ConfigurationTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ConfigurationTests.cs index 25c2f68d179..e8020bd255b 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ConfigurationTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/ConfigurationTests.cs @@ -5,6 +5,7 @@ using Microsoft.Diagnostics.Monitoring.TestCommon; using Microsoft.Diagnostics.Tools.Monitor; using Microsoft.Diagnostics.Tools.Monitor.Auth; +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -363,6 +364,132 @@ private string WriteAndRetrieveConfiguration(IConfiguration configuration, bool } } + [Theory] + [MemberData(nameof(GetTriggerOptionsTestData))] + public void ConfigurationMappingTriggerTest(Type triggerOptionsType, string triggerTypeName) + { + var actionData = GetActionOptionsTestData().First(); + var actionType = (Type)actionData[0]; + var actionName = (string)actionData[1]; + + var optionsFactory = new TestOptionsFactory( + triggerOptionsType, + actionType, + triggerTypeName, + actionName); + + var rootOptions = optionsFactory.CreateRootOptions(); + + ValidateOptionsMapping(rootOptions); + } + + [Theory] + [MemberData(nameof(GetActionOptionsTestData))] + public void ConfigurationMappingActionTest(Type actionOptionsType, string actionTypeName) + { + var triggerData = GetTriggerOptionsTestData().First(); + var triggerType = (Type)triggerData[0]; + var triggerName = (string)triggerData[1]; + + var optionsFactory = new TestOptionsFactory( + triggerType, + actionOptionsType, + triggerName, + actionTypeName); + + var rootOptions = optionsFactory.CreateRootOptions(); + + ValidateOptionsMapping(rootOptions); + } + + private static void ValidateOptionsMapping(RootOptions rootOptions) + { + var optionsMapper = new CommonOptionsMapper(); + + IDictionary expectedConfigurationValues = rootOptions.ToConfigurationValues(); + var configurationValues = optionsMapper.ToConfigurationValues(rootOptions); + ValidateMapping(expectedConfigurationValues, configurationValues); + + IDictionary expectedEnvironmentConfiguration = rootOptions.ToEnvironmentConfiguration(); + var environmentConfiguration = optionsMapper.ToEnvironmentConfiguration(rootOptions); + ValidateMapping(expectedEnvironmentConfiguration, environmentConfiguration); + + Assert.Equal(expectedEnvironmentConfiguration.Count, environmentConfiguration.Count); + IDictionary expectedKeyPerFileConfiguration = rootOptions.ToKeyPerFileConfiguration(); + var keyPerFileConfiguration = optionsMapper.ToKeyPerFileConfiguration(rootOptions); + + ValidateMapping(expectedKeyPerFileConfiguration, keyPerFileConfiguration); + + static void ValidateMapping(IDictionary expected, IDictionary actual) + { + foreach (var kvp in expected) + { + Assert.True(actual.TryGetValue(kvp.Key, out string value), + $"Key {kvp.Key} not found in configuration values."); + Assert.Equal(kvp.Value, value); + } + + Assert.Equal(expected.Count, actual.Count); + } + } + + public static IEnumerable GetTriggerOptionsTestData() + { + var triggerConstants = typeof(KnownCollectionRuleTriggers) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Select(f => (string)f.GetValue(null)); + + foreach (var triggerName in triggerConstants) + { + if (triggerName == KnownCollectionRuleTriggers.Startup) + continue; + + string expectedTypeName = $"{triggerName}Options"; + + Type optionsType = FindOptionsType(expectedTypeName, + "Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Triggers.EventCounterShortcuts", + "Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Triggers"); + + Assert.NotNull(optionsType); + yield return new object[] { optionsType, triggerName }; + } + } + + public static IEnumerable GetActionOptionsTestData() + { + var actionConstants = typeof(KnownCollectionRuleActions) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Select(f => (string)f.GetValue(null)); + + foreach (var actionName in actionConstants) + { + string expectedTypeName = $"{actionName}Options"; + + Type optionsType = FindOptionsType(expectedTypeName, + "Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Actions"); + + Assert.NotNull(optionsType); + yield return new object[] { optionsType, actionName }; + } + } + + private static Type FindOptionsType(string typeName, params string[] namespaces) + { + var assembly = typeof(KnownCollectionRuleActions).Assembly; + + foreach (var ns in namespaces) + { + string fullTypeName = $"{ns}.{typeName}"; + Type type = assembly.GetType(fullTypeName); + if (type != null) + { + return type; + } + } + + return null; + } + private static string CleanWhitespace(string rawText) { return string.Concat(rawText.Where(c => !char.IsWhiteSpace(c))); diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestOptionsFactory.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestOptionsFactory.cs new file mode 100644 index 00000000000..4e7e1330723 --- /dev/null +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestOptionsFactory.cs @@ -0,0 +1,195 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using Microsoft.Diagnostics.Tools.Monitor; +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.Diagnostics.Monitoring.Tool.UnitTests +{ + internal sealed class TestOptionsFactory + { + private Type TriggerOptionsType { get; } + private Type ActionOptionsType { get; } + private string TriggerTypeName { get; } + private string ActionTypeName { get; } + + // Cache property info for polymorphic types that need special handling + private static readonly PropertyInfo CollectionRuleTriggerOptionsSettingsProperty = typeof(CollectionRuleTriggerOptions).GetProperty(nameof(CollectionRuleTriggerOptions.Settings))!; + private static readonly PropertyInfo CollectionRuleActionOptionsSettingsProperty = typeof(CollectionRuleActionOptions).GetProperty(nameof(CollectionRuleActionOptions.Settings))!; + + public TestOptionsFactory(Type triggerOptionsType, Type actionOptionsType, string triggerTypeName, string actionTypeName) + { + TriggerOptionsType = triggerOptionsType; + ActionOptionsType = actionOptionsType; + TriggerTypeName = triggerTypeName; + ActionTypeName = actionTypeName; + } + + public RootOptions CreateRootOptions() + { + return (RootOptions)CreateObject(typeof(RootOptions)); + } + + public object CreateObject(Type type, PropertyInfo? propertyInfo = null) + { + if (TryCreateBuiltInObject(type, propertyInfo, out object? obj)) + { + return obj!; + } + + obj = Activator.CreateInstance(type)!; + + foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (!property.GetIndexParameters().Any()) + { + object value = CreateValue(property.PropertyType, property); + property.SetValue(obj, value); + } + } + + return obj; + } + + public bool TryCreateBuiltInObject(Type type, PropertyInfo? propertyInfo, out object? obj) + { + // Handle special cases for polymorphic types + if (type == typeof(CollectionRuleTriggerOptions)) + { + obj = CreateCollectionRuleTriggerOptions(); + return true; + } + + if (type == typeof(CollectionRuleActionOptions)) + { + obj = CreateCollectionRuleActionOptions(); + return true; + } + + obj = null; + return false; + } + + public object CreateValue(Type type, PropertyInfo? propertyInfo = null) + { + if (Nullable.GetUnderlyingType(type) is Type underlyingType) + { + type = underlyingType; + } + + if (type.IsPrimitive || + type.IsEnum || + typeof(Guid) == type || + typeof(string) == type || + typeof(TimeSpan) == type || + typeof(Uri) == type) + { + return CreateBuiltInValue(type); + } + else + { + if (TryCreateDictionary(type, out IDictionary dictionary)) + { + return dictionary; + } + else if (TryCreateList(type, out IList list)) + { + return list; + } + else + { + return CreateObject(type, propertyInfo); + } + } + } + + public static object CreateBuiltInValue(Type type) + { + if (type == typeof(string)) + { + return "SomeString"; + } + else if (type == typeof(Uri)) + { + return new Uri("http://localhost:5000"); + } + + return Activator.CreateInstance(type)!; + } + + CollectionRuleTriggerOptions CreateCollectionRuleTriggerOptions() + { + var triggerOptions = new CollectionRuleTriggerOptions(); + triggerOptions.Type = TriggerTypeName; + triggerOptions.Settings = CreateValue(TriggerOptionsType, CollectionRuleTriggerOptionsSettingsProperty); + return triggerOptions; + } + + CollectionRuleActionOptions CreateCollectionRuleActionOptions() + { + var actionOptions = new CollectionRuleActionOptions(); + actionOptions.Type = ActionTypeName; + actionOptions.Settings = CreateValue(ActionOptionsType, CollectionRuleActionOptionsSettingsProperty); + return actionOptions; + } + + bool TryCreateDictionary(Type type, out IDictionary dictionary) + { + dictionary = null!; + + foreach (var interfaceType in type.GetInterfaces().Concat([type])) + { + if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + { + Type[] genericArguments = interfaceType.GetGenericArguments(); + Type keyType = genericArguments[0]; + Type valueType = genericArguments[1]; + + Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + var dictionaryInstance = (IDictionary)Activator.CreateInstance(dictionaryType)!; + object key = CreateValue(keyType); + object value = CreateValue(valueType); + dictionaryInstance.Add(key, value); + + dictionary = dictionaryInstance; + return true; + } + } + + return false; + } + + bool TryCreateList(Type type, out IList list) + { + list = null!; + + if (type.IsArray) + { + Type elementType = type.GetElementType()!; + Array array = Array.CreateInstance(elementType, 1); + array.SetValue(CreateValue(elementType), 0); + list = array; + return true; + } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) + { + Type valueType = type.GetGenericArguments()[0]; + var listInstance = (IList)Activator.CreateInstance(type)!; + listInstance.Add(CreateValue(valueType)); + + list = listInstance; + return true; + } + + return false; + } + } +} diff --git a/src/Tools/dotnet-monitor/CollectionRules/ActionListExecutor.cs b/src/Tools/dotnet-monitor/CollectionRules/ActionListExecutor.cs index bafe19a5390..00ed55d906c 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/ActionListExecutor.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/ActionListExecutor.cs @@ -49,7 +49,7 @@ public async Task> ExecuteAction List deferredCompletions = new(context.Options.Actions.Count); var actionResults = new Dictionary(StringComparer.Ordinal); - var dependencyAnalyzer = ActionOptionsDependencyAnalyzer.Create(context); + var dependencyAnalyzer = ActionOptionsDependencyAnalyzer.Create(context, _actionOperations); try { @@ -91,7 +91,7 @@ public async Task> ExecuteAction )); } - object? newSettings = dependencyAnalyzer.SubstituteOptionValues(actionResults, actionIndex, actionOption.Settings); + object? newSettings = dependencyAnalyzer.SubstituteOptionValues(actionResults, actionIndex, actionOption); ICollectionRuleAction? action = factory.Create(context.ProcessInfo, newSettings); try diff --git a/src/Tools/dotnet-monitor/CollectionRules/ActionOptionsDependencyAnalyzer.cs b/src/Tools/dotnet-monitor/CollectionRules/ActionOptionsDependencyAnalyzer.cs index 2efb134cf1f..9b35df998cf 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/ActionOptionsDependencyAnalyzer.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/ActionOptionsDependencyAnalyzer.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; using System.Reflection; using System.Text; @@ -34,6 +35,7 @@ internal sealed class ActionOptionsDependencyAnalyzer private readonly CollectionRuleContext _ruleContext; private readonly ConfigurationTokenParser _tokenParser; + private readonly ICollectionRuleActionOperations _actionOperations; //Use action index instead of name, since it's possible for an unnamed action to have named dependencies. #nullable disable @@ -77,17 +79,18 @@ public string GetActionResultToken() => string.Concat(ActionReferencePrefix, Action.Name, ConfigurationTokenParser.Separator, ResultName, ConfigurationTokenParser.SubstitutionSuffix); } - public static ActionOptionsDependencyAnalyzer Create(CollectionRuleContext context) + public static ActionOptionsDependencyAnalyzer Create(CollectionRuleContext context, ICollectionRuleActionOperations actionOperations) { - var analyzer = new ActionOptionsDependencyAnalyzer(context, new ConfigurationTokenParser(context.Logger)); + var analyzer = new ActionOptionsDependencyAnalyzer(context, new ConfigurationTokenParser(context.Logger), actionOperations); analyzer.EnsureDependencies(); return analyzer; } - private ActionOptionsDependencyAnalyzer(CollectionRuleContext context, ConfigurationTokenParser tokenParser) + private ActionOptionsDependencyAnalyzer(CollectionRuleContext context, ConfigurationTokenParser tokenParser, ICollectionRuleActionOperations actionOperations) { _ruleContext = context ?? throw new ArgumentNullException(nameof(context)); _tokenParser = tokenParser ?? throw new ArgumentNullException(nameof(tokenParser)); + _actionOperations = actionOperations ?? throw new ArgumentNullException(nameof(actionOperations)); } #nullable disable @@ -112,8 +115,9 @@ public IList GetActionDependencies(int actionIndex) } #nullable restore - public object? SubstituteOptionValues(IDictionary actionResults, int actionIndex, object? settings) + public object? SubstituteOptionValues(IDictionary actionResults, int actionIndex, CollectionRuleActionOptions actionOptions) { + object? settings = actionOptions.Settings; //Attempt to substitute context properties. object? originalSettings = settings; @@ -160,7 +164,11 @@ public IList GetActionDependencies(int actionIndex) } string? commandLine = _ruleContext.EndpointInfo.CommandLine; - settings = _tokenParser.SubstituteOptionValues(settings, new TokenContext + if (!_actionOperations.TryGetOptionsType(actionOptions.Type, out Type settingsType)) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Strings.ErrorMessage_UnknownActionType, actionOptions.Name)); + } + settings = _tokenParser.SubstituteOptionValues(settings, settingsType, new TokenContext { CloneOnSubstitution = ReferenceEquals(originalSettings, settings), RuntimeId = _ruleContext.EndpointInfo.RuntimeInstanceCookie, @@ -191,7 +199,11 @@ private void EnsureDependencies() private void EnsureDependencies(CollectionRuleActionOptions options, int actionIndex) { - foreach (PropertyInfo property in GetDependencyPropertiesFromSettings(options)) + if (!_actionOperations.TryGetOptionsType(options.Type, out Type optionsType)) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Strings.ErrorMessage_UnknownActionType, options.Name)); + } + foreach (PropertyInfo property in GetDependencyPropertiesFromSettings(optionsType)) { string? originalValue = (string?)property.GetValue(options.Settings); if (string.IsNullOrEmpty(originalValue)) @@ -287,9 +299,9 @@ private bool GetActionResultReference(string actionReference, int actionIndex, return true; } - private static IEnumerable GetDependencyPropertiesFromSettings(CollectionRuleActionOptions options) + private static IEnumerable GetDependencyPropertiesFromSettings(Type optionsType) { - return ConfigurationTokenParser.GetPropertiesFromSettings(options.Settings, p => p.GetCustomAttributes(typeof(ActionOptionsDependencyPropertyAttribute), inherit: true).Any()); + return ConfigurationTokenParser.GetPropertiesFromSettings(optionsType, p => p.GetCustomAttributes(typeof(ActionOptionsDependencyPropertyAttribute), inherit: true).Any()); } } } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectionRuleActionOperations.cs b/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectionRuleActionOperations.cs index 746af4f906a..9d8d78d53b1 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectionRuleActionOperations.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Actions/CollectionRuleActionOperations.cs @@ -93,5 +93,20 @@ public bool TryValidateOptions( return false; } } + + /// + public bool TryGetOptionsType( + string actionName, + out Type optionsType) + { + if (_map.TryGetValue(actionName, out ICollectionRuleActionDescriptor descriptor)) + { + optionsType = descriptor.OptionsType; + return true; + } + + optionsType = null; + return false; + } } } diff --git a/src/Tools/dotnet-monitor/CollectionRules/Actions/ICollectionRuleActionOperations.cs b/src/Tools/dotnet-monitor/CollectionRules/Actions/ICollectionRuleActionOperations.cs index 075f3195f0c..459cfeade80 100644 --- a/src/Tools/dotnet-monitor/CollectionRules/Actions/ICollectionRuleActionOperations.cs +++ b/src/Tools/dotnet-monitor/CollectionRules/Actions/ICollectionRuleActionOperations.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using Microsoft.Extensions.Configuration; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -40,5 +41,13 @@ bool TryValidateOptions( object options, ValidationContext validationContext, ICollection results); + + /// + /// Attempts to get the type of the options instance + /// associated with the registered action name. + /// + bool TryGetOptionsType( + string actionName, + out Type optionsType); } } diff --git a/src/Tools/dotnet-monitor/Commands/GenerateApiKeyCommandHandler.cs b/src/Tools/dotnet-monitor/Commands/GenerateApiKeyCommandHandler.cs index a13b184fcdc..020fb19b5c4 100644 --- a/src/Tools/dotnet-monitor/Commands/GenerateApiKeyCommandHandler.cs +++ b/src/Tools/dotnet-monitor/Commands/GenerateApiKeyCommandHandler.cs @@ -67,7 +67,8 @@ public static void Invoke(OutputFormat output, TimeSpan expiration, TextWriter o { // Create configuration from object model. MemoryConfigurationSource source = new(); - source.InitialData = (IDictionary)opts.ToConfigurationValues(); // Cast the values as nullable, since they are reference types we can safely do this. + CommonOptionsMapper optionsMapper = new(); + source.InitialData = (IDictionary)optionsMapper.ToConfigurationValues(opts); // Cast the values as nullable, since they are reference types we can safely do this. ConfigurationBuilder builder = new(); builder.Add(source); IConfigurationRoot configuration = builder.Build(); @@ -99,11 +100,14 @@ public static void Invoke(OutputFormat output, TimeSpan expiration, TextWriter o case OutputFormat.Cmd: case OutputFormat.PowerShell: case OutputFormat.Shell: - IDictionary optList = opts.ToEnvironmentConfiguration(); + { + CommonOptionsMapper optionsMapper = new(); + IDictionary optList = optionsMapper.ToEnvironmentConfiguration(opts); foreach ((string name, string value) in optList) { outputBldr.AppendFormat(CultureInfo.InvariantCulture, GetFormatString(output), name, value); outputBldr.AppendLine(); + } } break; } diff --git a/src/Tools/dotnet-monitor/CommonOptionsMapper.cs b/src/Tools/dotnet-monitor/CommonOptionsMapper.cs new file mode 100644 index 00000000000..c0bf345d22d --- /dev/null +++ b/src/Tools/dotnet-monitor/CommonOptionsMapper.cs @@ -0,0 +1,1004 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +#if UNITTEST +using Microsoft.Diagnostics.Monitoring.TestCommon; +#endif +using Microsoft.Diagnostics.Monitoring.Options; +using Microsoft.Diagnostics.Monitoring.WebApi; +using Microsoft.Diagnostics.Monitoring.WebApi.Models; +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules; +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options; +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Actions; +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Triggers; +using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Triggers.EventCounterShortcuts; +using Microsoft.Diagnostics.Tools.Monitor.Egress.FileSystem; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Microsoft.Diagnostics.Tools.Monitor +{ + internal class CommonOptionsMapper + { + private const string KeySegmentSeparator = "__"; + + /// + /// Generates a map of options that can be passed directly to configuration via an in-memory collection. + /// + /// + /// Each key is the configuration path; each value is the configuration path value. + /// + public IDictionary ToConfigurationValues(RootOptions options) + { + Dictionary variables = new(StringComparer.OrdinalIgnoreCase); + MapRootOptions(options, string.Empty, ConfigurationPath.KeyDelimiter, variables); + return variables; + } + + /// + /// Generates an environment variable map of the options. + /// + /// + /// Each key is the variable name; each value is the variable value. + /// + public IDictionary ToEnvironmentConfiguration(RootOptions options, bool useDotnetMonitorPrefix = false) + { + Dictionary variables = new(StringComparer.OrdinalIgnoreCase); + MapRootOptions(options, useDotnetMonitorPrefix ? ToolIdentifiers.StandardPrefix : string.Empty, KeySegmentSeparator, variables); + return variables; + } + + /// + /// Generates a key-per-file map of the options. + /// + /// + /// Each key is the file name; each value is the file content. + /// + public IDictionary ToKeyPerFileConfiguration(RootOptions options) + { + Dictionary variables = new(StringComparer.OrdinalIgnoreCase); + MapRootOptions(options, string.Empty, KeySegmentSeparator, variables); + return variables; + } + + private Dictionary>>? _actionSettingsMap; + + public void AddActionSettings(string type, Action> mapAction) + { + (_actionSettingsMap ??= new()).Add(type, (obj, valueName, separator, map) => + { + mapAction((TSettings)obj, valueName, separator, map); + }); + } + + private static void MapString(string? value, string valueName, IDictionary map) + { + if (value != null) + { + map.Add(valueName, ConvertUtils.ToString(value, CultureInfo.InvariantCulture)); + } + } + + private static void MapUri(Uri? value, string valueName, IDictionary map) + { + if (null != value) + { + map.Add(valueName, ConvertUtils.ToString(value, CultureInfo.InvariantCulture)); + } + } + + private static void MapValue(T? value, string valueName, IDictionary map) where T : struct + { + if (value != null) + { + map.Add(valueName, ConvertUtils.ToString(value.Value, CultureInfo.InvariantCulture)); + } + } + + private static void MapValue(T value, string valueName, IDictionary map) where T : struct + { + map.Add(valueName, ConvertUtils.ToString(value, CultureInfo.InvariantCulture)); + } + + private static string BuildPropertyPath(string prefix, string name, string separator) + { + return FormattableString.Invariant($"{prefix}{separator}{name}"); + } + + private static string BuildPrefix(string valueName, string separator) + { + return FormattableString.Invariant($"{valueName}{separator}"); + } + + private void MapRootOptions(RootOptions obj, string prefix, string separator, IDictionary map) + { + MapAuthenticationOptions(obj.Authentication, BuildPropertyPath(prefix, nameof(obj.Authentication), ""), separator, map); + MapDictionary_String_CollectionRuleOptions(obj.CollectionRules, BuildPropertyPath(prefix, nameof(obj.CollectionRules), ""), separator, map); + MapGlobalCounterOptions(obj.GlobalCounter, BuildPropertyPath(prefix, nameof(obj.GlobalCounter), ""), separator, map); + MapInProcessFeaturesOptions(obj.InProcessFeatures, BuildPropertyPath(prefix, nameof(obj.InProcessFeatures), ""), separator, map); + MapCorsConfigurationOptions(obj.CorsConfiguration, BuildPropertyPath(prefix, nameof(obj.CorsConfiguration), ""), separator, map); + MapDiagnosticPortOptions(obj.DiagnosticPort, BuildPropertyPath(prefix, nameof(obj.DiagnosticPort), ""), separator, map); + MapEgressOptions(obj.Egress, BuildPropertyPath(prefix, nameof(obj.Egress), ""), separator, map); + MapMetricsOptions(obj.Metrics, BuildPropertyPath(prefix, nameof(obj.Metrics), ""), separator, map); + MapStorageOptions(obj.Storage, BuildPropertyPath(prefix, nameof(obj.Storage), ""), separator, map); + MapProcessFilterOptions(obj.DefaultProcess, BuildPropertyPath(prefix, nameof(obj.DefaultProcess), ""), separator, map); + MapCollectionRuleDefaultsOptions(obj.CollectionRuleDefaults, BuildPropertyPath(prefix, nameof(obj.CollectionRuleDefaults), ""), separator, map); + MapTemplateOptions(obj.Templates, BuildPropertyPath(prefix, nameof(obj.Templates), ""), separator, map); + MapDotnetMonitorDebugOptions(obj.DotnetMonitorDebug, BuildPropertyPath(prefix, nameof(obj.DotnetMonitorDebug), ""), separator, map); + } + + private static void MapAuthenticationOptions(AuthenticationOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapMonitorApiKeyOptions(obj.MonitorApiKey, BuildPropertyPath(valueName, nameof(obj.MonitorApiKey), separator), separator, map); + MapAzureAdOptions(obj.AzureAd, BuildPropertyPath(valueName, nameof(obj.AzureAd), separator), separator, map); + } + } + + private static void MapMonitorApiKeyOptions(MonitorApiKeyOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Subject, BuildPropertyPath(valueName, nameof(obj.Subject), separator), map); + MapString(obj.PublicKey, BuildPropertyPath(valueName, nameof(obj.PublicKey), separator), map); + MapString(obj.Issuer, BuildPropertyPath(valueName, nameof(obj.Issuer), separator), map); + } + } + + private static void MapAzureAdOptions(AzureAdOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapUri(obj.Instance, BuildPropertyPath(valueName, nameof(obj.Instance), separator), map); + MapString(obj.TenantId, BuildPropertyPath(valueName, nameof(obj.TenantId), separator), map); + MapString(obj.ClientId, BuildPropertyPath(valueName, nameof(obj.ClientId), separator), map); + MapUri(obj.AppIdUri, BuildPropertyPath(valueName, nameof(obj.AppIdUri), separator), map); + MapString(obj.RequiredRole, BuildPropertyPath(valueName, nameof(obj.RequiredRole), separator), map); + } + } + + private void MapDictionary_String_CollectionRuleOptions(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + foreach ((string key, CollectionRuleOptions value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapCollectionRuleOptions(value, BuildPropertyPath(valueName, keyString, separator), separator, map); + } + } + } + + private void MapCollectionRuleOptions(CollectionRuleOptions obj, string valueName, string separator, IDictionary map) + { + MapList_ProcessFilterDescriptor(obj.Filters, BuildPropertyPath(valueName, nameof(obj.Filters), separator), separator, map); + MapCollectionRuleTriggerOptions(obj.Trigger, BuildPropertyPath(valueName, nameof(obj.Trigger), separator), separator, map); + MapList_CollectionRuleActionOptions(obj.Actions, BuildPropertyPath(valueName, nameof(obj.Actions), separator), separator, map); + MapCollectionRuleLimitsOptions(obj.Limits, BuildPropertyPath(valueName, nameof(obj.Limits), separator), separator, map); + } + + private static void MapList_ProcessFilterDescriptor(List? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Count; index++) + { + ProcessFilterDescriptor value = obj[index]; + MapProcessFilterDescriptor(value, FormattableString.Invariant($"{prefix}{index}"), separator, map); + } + } + } + + private static void MapProcessFilterDescriptor(ProcessFilterDescriptor obj, string valueName, string separator, IDictionary map) + { + MapValue(obj.Key, BuildPropertyPath(valueName, nameof(obj.Key), separator), map); + MapString(obj.Value, BuildPropertyPath(valueName, nameof(obj.Value), separator), map); + MapValue(obj.MatchType, BuildPropertyPath(valueName, nameof(obj.MatchType), separator), map); + MapString(obj.ProcessName, BuildPropertyPath(valueName, nameof(obj.ProcessName), separator), map); + MapString(obj.ProcessId, BuildPropertyPath(valueName, nameof(obj.ProcessId), separator), map); + MapString(obj.CommandLine, BuildPropertyPath(valueName, nameof(obj.CommandLine), separator), map); + MapString(obj.ManagedEntryPointAssemblyName, BuildPropertyPath(valueName, nameof(obj.ManagedEntryPointAssemblyName), separator), map); + } + + private static void MapCollectionRuleTriggerOptions(CollectionRuleTriggerOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Type, BuildPropertyPath(valueName, nameof(obj.Type), separator), map); + MapCollectionRuleTriggerOptions_Settings(obj.Type, obj.Settings, BuildPropertyPath(valueName, nameof(obj.Settings), separator), separator, map); + } + } + + private static void MapCollectionRuleTriggerOptions_Settings(string type, object? settings, string valueName, string separator, IDictionary map) + { + if (settings == null) + return; + + switch (type) + { + case KnownCollectionRuleTriggers.AspNetRequestCount when settings is AspNetRequestCountOptions requestCountOptions: + MapAspNetRequestCountOptions(requestCountOptions, valueName, separator, map); + break; + case KnownCollectionRuleTriggers.AspNetRequestDuration when settings is AspNetRequestDurationOptions requestDurationOptions: + MapAspNetRequestDurationOptions(requestDurationOptions, valueName, separator, map); + break; + case KnownCollectionRuleTriggers.AspNetResponseStatus when settings is AspNetResponseStatusOptions responseStatusOptions: + MapAspNetResponseStatusOptions(responseStatusOptions, valueName, separator, map); + break; + case KnownCollectionRuleTriggers.EventCounter when settings is EventCounterOptions eventCounterOptions: + MapEventCounterOptions(eventCounterOptions, valueName, separator, map); + break; + case KnownCollectionRuleTriggers.CPUUsage when settings is CPUUsageOptions cpuUsageOptions: + MapCPUUsageOptions(cpuUsageOptions, valueName, separator, map); + break; + case KnownCollectionRuleTriggers.GCHeapSize when settings is GCHeapSizeOptions heapSizeOptions: + MapGCHeapSizeOptions(heapSizeOptions, valueName, separator, map); + break; + case KnownCollectionRuleTriggers.ThreadpoolQueueLength when settings is ThreadpoolQueueLengthOptions queueLengthOptions: + MapThreadpoolQueueLengthOptions(queueLengthOptions, valueName, separator, map); + break; + case KnownCollectionRuleTriggers.EventMeter when settings is EventMeterOptions eventMeterOptions: + MapEventMeterOptions(eventMeterOptions, valueName, separator, map); + break; + default: + throw new NotSupportedException($"Unknown trigger type: {type}"); + } + } + + private static void MapAspNetRequestCountOptions(AspNetRequestCountOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.RequestCount, BuildPropertyPath(valueName, nameof(obj.RequestCount), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + MapArray_String(obj.IncludePaths, BuildPropertyPath(valueName, nameof(obj.IncludePaths), separator), separator, map); + MapArray_String(obj.ExcludePaths, BuildPropertyPath(valueName, nameof(obj.ExcludePaths), separator), separator, map); + } + } + + private static void MapArray_String(string[]? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Length; index++) + { + string value = obj[index]; + MapString(value, FormattableString.Invariant($"{prefix}{index}"), map); + } + } + } + + private static void MapAspNetRequestDurationOptions(AspNetRequestDurationOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.RequestCount, BuildPropertyPath(valueName, nameof(obj.RequestCount), separator), map); + MapValue(obj.RequestDuration, BuildPropertyPath(valueName, nameof(obj.RequestDuration), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + MapArray_String(obj.IncludePaths, BuildPropertyPath(valueName, nameof(obj.IncludePaths), separator), separator, map); + MapArray_String(obj.ExcludePaths, BuildPropertyPath(valueName, nameof(obj.ExcludePaths), separator), separator, map); + } + } + + private static void MapAspNetResponseStatusOptions(AspNetResponseStatusOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapArray_String(obj.StatusCodes, BuildPropertyPath(valueName, nameof(obj.StatusCodes), separator), separator, map); + MapValue(obj.ResponseCount, BuildPropertyPath(valueName, nameof(obj.ResponseCount), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + MapArray_String(obj.IncludePaths, BuildPropertyPath(valueName, nameof(obj.IncludePaths), separator), separator, map); + MapArray_String(obj.ExcludePaths, BuildPropertyPath(valueName, nameof(obj.ExcludePaths), separator), separator, map); + } + } + + private static void MapEventCounterOptions(EventCounterOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.ProviderName, BuildPropertyPath(valueName, nameof(obj.ProviderName), separator), map); + MapString(obj.CounterName, BuildPropertyPath(valueName, nameof(obj.CounterName), separator), map); + MapValue(obj.GreaterThan, BuildPropertyPath(valueName, nameof(obj.GreaterThan), separator), map); + MapValue(obj.LessThan, BuildPropertyPath(valueName, nameof(obj.LessThan), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + } + } + + private static void MapCPUUsageOptions(CPUUsageOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.GreaterThan, BuildPropertyPath(valueName, nameof(obj.GreaterThan), separator), map); + MapValue(obj.LessThan, BuildPropertyPath(valueName, nameof(obj.LessThan), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + } + } + + private static void MapGCHeapSizeOptions(GCHeapSizeOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.GreaterThan, BuildPropertyPath(valueName, nameof(obj.GreaterThan), separator), map); + MapValue(obj.LessThan, BuildPropertyPath(valueName, nameof(obj.LessThan), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + } + } + + private static void MapThreadpoolQueueLengthOptions(ThreadpoolQueueLengthOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.GreaterThan, BuildPropertyPath(valueName, nameof(obj.GreaterThan), separator), map); + MapValue(obj.LessThan, BuildPropertyPath(valueName, nameof(obj.LessThan), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + } + } + + private static void MapEventMeterOptions(EventMeterOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.MeterName, BuildPropertyPath(valueName, nameof(obj.MeterName), separator), map); + MapString(obj.InstrumentName, BuildPropertyPath(valueName, nameof(obj.InstrumentName), separator), map); + MapValue(obj.GreaterThan, BuildPropertyPath(valueName, nameof(obj.GreaterThan), separator), map); + MapValue(obj.LessThan, BuildPropertyPath(valueName, nameof(obj.LessThan), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + MapValue(obj.HistogramPercentile, BuildPropertyPath(valueName, nameof(obj.HistogramPercentile), separator), map); + } + } + + private static void MapCollectionRuleLimitsOptions(CollectionRuleLimitsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.ActionCount, BuildPropertyPath(valueName, nameof(obj.ActionCount), separator), map); + MapValue(obj.ActionCountSlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.ActionCountSlidingWindowDuration), separator), map); + MapValue(obj.RuleDuration, BuildPropertyPath(valueName, nameof(obj.RuleDuration), separator), map); + } + } + + private void MapList_CollectionRuleActionOptions(List obj, string valueName, string separator, IDictionary map) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Count; index++) + { + CollectionRuleActionOptions value = obj[index]; + MapCollectionRuleActionOptions(value, FormattableString.Invariant($"{prefix}{index}"), separator, map); + } + } + + private void MapCollectionRuleActionOptions(CollectionRuleActionOptions obj, string valueName, string separator, IDictionary map) + { + MapString(obj.Name, BuildPropertyPath(valueName, nameof(obj.Name), separator), map); + MapString(obj.Type, BuildPropertyPath(valueName, nameof(obj.Type), separator), map); + MapCollectionRuleActionOptions_Settings(obj.Type, obj.Settings, BuildPropertyPath(valueName, nameof(obj.Settings), separator), separator, map); + MapValue(obj.WaitForCompletion, BuildPropertyPath(valueName, nameof(obj.WaitForCompletion), separator), map); + } + + private void MapCollectionRuleActionOptions_Settings(string type, object? settings, string valueName, string separator, IDictionary map) + { + if (settings == null) + return; + + switch (type) + { + case KnownCollectionRuleActions.CollectDump when settings is CollectDumpOptions dumpOptions: + MapCollectDumpOptions(dumpOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.CollectExceptions when settings is CollectExceptionsOptions exceptionsOptions: + MapCollectExceptionsOptions(exceptionsOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.CollectGCDump when settings is CollectGCDumpOptions gcDumpOptions: + MapCollectGCDumpOptions(gcDumpOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.CollectLiveMetrics when settings is CollectLiveMetricsOptions metricsOptions: + MapCollectLiveMetricsOptions(metricsOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.CollectLogs when settings is CollectLogsOptions logsOptions: + MapCollectLogsOptions(logsOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.CollectStacks when settings is CollectStacksOptions stacksOptions: + MapCollectStacksOptions(stacksOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.CollectTrace when settings is CollectTraceOptions traceOptions: + MapCollectTraceOptions(traceOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.Execute when settings is ExecuteOptions executeOptions: + MapExecuteOptions(executeOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.LoadProfiler when settings is LoadProfilerOptions profilerOptions: + MapLoadProfilerOptions(profilerOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.SetEnvironmentVariable when settings is SetEnvironmentVariableOptions setEnvOptions: + MapSetEnvironmentVariableOptions(setEnvOptions, valueName, separator, map); + break; + case KnownCollectionRuleActions.GetEnvironmentVariable when settings is GetEnvironmentVariableOptions getEnvOptions: + MapGetEnvironmentVariableOptions(getEnvOptions, valueName, separator, map); + break; + default: + if (_actionSettingsMap?.TryGetValue(type, out Action>? mapAction) == true) + { + mapAction(settings, valueName, separator, map); + } + else + { + throw new NotSupportedException($"Unknown action type: {type}"); + } + break; + } + } + + private static void MapCollectDumpOptions(CollectDumpOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.Type, BuildPropertyPath(valueName, nameof(obj.Type), separator), map); + MapString(obj.Egress, BuildPropertyPath(valueName, nameof(obj.Egress), separator), map); + MapString(obj.ArtifactName, BuildPropertyPath(valueName, nameof(obj.ArtifactName), separator), map); + } + } + + private static void MapCollectExceptionsOptions(CollectExceptionsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Egress, BuildPropertyPath(valueName, nameof(obj.Egress), separator), map); + MapValue(obj.Format, BuildPropertyPath(valueName, nameof(obj.Format), separator), map); + MapExceptionsConfiguration(obj.Filters, BuildPropertyPath(valueName, nameof(obj.Filters), separator), separator, map); + MapString(obj.ArtifactName, BuildPropertyPath(valueName, nameof(obj.ArtifactName), separator), map); + } + } + + private static void MapExceptionsConfiguration(ExceptionsConfiguration? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapList_ExceptionFilter(obj.Include, BuildPropertyPath(valueName, nameof(obj.Include), separator), separator, map); + MapList_ExceptionFilter(obj.Exclude, BuildPropertyPath(valueName, nameof(obj.Exclude), separator), separator, map); + } + } + + private static void MapList_ExceptionFilter(List? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Count; index++) + { + ExceptionFilter value = obj[index]; + MapExceptionFilter(value, FormattableString.Invariant($"{prefix}{index}"), separator, map); + } + } + } + + private static void MapExceptionFilter(ExceptionFilter obj, string valueName, string separator, IDictionary map) + { + MapString(obj.ExceptionType, BuildPropertyPath(valueName, nameof(obj.ExceptionType), separator), map); + MapString(obj.ModuleName, BuildPropertyPath(valueName, nameof(obj.ModuleName), separator), map); + MapString(obj.TypeName, BuildPropertyPath(valueName, nameof(obj.TypeName), separator), map); + MapString(obj.MethodName, BuildPropertyPath(valueName, nameof(obj.MethodName), separator), map); + } + + private static void MapCollectGCDumpOptions(CollectGCDumpOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Egress, BuildPropertyPath(valueName, nameof(obj.Egress), separator), map); + MapString(obj.ArtifactName, BuildPropertyPath(valueName, nameof(obj.ArtifactName), separator), map); + } + } + + private static void MapCollectLiveMetricsOptions(CollectLiveMetricsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.IncludeDefaultProviders, BuildPropertyPath(valueName, nameof(obj.IncludeDefaultProviders), separator), map); + MapArray_EventMetricsProvider(obj.Providers, BuildPropertyPath(valueName, nameof(obj.Providers), separator), separator, map); + MapArray_EventMetricsMeter(obj.Meters, BuildPropertyPath(valueName, nameof(obj.Meters), separator), separator, map); + MapValue(obj.Duration, BuildPropertyPath(valueName, nameof(obj.Duration), separator), map); + MapString(obj.Egress, BuildPropertyPath(valueName, nameof(obj.Egress), separator), map); + MapString(obj.ArtifactName, BuildPropertyPath(valueName, nameof(obj.ArtifactName), separator), map); + } + } + + private static void MapArray_EventMetricsProvider(EventMetricsProvider[]? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Length; index++) + { + EventMetricsProvider value = obj[index]; + MapEventMetricsProvider(value, FormattableString.Invariant($"{prefix}{index}"), separator, map); + } + } + } + + private static void MapEventMetricsProvider(EventMetricsProvider obj, string valueName, string separator, IDictionary map) + { + MapString(obj.ProviderName, BuildPropertyPath(valueName, nameof(obj.ProviderName), separator), map); + MapArray_String(obj.CounterNames, BuildPropertyPath(valueName, nameof(obj.CounterNames), separator), separator, map); + } + + private static void MapArray_EventMetricsMeter(EventMetricsMeter[]? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Length; index++) + { + EventMetricsMeter value = obj[index]; + MapEventMetricsMeter(value, FormattableString.Invariant($"{prefix}{index}"), separator, map); + } + } + } + + private static void MapEventMetricsMeter(EventMetricsMeter obj, string valueName, string separator, IDictionary map) + { + MapString(obj.MeterName, BuildPropertyPath(valueName, nameof(obj.MeterName), separator), map); + MapArray_String(obj.InstrumentNames, BuildPropertyPath(valueName, nameof(obj.InstrumentNames), separator), separator, map); + } + + + private static void MapCollectLogsOptions(CollectLogsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.DefaultLevel, BuildPropertyPath(valueName, nameof(obj.DefaultLevel), separator), map); + MapDictionary_String_LogLevel(obj.FilterSpecs, BuildPropertyPath(valueName, nameof(obj.FilterSpecs), separator), separator, map); + MapValue(obj.UseAppFilters, BuildPropertyPath(valueName, nameof(obj.UseAppFilters), separator), map); + MapValue(obj.Duration, BuildPropertyPath(valueName, nameof(obj.Duration), separator), map); + MapString(obj.Egress, BuildPropertyPath(valueName, nameof(obj.Egress), separator), map); + MapValue(obj.Format, BuildPropertyPath(valueName, nameof(obj.Format), separator), map); + MapString(obj.ArtifactName, BuildPropertyPath(valueName, nameof(obj.ArtifactName), separator), map); + } + } + + private static void MapDictionary_String_LogLevel(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + foreach ((string key, LogLevel? value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapValue(value, FormattableString.Invariant($"{prefix}{keyString}"), map); + } + } + } + + private static void MapCollectStacksOptions(CollectStacksOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Egress, BuildPropertyPath(valueName, nameof(obj.Egress), separator), map); + MapValue(obj.Format, BuildPropertyPath(valueName, nameof(obj.Format), separator), map); + MapString(obj.ArtifactName, BuildPropertyPath(valueName, nameof(obj.ArtifactName), separator), map); + } + } + + private static void MapCollectTraceOptions(CollectTraceOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.Profile, BuildPropertyPath(valueName, nameof(obj.Profile), separator), map); + MapList_EventPipeProvider(obj.Providers, BuildPropertyPath(valueName, nameof(obj.Providers), separator), separator, map); + MapValue(obj.RequestRundown, BuildPropertyPath(valueName, nameof(obj.RequestRundown), separator), map); + MapValue(obj.BufferSizeMegabytes, BuildPropertyPath(valueName, nameof(obj.BufferSizeMegabytes), separator), map); + MapValue(obj.Duration, BuildPropertyPath(valueName, nameof(obj.Duration), separator), map); + MapString(obj.Egress, BuildPropertyPath(valueName, nameof(obj.Egress), separator), map); + MapTraceEventFilter(obj.StoppingEvent, BuildPropertyPath(valueName, nameof(obj.StoppingEvent), separator), separator, map); + MapString(obj.ArtifactName, BuildPropertyPath(valueName, nameof(obj.ArtifactName), separator), map); + } + } + + private static void MapList_EventPipeProvider(List? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Count; index++) + { + EventPipeProvider value = obj[index]; + MapEventPipeProvider(value, FormattableString.Invariant($"{prefix}{index}"), separator, map); + } + } + } + + private static void MapEventPipeProvider(EventPipeProvider obj, string valueName, string separator, IDictionary map) + { + MapString(obj.Name, BuildPropertyPath(valueName, nameof(obj.Name), separator), map); + MapString(obj.Keywords, BuildPropertyPath(valueName, nameof(obj.Keywords), separator), map); + MapValue(obj.EventLevel, BuildPropertyPath(valueName, nameof(obj.EventLevel), separator), map); + MapDictionary_String_String(obj.Arguments, BuildPropertyPath(valueName, nameof(obj.Arguments), separator), separator, map); + } + + private static void MapDictionary_String_String(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + foreach ((string key, string value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapString(value, FormattableString.Invariant($"{prefix}{keyString}"), map); + } + } + } + + private static void MapTraceEventFilter(TraceEventFilter? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.ProviderName, BuildPropertyPath(valueName, nameof(obj.ProviderName), separator), map); + MapString(obj.EventName, BuildPropertyPath(valueName, nameof(obj.EventName), separator), map); + MapDictionary_String_String(obj.PayloadFilter, BuildPropertyPath(valueName, nameof(obj.PayloadFilter), separator), separator, map); + } + } + + private static void MapExecuteOptions(ExecuteOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Path, BuildPropertyPath(valueName, nameof(obj.Path), separator), map); + MapString(obj.Arguments, BuildPropertyPath(valueName, nameof(obj.Arguments), separator), map); + MapValue(obj.IgnoreExitCode, BuildPropertyPath(valueName, nameof(obj.IgnoreExitCode), separator), map); + } + } + + private static void MapLoadProfilerOptions(LoadProfilerOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Path, BuildPropertyPath(valueName, nameof(obj.Path), separator), map); + MapValue(obj.Clsid, BuildPropertyPath(valueName, nameof(obj.Clsid), separator), map); + } + } + + private static void MapSetEnvironmentVariableOptions(SetEnvironmentVariableOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Name, BuildPropertyPath(valueName, nameof(obj.Name), separator), map); + MapString(obj.Value, BuildPropertyPath(valueName, nameof(obj.Value), separator), map); + } + } + + private static void MapGetEnvironmentVariableOptions(GetEnvironmentVariableOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Name, BuildPropertyPath(valueName, nameof(obj.Name), separator), map); + } + } + + private static void MapGlobalCounterOptions(GlobalCounterOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.IntervalSeconds, BuildPropertyPath(valueName, nameof(obj.IntervalSeconds), separator), map); + MapValue(obj.MaxHistograms, BuildPropertyPath(valueName, nameof(obj.MaxHistograms), separator), map); + MapValue(obj.MaxTimeSeries, BuildPropertyPath(valueName, nameof(obj.MaxTimeSeries), separator), map); + MapDictionary_String_GlobalProviderOptions(obj.Providers, BuildPropertyPath(valueName, nameof(obj.Providers), separator), separator, map); + } + } + + private static void MapDictionary_String_GlobalProviderOptions(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + foreach ((string key, GlobalProviderOptions value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapGlobalProviderOptions(value, FormattableString.Invariant($"{prefix}{keyString}"), separator, map); + } + } + } + + private static void MapGlobalProviderOptions(GlobalProviderOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.IntervalSeconds, BuildPropertyPath(valueName, nameof(obj.IntervalSeconds), separator), map); + } + } + + private static void MapInProcessFeaturesOptions(InProcessFeaturesOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.Enabled, BuildPropertyPath(valueName, nameof(obj.Enabled), separator), map); + MapCallStacksOptions(obj.CallStacks, BuildPropertyPath(valueName, nameof(obj.CallStacks), separator), separator, map); + MapExceptionsOptions(obj.Exceptions, BuildPropertyPath(valueName, nameof(obj.Exceptions), separator), separator, map); + MapParameterCapturingOptions(obj.ParameterCapturing, BuildPropertyPath(valueName, nameof(obj.ParameterCapturing), separator), separator, map); + } + } + + private static void MapCallStacksOptions(CallStacksOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.Enabled, BuildPropertyPath(valueName, nameof(obj.Enabled), separator), map); + } + } + + private static void MapExceptionsOptions(ExceptionsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.Enabled, BuildPropertyPath(valueName, nameof(obj.Enabled), separator), map); + MapValue(obj.TopLevelLimit, BuildPropertyPath(valueName, nameof(obj.TopLevelLimit), separator), map); + MapExceptionsConfiguration(obj.CollectionFilters, BuildPropertyPath(valueName, nameof(obj.CollectionFilters), separator), separator, map); + MapValue(obj.CollectOnStartup, BuildPropertyPath(valueName, nameof(obj.CollectOnStartup), separator), map); + } + } + + private static void MapCorsConfigurationOptions(CorsConfigurationOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.AllowedOrigins, BuildPropertyPath(valueName, nameof(obj.AllowedOrigins), separator), map); + } + } + + private static void MapParameterCapturingOptions(ParameterCapturingOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.Enabled, BuildPropertyPath(valueName, nameof(obj.Enabled), separator), map); + } + } + + private static void MapDiagnosticPortOptions(DiagnosticPortOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.ConnectionMode, BuildPropertyPath(valueName, nameof(obj.ConnectionMode), separator), map); + MapString(obj.EndpointName, BuildPropertyPath(valueName, nameof(obj.EndpointName), separator), map); + MapValue(obj.MaxConnections, BuildPropertyPath(valueName, nameof(obj.MaxConnections), separator), map); + MapValue(obj.DeleteEndpointOnStartup, BuildPropertyPath(valueName, nameof(obj.DeleteEndpointOnStartup), separator), map); + } + } + + private static void MapEgressOptions(EgressOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapDictionary_String_FileSystemEgressProviderOptions(obj.FileSystem, BuildPropertyPath(valueName, nameof(obj.FileSystem), separator), separator, map); + MapDictionary_String_String(obj.Properties, BuildPropertyPath(valueName, nameof(obj.Properties), separator), separator, map); + } + } + + + private static void MapDictionary_String_FileSystemEgressProviderOptions(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + foreach ((string key, FileSystemEgressProviderOptions value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapFileSystemEgressProviderOptions(value, FormattableString.Invariant($"{prefix}{keyString}"), separator, map); + } + } + } + + private static void MapFileSystemEgressProviderOptions(FileSystemEgressProviderOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.DirectoryPath, BuildPropertyPath(valueName, nameof(obj.DirectoryPath), separator), map); + MapString(obj.IntermediateDirectoryPath, BuildPropertyPath(valueName, nameof(obj.IntermediateDirectoryPath), separator), map); + MapValue(obj.CopyBufferSize, BuildPropertyPath(valueName, nameof(obj.CopyBufferSize), separator), map); + } + } + + private static void MapMetricsOptions(MetricsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.Enabled, BuildPropertyPath(valueName, nameof(obj.Enabled), separator), map); + MapString(obj.Endpoints, BuildPropertyPath(valueName, nameof(obj.Endpoints), separator), map); + MapValue(obj.MetricCount, BuildPropertyPath(valueName, nameof(obj.MetricCount), separator), map); + MapValue(obj.IncludeDefaultProviders, BuildPropertyPath(valueName, nameof(obj.IncludeDefaultProviders), separator), map); + MapList_MetricProvider(obj.Providers, BuildPropertyPath(valueName, nameof(obj.Providers), separator), separator, map); + MapList_MeterConfiguration(obj.Meters, BuildPropertyPath(valueName, nameof(obj.Meters), separator), separator, map); + } + } + + private static void MapList_MetricProvider(List? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Count; index++) + { + MetricProvider value = obj[index]; + MapMetricProvider(value, FormattableString.Invariant($"{prefix}{index}"), separator, map); + } + } + } + + private static void MapMetricProvider(MetricProvider obj, string valueName, string separator, IDictionary map) + { + MapString(obj.ProviderName, BuildPropertyPath(valueName, nameof(obj.ProviderName), separator), map); + MapList_String(obj.CounterNames, BuildPropertyPath(valueName, nameof(obj.CounterNames), separator), separator, map); + } + + private static void MapList_String(List? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Count; index++) + { + string value = obj[index]; + MapString(value, FormattableString.Invariant($"{prefix}{index}"), map); + } + } + } + + private static void MapList_MeterConfiguration(List? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + for (int index = 0; index < obj.Count; index++) + { + MeterConfiguration value = obj[index]; + MapMeterConfiguration(value, FormattableString.Invariant($"{prefix}{index}"), separator, map); + } + } + } + + private static void MapMeterConfiguration(MeterConfiguration obj, string valueName, string separator, IDictionary map) + { + MapString(obj.MeterName, BuildPropertyPath(valueName, nameof(obj.MeterName), separator), map); + MapList_String(obj.InstrumentNames, BuildPropertyPath(valueName, nameof(obj.InstrumentNames), separator), separator, map); + } + + private static void MapStorageOptions(StorageOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.DefaultSharedPath, BuildPropertyPath(valueName, nameof(obj.DefaultSharedPath), separator), map); + MapString(obj.DumpTempFolder, BuildPropertyPath(valueName, nameof(obj.DumpTempFolder), separator), map); + MapString(obj.SharedLibraryPath, BuildPropertyPath(valueName, nameof(obj.SharedLibraryPath), separator), map); + } + } + + private static void MapProcessFilterOptions(ProcessFilterOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapList_ProcessFilterDescriptor(obj.Filters, BuildPropertyPath(valueName, nameof(obj.Filters), separator), separator, map); + } + } + + private static void MapCollectionRuleDefaultsOptions(CollectionRuleDefaultsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapCollectionRuleTriggerDefaultsOptions(obj.Triggers, BuildPropertyPath(valueName, nameof(obj.Triggers), separator), separator, map); + MapCollectionRuleActionDefaultsOptions(obj.Actions, BuildPropertyPath(valueName, nameof(obj.Actions), separator), separator, map); + MapCollectionRuleLimitsDefaultsOptions(obj.Limits, BuildPropertyPath(valueName, nameof(obj.Limits), separator), separator, map); + } + } + + private static void MapCollectionRuleTriggerDefaultsOptions(CollectionRuleTriggerDefaultsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.RequestCount, BuildPropertyPath(valueName, nameof(obj.RequestCount), separator), map); + MapValue(obj.ResponseCount, BuildPropertyPath(valueName, nameof(obj.ResponseCount), separator), map); + MapValue(obj.SlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.SlidingWindowDuration), separator), map); + } + } + + private static void MapCollectionRuleActionDefaultsOptions(CollectionRuleActionDefaultsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapString(obj.Egress, BuildPropertyPath(valueName, nameof(obj.Egress), separator), map); + } + } + + private static void MapCollectionRuleLimitsDefaultsOptions(CollectionRuleLimitsDefaultsOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.ActionCount, BuildPropertyPath(valueName, nameof(obj.ActionCount), separator), map); + MapValue(obj.ActionCountSlidingWindowDuration, BuildPropertyPath(valueName, nameof(obj.ActionCountSlidingWindowDuration), separator), map); + MapValue(obj.RuleDuration, BuildPropertyPath(valueName, nameof(obj.RuleDuration), separator), map); + } + } + + private void MapTemplateOptions(TemplateOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapDictionary_String_ProcessFilterDescriptor(obj.CollectionRuleFilters, BuildPropertyPath(valueName, nameof(obj.CollectionRuleFilters), separator), separator, map); + MapDictionary_String_CollectionRuleTriggerOptions(obj.CollectionRuleTriggers, BuildPropertyPath(valueName, nameof(obj.CollectionRuleTriggers), separator), separator, map); + MapDictionary_String_CollectionRuleActionOptions(obj.CollectionRuleActions, BuildPropertyPath(valueName, nameof(obj.CollectionRuleActions), separator), separator, map); + MapDictionary_String_CollectionRuleLimitsOptions(obj.CollectionRuleLimits, BuildPropertyPath(valueName, nameof(obj.CollectionRuleLimits), separator), separator, map); + } + } + + private static void MapDictionary_String_ProcessFilterDescriptor(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + foreach ((string key, ProcessFilterDescriptor value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapProcessFilterDescriptor(value, FormattableString.Invariant($"{prefix}{keyString}"), separator, map); + } + } + } + + private static void MapDictionary_String_CollectionRuleTriggerOptions(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + foreach ((string key, CollectionRuleTriggerOptions value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapCollectionRuleTriggerOptions(value, FormattableString.Invariant($"{prefix}{keyString}"), separator, map); + } + } + } + + private void MapDictionary_String_CollectionRuleActionOptions(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + foreach ((string key, CollectionRuleActionOptions value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapCollectionRuleActionOptions(value, FormattableString.Invariant($"{prefix}{keyString}"), separator, map); + } + } + } + + private static void MapDictionary_String_CollectionRuleLimitsOptions(IDictionary? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + string prefix = BuildPrefix(valueName, separator); + foreach ((string key, CollectionRuleLimitsOptions value) in obj) + { + string keyString = ConvertUtils.ToString(key, CultureInfo.InvariantCulture); + MapCollectionRuleLimitsOptions(value, FormattableString.Invariant($"{prefix}{keyString}"), separator, map); + } + } + } + + private static void MapDotnetMonitorDebugOptions(DotnetMonitorDebugOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapExceptionsDebugOptions(obj.Exceptions, BuildPropertyPath(valueName, nameof(obj.Exceptions), separator), separator, map); + } + } + + private static void MapExceptionsDebugOptions(ExceptionsDebugOptions? obj, string valueName, string separator, IDictionary map) + { + if (null != obj) + { + MapValue(obj.IncludeMonitorExceptions, BuildPropertyPath(valueName, nameof(obj.IncludeMonitorExceptions), separator), map); + } + } + } +} diff --git a/src/Tools/dotnet-monitor/ConfigurationTokenParser.cs b/src/Tools/dotnet-monitor/ConfigurationTokenParser.cs index a672c46b097..e2d4068645f 100644 --- a/src/Tools/dotnet-monitor/ConfigurationTokenParser.cs +++ b/src/Tools/dotnet-monitor/ConfigurationTokenParser.cs @@ -60,11 +60,11 @@ public ConfigurationTokenParser(ILogger logger) _logger = logger; } - public object? SubstituteOptionValues(object? originalSettings, TokenContext context) + public object? SubstituteOptionValues(object? originalSettings, Type settingsType, TokenContext context) { object? settings = originalSettings; - foreach (PropertyInfo propertyInfo in GetPropertiesFromSettings(settings)) + foreach (PropertyInfo propertyInfo in GetPropertiesFromSettings(settingsType)) { string? originalPropertyValue = (string?)propertyInfo.GetValue(settings); if (string.IsNullOrEmpty(originalPropertyValue)) @@ -119,11 +119,12 @@ public bool TryCloneSettings(object? originalSettings, ref object? settings) return true; } - public static IEnumerable GetPropertiesFromSettings(object? settings, Predicate? predicate = null) => - settings?.GetType() - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.PropertyType == typeof(string) && (predicate?.Invoke(p) ?? true)) ?? - Enumerable.Empty(); + public static IEnumerable GetPropertiesFromSettings(Type type, Predicate? predicate = null) + { + return type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.PropertyType == typeof(string) && (predicate?.Invoke(p) ?? true)) + .ToArray(); + } private static string CreateTokenReference(string category, string token) => FormattableString.Invariant($"{SubstitutionPrefix}{category}{Separator}{token}{SubstitutionSuffix}");