/// <summary> /// Invokes the Elastic.Apm.StartupHook.Loader.Loader.Initialize() method from a specific assembly /// </summary> /// <param name="loaderAssembly">The loader assembly</param> private static void InvokerLoaderMethod(Assembly loaderAssembly) { if (loaderAssembly is null) { return; } const string loaderTypeName = "Elastic.Apm.StartupHook.Loader.Loader"; const string loaderTypeMethod = "Initialize"; _logger.WriteLine($"Get {loaderTypeName} type"); var loaderType = loaderAssembly.GetType(loaderTypeName); if (loaderType is null) { _logger.WriteLine($"{loaderTypeName} type is null"); return; } _logger.WriteLine($"Get {loaderTypeName}.{loaderTypeMethod} method"); var initializeMethod = loaderType.GetMethod(loaderTypeMethod, BindingFlags.Public | BindingFlags.Static); if (initializeMethod is null) { _logger.WriteLine($"{loaderTypeName}.{loaderTypeMethod} method is null"); return; } _logger.WriteLine($"Invoke {loaderTypeName}.{loaderTypeMethod} method"); initializeMethod.Invoke(null, null); }
static void LoadDiagnosticSubscriber(IDiagnosticsSubscriber diagnosticsSubscriber, StartupHookLogger logger) { try { Agent.Subscribe(diagnosticsSubscriber); } catch (Exception e) { logger.WriteLine($"Failed subscribing to {diagnosticsSubscriber.GetType().Name}, " + $"Exception type: {e.GetType().Name}, message: {e.Message}"); } }
/// <summary> /// Loads assemblies from the loader directory if they exist /// </summary> /// <param name="loaderDirectory"></param> private static void LoadAssembliesFromLoaderDirectory(string loaderDirectory) { var context = new ElasticApmAssemblyLoadContext(); AssemblyLoadContext.Default.Resolving += (_, name) => { var assemblyPath = Path.Combine(loaderDirectory, name.Name + ".dll"); if (File.Exists(assemblyPath)) { try { var assemblyName = AssemblyName.GetAssemblyName(assemblyPath); if (name.Version == assemblyName.Version) { var keyToken = name.GetPublicKeyToken(); var assemblyKeyToken = assemblyName.GetPublicKeyToken(); if (keyToken.SequenceEqual(assemblyKeyToken)) { // load Elastic.Apm assemblies with the default assembly load context, to allow DiagnosticListeners to subscribe. // For all other dependencies, load with a separate load context to not conflict with application dependencies. return(name.Name.StartsWith("Elastic.Apm", StringComparison.Ordinal) ? AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath) : context.LoadFromAssemblyPath(assemblyPath)); } } } catch (Exception e) { _logger.WriteLine(e.ToString()); } } return(null); }; }
/// <summary> /// The Initialize method called by DOTNET_STARTUP_HOOKS /// </summary> public static void Initialize() { var startupHookEnvVar = Environment.GetEnvironmentVariable("DOTNET_STARTUP_HOOKS"); var startupHookDirectory = Path.GetDirectoryName(startupHookEnvVar); if (string.IsNullOrEmpty(startupHookEnvVar) || !File.Exists(startupHookEnvVar)) { return; } _logger = StartupHookLogger.Create(); _logger.WriteLine($"Check if {SystemDiagnosticsDiagnosticsource} is loaded"); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); _logger.WriteLine($"Assemblies loaded:{Environment.NewLine}{string.Join(Environment.NewLine, assemblies.Select(a => a.GetName()))}"); var diagnosticSourceAssemblies = assemblies .Where(a => a.GetName().Name.Equals(SystemDiagnosticsDiagnosticsource, StringComparison.Ordinal)) .ToList(); Assembly diagnosticSourceAssembly; switch (diagnosticSourceAssemblies.Count) { case 0: _logger.WriteLine($"No {SystemDiagnosticsDiagnosticsource} loaded. Load using AssemblyLoadContext.Default"); diagnosticSourceAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(SystemDiagnosticsDiagnosticsource)); break; case 1: diagnosticSourceAssembly = diagnosticSourceAssemblies[0]; break; default: _logger.WriteLine($"Found {diagnosticSourceAssemblies.Count} {SystemDiagnosticsDiagnosticsource} assemblies loaded in the app domain"); diagnosticSourceAssembly = diagnosticSourceAssemblies.First(); break; } Assembly loaderAssembly = null; string loaderDirectory; if (diagnosticSourceAssembly is null) { // use agent compiled against the highest version of System.Diagnostics.DiagnosticSource var highestAvailableAgent = Directory.EnumerateDirectories(startupHookDirectory) .Where(d => VersionRegex.IsMatch(d)) .OrderByDescending(d => VersionRegex.Match(d).Groups["major"].Value) .First(); loaderDirectory = Path.Combine(startupHookDirectory, highestAvailableAgent); loaderAssembly = AssemblyLoadContext.Default .LoadFromAssemblyPath(Path.Combine(loaderDirectory, LoaderDll)); } else { var diagnosticSourceAssemblyName = diagnosticSourceAssembly.GetName(); var diagnosticSourcePublicKeyToken = diagnosticSourceAssemblyName.GetPublicKeyToken(); if (!diagnosticSourcePublicKeyToken.SequenceEqual(SystemDiagnosticsDiagnosticSourcePublicKeyToken)) { _logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} public key token " + $"{PublicKeyTokenBytesToString(diagnosticSourcePublicKeyToken)} did not match expected " + $"public key token {PublicKeyTokenBytesToString(SystemDiagnosticsDiagnosticSourcePublicKeyToken)}"); return; } var diagnosticSourceVersion = diagnosticSourceAssemblyName.Version; _logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} {diagnosticSourceVersion} loaded"); loaderDirectory = Path.Combine(startupHookDirectory, $"{diagnosticSourceVersion.Major}.0.0"); if (Directory.Exists(loaderDirectory)) { loaderAssembly = AssemblyLoadContext.Default .LoadFromAssemblyPath(Path.Combine(loaderDirectory, LoaderDll)); } else { _logger.WriteLine( $"No compatible agent for {SystemDiagnosticsDiagnosticsource} {diagnosticSourceVersion}. Agent not loaded"); } } if (loaderAssembly is null) { _logger.WriteLine( $"No {LoaderDll} assembly loaded. Agent not loaded"); } LoadAssembliesFromLoaderDirectory(loaderDirectory); InvokerLoaderMethod(loaderAssembly); }