// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.Encodings.Web; using System.Text.Json; using IntegrationsJsonGenerator; const string instrumentMethodAttributeName = "OpenTelemetry.AutoInstrumentation.Instrumentations.InstrumentMethodAttribute"; var thisFilePath = GetSourceFilePathName(); var solutionFolder = Path.Combine(thisFilePath, "..", "..", ".."); var autoInstrumentationLibPath = Path.Combine(solutionFolder, "bin", "tracer-home", "net", "OpenTelemetry.AutoInstrumentation.dll"); var autoInstrumentationLib = Assembly.LoadFrom(autoInstrumentationLibPath); var assemblyInstrumentMethodAttributes = autoInstrumentationLib.DefinedTypes .Where(type => InheritsFrom(type, instrumentMethodAttributeName)).Select(x => x.FullName); var integrations = new Dictionary<(string, string), Integration>(); foreach (var typeInfo in autoInstrumentationLib.GetTypes()) { foreach (var attribute in typeInfo.GetCustomAttributes() .Where(a => assemblyInstrumentMethodAttributes.Contains(a.GetType().FullName))) { var integration = ConvertToIntegration(typeInfo.FullName!, attribute); if (!integrations.ContainsKey((integration.IntegartionType, integration.IntegrationName))) { integrations.Add( (integration.IntegartionType, integration.IntegrationName), new Integration { Name = integration.IntegrationName, Type = integration.IntegartionType, MethodReplacements = new List { integration.MethodReplacement } }); } else { var integration2 = integrations[(integration.IntegartionType, integration.IntegrationName)]; integration2.MethodReplacements.Add(integration.MethodReplacement); } } } var productionIntegrations = integrations.Where(x => x.Key.Item2 != "StrongNamedValidation").Select(x => x.Value) .OrderBy(x => x.Name).ToArray(); var testIntegrations = integrations.Where(x => x.Key.Item2 == "StrongNamedValidation").Select(x => AppendMockIntegrations(x.Value)) .OrderBy(x => x.Name).ToArray(); UpdateIntegrationFile(Path.Combine(solutionFolder, "integrations.json"), productionIntegrations); UpdateIntegrationFile(Path.Combine(solutionFolder, "test", "IntegrationTests", "StrongNamedTestsIntegrations.json"), testIntegrations); bool InheritsFrom(Type type, string baseType) { while (true) { if (type.FullName == baseType) { return true; } if (type.BaseType is null) { return false; } type = type.BaseType; } } (string IntegartionType, string IntegrationName, MethodReplacement MethodReplacement) ConvertToIntegration(string wrapperTypeName, Attribute attribute) { var integrationName = GetPropertyValue("IntegrationName", attribute); var integrationType = GetPropertyValue("Type", attribute).ToString()!; var methodReplacement = new MethodReplacement { Wrapper = { Type = wrapperTypeName }, Target = { Assembly = GetPropertyValue("AssemblyName", attribute), Type = GetPropertyValue("TypeName", attribute), Method = GetPropertyValue("MethodName", attribute) } }; var returnTypeName = GetPropertyValue("ReturnTypeName", attribute); var parameterTypeNames = GetPropertyValue("ParameterTypeNames", attribute); methodReplacement.Target.SignatureTypes = new[] { returnTypeName }.Concat(parameterTypeNames).ToArray(); var minVersion = GetPropertyValue("MinimumVersion", attribute).Split('.'); methodReplacement.Target.MinimumMajor = int.Parse(minVersion[0]); if (minVersion.Length > 1 && minVersion[1] != "*") { methodReplacement.Target.MinimumMinor = int.Parse(minVersion[1]); } if (minVersion.Length > 2 && minVersion[2] != "*") { methodReplacement.Target.MinimumPath = int.Parse(minVersion[2]); } var maxVersion = GetPropertyValue("MaximumVersion", attribute).Split('.'); methodReplacement.Target.MaximumMajor = int.Parse(maxVersion[0]); if (maxVersion.Length > 1 && maxVersion[1] != "*") { methodReplacement.Target.MaximumMinor = int.Parse(maxVersion[1]); } if (maxVersion.Length > 2 && maxVersion[2] != "*") { methodReplacement.Target.MaximumPath = int.Parse(maxVersion[2]); } return (integrationType, integrationName, methodReplacement); } T GetPropertyValue(string propertyName, Attribute attribute) { var type = attribute.GetType(); var getMethod = type.GetProperty(propertyName)!.GetGetMethod()!; if (!getMethod.ReturnType.IsAssignableTo(typeof(T))) { throw new ArgumentException($"Property {propertyName} is not assignable to {typeof(T)}"); } var value = getMethod.Invoke(attribute, Array.Empty())!; return (T)value; } void UpdateIntegrationFile(string filePath, Integration[] productionIntegrations1) { using var fileStream = new FileStream(filePath, FileMode.Truncate); var jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; JsonSerializer.Serialize(fileStream, productionIntegrations1, jsonSerializerOptions); } static string GetSourceFilePathName([CallerFilePath] string? callerFilePath = null) => callerFilePath ?? string.Empty; static Integration AppendMockIntegrations(Integration testIntegration) { // Add some special cases used by the integration tests. This way the integrations // file used in the integrations test doesn't change on each run of the tool. var targetAssembly = testIntegration.MethodReplacements[0].Target.Assembly; var targetType = testIntegration.MethodReplacements[0].Target.Type; var targetSignatureTypes = testIntegration.MethodReplacements[0].Target.SignatureTypes; testIntegration.MethodReplacements.Add(new MethodReplacement { Target = new Target { Assembly = targetAssembly, Type = targetType, Method = "InstrumentationTargetMissingBytecodeInstrumentationType", SignatureTypes = targetSignatureTypes, MaximumMajor = 1, MinimumMajor = 1, }, Wrapper = new Wrapper { Type = "OpenTelemetry.AutoInstrumentation.Instrumentations.Validations.MissingInstrumentationType", }, }); testIntegration.MethodReplacements.Add(new MethodReplacement { Target = new Target { Assembly = testIntegration.MethodReplacements[0].Target.Assembly, Type = targetType, Method = "InstrumentationTargetMissingBytecodeInstrumentationMethod", SignatureTypes = targetSignatureTypes, MaximumMajor = 1, MinimumMajor = 1, }, Wrapper = new Wrapper { Type = "OpenTelemetry.AutoInstrumentation.DuckTyping.DuckAttribute", }, }); return testIntegration; }