|
3 | 3 | using System.IO; |
4 | 4 | using System.Linq; |
5 | 5 | using System.Net.Http; |
| 6 | +using System.Reflection; |
6 | 7 | using System.Runtime.InteropServices; |
7 | 8 | using System.Text; |
8 | 9 | using System.Text.Json; |
@@ -2574,6 +2575,172 @@ string NormalizedPath(AbsolutePath ap) |
2574 | 2575 | await LogParser.ReportNativeMetrics(logDirectory); |
2575 | 2576 | }); |
2576 | 2577 |
|
| 2578 | + public Target CheckConfigurationKeysAgainstJsonValidations => _ => _ |
| 2579 | + .Description("Validates that all ConfigurationKeys constants are present in supported-configurations.json") |
| 2580 | + .After(CompileManagedSrc) |
| 2581 | + .Executes(() => |
| 2582 | + { |
| 2583 | + var jsonPath = TracerDirectory / "src" / "Datadog.Trace" / "Configuration" / "supported-configurations.json"; |
| 2584 | + if (!File.Exists(jsonPath)) |
| 2585 | + { |
| 2586 | + Logger.Error("supported-configurations.json file not found at {JsonPath}", jsonPath); |
| 2587 | + return; |
| 2588 | + } |
| 2589 | + |
| 2590 | + // Parse JSON to get supported configuration keys |
| 2591 | + var jsonContent = File.ReadAllText(jsonPath); |
| 2592 | + var jsonDoc = JsonDocument.Parse(jsonContent); |
| 2593 | + var canonicalKeysInJson = new HashSet<string>(); |
| 2594 | + var aliasesInJson = new HashSet<string>(); |
| 2595 | + var deprecationsInJson = new HashSet<string>(); |
| 2596 | + |
| 2597 | + // Add keys from supportedConfigurations section |
| 2598 | + if (jsonDoc.RootElement.TryGetProperty("supportedConfigurations", out var supportedConfigs)) |
| 2599 | + { |
| 2600 | + foreach (var property in supportedConfigs.EnumerateObject()) |
| 2601 | + { |
| 2602 | + canonicalKeysInJson.Add(property.Name); |
| 2603 | + deprecationsInJson.Add(property.Name); |
| 2604 | + } |
| 2605 | + } |
| 2606 | + |
| 2607 | + // Add keys from aliases section (add elements in the arrays) |
| 2608 | + if (jsonDoc.RootElement.TryGetProperty("aliases", out var aliases)) |
| 2609 | + { |
| 2610 | + foreach (var property in aliases.EnumerateObject()) |
| 2611 | + { |
| 2612 | + // Add all alias keys from the array |
| 2613 | + if (property.Value.ValueKind == JsonValueKind.Array) |
| 2614 | + { |
| 2615 | + foreach (var aliasElement in property.Value.EnumerateArray()) |
| 2616 | + { |
| 2617 | + if (aliasElement.ValueKind == JsonValueKind.String) |
| 2618 | + { |
| 2619 | + aliasesInJson.Add(aliasElement.GetString()); |
| 2620 | + } |
| 2621 | + } |
| 2622 | + } |
| 2623 | + } |
| 2624 | + } |
| 2625 | + |
| 2626 | + // Add keys from deprecations section (add the deprecated keys themselves) |
| 2627 | + if (jsonDoc.RootElement.TryGetProperty("deprecations", out var deprecations)) |
| 2628 | + { |
| 2629 | + foreach (var property in deprecations.EnumerateObject()) |
| 2630 | + { |
| 2631 | + deprecationsInJson.Add(property.Name); |
| 2632 | + } |
| 2633 | + } |
| 2634 | + |
| 2635 | + // Load the net6.0 assembly for configuration key analysis |
| 2636 | + // Configuration keys are the same across all frameworks, so we only need one |
| 2637 | + const string targetFramework = "net6.0"; |
| 2638 | + var assemblyPath = TracerDirectory / "src" / "Datadog.Trace" / "bin" / BuildConfiguration / targetFramework / "Datadog.Trace.dll"; |
| 2639 | + |
| 2640 | + if (!File.Exists(assemblyPath)) |
| 2641 | + { |
| 2642 | + throw new InvalidOperationException($"Assembly not found at {assemblyPath} for framework {targetFramework}. Make sure CompileManagedSrc has run."); |
| 2643 | + } |
| 2644 | + |
| 2645 | + Assembly tracerAssembly; |
| 2646 | + try |
| 2647 | + { |
| 2648 | + // Load the assembly directly - no complex resolution needed |
| 2649 | + tracerAssembly = Assembly.LoadFrom(assemblyPath); |
| 2650 | + Logger.Information("Using {Framework} assembly for configuration key analysis", targetFramework); |
| 2651 | + } |
| 2652 | + catch (Exception ex) |
| 2653 | + { |
| 2654 | + throw new InvalidOperationException($"Failed to load assembly for {targetFramework}: {ex.Message}", ex); |
| 2655 | + } |
| 2656 | + |
| 2657 | + // Use reflection to get all configuration keys from the ConfigurationKeys class |
| 2658 | + var configurationKeysInCode = new HashSet<string>(); |
| 2659 | + var keyToFieldMap = new Dictionary<string, string>(); |
| 2660 | + |
| 2661 | + const string configKeysClassName = "Datadog.Trace.Configuration.ConfigurationKeys"; |
| 2662 | + // Get the ConfigurationKeys type (includes all partial classes) |
| 2663 | + var configKeysType = tracerAssembly.GetType(configKeysClassName); |
| 2664 | + if (configKeysType == null) |
| 2665 | + { |
| 2666 | + throw new InvalidOperationException($"Could not find {configKeysClassName} type in assembly for framework {targetFramework}"); |
| 2667 | + } |
| 2668 | + |
| 2669 | + // Get all public const string fields from the type and all nested types (one level deep) |
| 2670 | + var allTypes = new List<Type> { configKeysType }; |
| 2671 | + allTypes.AddRange(configKeysType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic)); |
| 2672 | + |
| 2673 | + Logger.Information("Found {TypeCount} types to analyze (including nested classes)", allTypes.Count); |
| 2674 | + |
| 2675 | + foreach (var type in allTypes) |
| 2676 | + { |
| 2677 | + var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy) |
| 2678 | + .Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string)); |
| 2679 | + |
| 2680 | + Logger.Information(" Type {TypeName} has {FieldCount} const string fields", type.Name, fields.Count()); |
| 2681 | + |
| 2682 | + foreach (var field in fields) |
| 2683 | + { |
| 2684 | + var value = (string)field.GetValue(null)!; |
| 2685 | + configurationKeysInCode.Add(value); |
| 2686 | + |
| 2687 | + // Build a more descriptive path for nested classes |
| 2688 | + var typeName = type.DeclaringType != null ? $"ConfigurationKeys.{type.Name}" : "ConfigurationKeys"; |
| 2689 | + keyToFieldMap[value] = $"{typeName}.{field.Name}"; |
| 2690 | + } |
| 2691 | + } |
| 2692 | + |
| 2693 | + Logger.Information("Found {KeyCount} configuration keys in {Framework}", configurationKeysInCode.Count, targetFramework); |
| 2694 | + |
| 2695 | + if (!configurationKeysInCode.Any()) |
| 2696 | + { |
| 2697 | + throw new InvalidOperationException($"Could not extract any configuration keys from {targetFramework} framework"); |
| 2698 | + } |
| 2699 | + |
| 2700 | + var canonicalAndDeprecationsInJson = canonicalKeysInJson.Concat(deprecationsInJson); |
| 2701 | + // Find keys that are in JSON but not defined in ConfigurationKeys |
| 2702 | + var missingFromCode = canonicalAndDeprecationsInJson.Except(configurationKeysInCode).ToList(); |
| 2703 | + |
| 2704 | + var allKeysInJson = canonicalAndDeprecationsInJson.Concat(aliasesInJson); |
| 2705 | + // Find keys that are defined in ConfigurationKeys but missing from JSON |
| 2706 | + var missingFromJson = configurationKeysInCode.Except(allKeysInJson).ToList(); |
| 2707 | + // Report results |
| 2708 | + if (missingFromJson.Any()) |
| 2709 | + { |
| 2710 | + Logger.Error("Configuration keys defined in ConfigurationKeys but missing from supported-configurations.json:"); |
| 2711 | + foreach (var key in missingFromJson.OrderBy(k => k)) |
| 2712 | + { |
| 2713 | + var fieldInfo = keyToFieldMap.GetValueOrDefault(key, "Unknown"); |
| 2714 | + Logger.Error(" - {Key} (defined as {FieldInfo})", key, fieldInfo); |
| 2715 | + } |
| 2716 | + } |
| 2717 | + |
| 2718 | + if (missingFromCode.Any()) |
| 2719 | + { |
| 2720 | + Logger.Warning("Configuration keys in supported-configurations.json but not defined in ConfigurationKeys:"); |
| 2721 | + foreach (var key in missingFromCode.OrderBy(k => k)) |
| 2722 | + { |
| 2723 | + Logger.Warning(" - {Key}", key); |
| 2724 | + } |
| 2725 | + } |
| 2726 | + |
| 2727 | + if (!missingFromJson.Any() && !missingFromCode.Any()) |
| 2728 | + { |
| 2729 | + Logger.Information("✅ All configuration keys are properly synchronized between ConfigurationKeys and supported-configurations.json"); |
| 2730 | + Logger.Information("Total unique configuration keys found across all frameworks: {TotalKeys}", configurationKeysInCode.Count); |
| 2731 | + } |
| 2732 | + else |
| 2733 | + { |
| 2734 | + var totalIssues = missingFromJson.Count + missingFromCode.Count; |
| 2735 | + Logger.Error("❌ Found {TotalIssues} configuration key synchronization issues", totalIssues); |
| 2736 | + |
| 2737 | + if (missingFromJson.Any()) |
| 2738 | + { |
| 2739 | + throw new InvalidOperationException($"Found {missingFromJson.Count} configuration keys defined in ConfigurationKeys but missing from supported-configurations.json. Please add them to the JSON file."); |
| 2740 | + } |
| 2741 | + } |
| 2742 | + }); |
| 2743 | + |
2577 | 2744 | private async Task CheckLogsForErrors(List<Regex> knownPatterns, bool allFilesMustExist, LogLevel minLogLevel, List<(string IgnoreReasonTag, Regex Regex)> reportablePatterns) |
2578 | 2745 | { |
2579 | 2746 | var logDirectory = BuildDataDirectory / "logs"; |
|
0 commit comments