Skip to content

Commit

Permalink
Use plugin manager to organize plugin loading. Add possibility to loa…
Browse files Browse the repository at this point in the history
…d multiple plugins.
  • Loading branch information
RassK committed Apr 27, 2021
1 parent ea62865 commit e6e4874
Show file tree
Hide file tree
Showing 20 changed files with 340 additions and 221 deletions.
37 changes: 2 additions & 35 deletions src/Datadog.Trace.ClrProfiler.Managed/Instrumentation.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using Datadog.Trace.Configuration;
using Datadog.Trace.DiagnosticListeners;
using Datadog.Trace.Logging;
using Datadog.Trace.Plugins;
using Datadog.Trace.ServiceFabric;

namespace Datadog.Trace.ClrProfiler
Expand Down Expand Up @@ -61,7 +60,7 @@ public static void Initialize()

try
{
TryLoadVendorPlugin();
PluginManager.TryLoadPlugins(GlobalSettings.Source.PluginsConfiguration);

// ensure global instance is created if it's not already
_ = Tracer.Instance;
Expand Down Expand Up @@ -130,37 +129,5 @@ private static void StartDiagnosticManager()
DiagnosticManager.Instance = diagnosticManager;
}
#endif

private static void TryLoadVendorPlugin()
{
string pluginName = GlobalSettings.Source.VendorPluginName;
if (!string.IsNullOrWhiteSpace(pluginName))
{
string pluginPath = Path.Combine(
Path.GetDirectoryName(typeof(Instrumentation).Assembly.Location),
pluginName);

if (File.Exists(pluginPath))
{
try
{
Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);

GlobalSettings.SetVendorPlugin(pluginAssembly);

Log.Information("Vendor plugin assembly loaded '{0}'.", pluginAssembly.FullName);
}
catch (Exception ex)
{
Log.Warning(ex, "Plugin assembly could not be loaded.");
Log.Information("Skipping vendor plugin load");
}
}
else
{
Log.Warning("Plugin path is defined but could not find the path '{0}'.", pluginPath);
}
}
}
}
}
10 changes: 5 additions & 5 deletions src/Datadog.Trace/Configuration/ConfigurationKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ public static class ConfigurationKeys
/// </summary>
public const string ConfigurationFileName = "OTEL_TRACE_CONFIG_FILE";

/// <summary>
/// Configuration key for the path to the plugins configuration file.
/// </summary>
public const string PluginConfigurationFileName = "OTEL_DOTNET_TRACER_PLUGINS_FILE";

/// <summary>
/// Configuration key for the application's environment. Sets the "env" tag on every <see cref="Span"/>.
/// </summary>
Expand Down Expand Up @@ -348,11 +353,6 @@ public static class ConfigurationKeys
/// <seealso cref="TracerSettings.PartialFlushMinSpans"/>
public const string PartialFlushMinSpans = "OTEL_TRACE_PARTIAL_FLUSH_MIN_SPANS";

/// <summary>
/// Configuration key for specifing vendor plugin path
/// </summary>
public const string VendorPluginName = "OTEL_VENDOR_PLUGIN";

/// <summary>
/// String format patterns used to match integration-specific configuration keys.
/// </summary>
Expand Down
22 changes: 0 additions & 22 deletions src/Datadog.Trace/Configuration/DefaultFactoryConfigurator.cs

This file was deleted.

82 changes: 25 additions & 57 deletions src/Datadog.Trace/Configuration/GlobalSettings.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Datadog.Trace.Logging;
using Datadog.Trace.Vendors.Serilog.Events;

Expand Down Expand Up @@ -35,10 +33,10 @@ internal GlobalSettings(IConfigurationSource source)
// default value
true;

VendorPluginName = source?.GetString(ConfigurationKeys.VendorPluginName);

// Sets initial / fallback configuration
FactoryConfigurator = new DefaultFactoryConfigurator();
if (TryLoadPluginJsonConfigurationFile(source, out JsonConfigurationSource jsonConfigurationSource))
{
PluginsConfiguration = jsonConfigurationSource;
}
}

/// <summary>
Expand All @@ -49,21 +47,6 @@ internal GlobalSettings(IConfigurationSource source)
/// <seealso cref="ConfigurationKeys.DebugEnabled"/>
public bool DebugEnabled { get; private set; }

/// <summary>
/// Gets the vendor plugin path.
/// </summary>
public string VendorPluginName { get; private set; }

/// <summary>
/// Gets a value indicating whether vendor plugin is loaded.
/// </summary>
public bool VendorPluginLoaded { get; private set; }

/// <summary>
/// Gets the vendor plugin assembly.
/// </summary>
public Assembly VendorPlugin { get; private set; }

/// <summary>
/// Gets or sets the global settings instance.
/// </summary>
Expand All @@ -78,9 +61,9 @@ internal GlobalSettings(IConfigurationSource source)
internal bool DiagnosticSourceEnabled { get; }

/// <summary>
/// Gets the factory configurator.
/// Gets the plugins configuration.
/// </summary>
internal IFactoryConfigurator FactoryConfigurator { get; private set; }
internal JsonConfigurationSource PluginsConfiguration { get; }

/// <summary>
/// Set whether debug mode is enabled.
Expand All @@ -101,18 +84,6 @@ public static void SetDebugEnabled(bool enabled)
}
}

/// <summary>
/// Sets a vendor plugin on successful load.
/// </summary>
/// <param name="assembly">Loaded plugin assembly</param>
public static void SetVendorPlugin(Assembly assembly)
{
Source.VendorPluginLoaded = true;
Source.VendorPlugin = assembly;

LoadVendorFactoryConfigurator();
}

/// <summary>
/// Used to refresh global settings when environment variables or config sources change.
/// This is not necessary if changes are set via code, only environment.
Expand Down Expand Up @@ -160,15 +131,28 @@ internal static CompositeConfigurationSource CreateDefaultConfigurationSource()
return configurationSource;
}

private static bool TryLoadJsonConfigurationFile(IConfigurationSource configurationSource, out IConfigurationSource jsonConfigurationSource)
internal static bool TryLoadPluginJsonConfigurationFile(IConfigurationSource configurationSource, out JsonConfigurationSource jsonConfigurationSource)
{
var configurationFileName = configurationSource.GetString(ConfigurationKeys.PluginConfigurationFileName) ??
Path.Combine(GetCurrentDirectory(), "plugins.json");

return TryLoadJsonConfigurationFile(configurationFileName, out jsonConfigurationSource);
}

private static bool TryLoadJsonConfigurationFile(IConfigurationSource configurationSource, out JsonConfigurationSource jsonConfigurationSource)
{
// if environment variable is not set, look for default file name in the current directory
var configurationFileName = configurationSource.GetString(ConfigurationKeys.ConfigurationFileName) ??
configurationSource.GetString("OTEL_DOTNET_TRACER_CONFIG_FILE") ??
Path.Combine(GetCurrentDirectory(), "datadog.json");

return TryLoadJsonConfigurationFile(configurationFileName, out jsonConfigurationSource);
}

private static bool TryLoadJsonConfigurationFile(string configurationFileName, out JsonConfigurationSource jsonConfigurationSource)
{
try
{
// if environment variable is not set, look for default file name in the current directory
var configurationFileName = configurationSource.GetString(ConfigurationKeys.ConfigurationFileName) ??
configurationSource.GetString("OTEL_DOTNET_TRACER_CONFIG_FILE") ??
Path.Combine(GetCurrentDirectory(), "datadog.json");

if (string.Equals(Path.GetExtension(configurationFileName), ".JSON", StringComparison.OrdinalIgnoreCase) &&
File.Exists(configurationFileName))
{
Expand Down Expand Up @@ -223,21 +207,5 @@ private static bool TryLoadHostingEnvironmentPath(out string hostingPath)
hostingPath = default;
return false;
}

private static void LoadVendorFactoryConfigurator()
{
if (Source.VendorPluginLoaded)
{
var factoryInterface = typeof(IFactoryConfigurator);
var factoryType = Source.VendorPlugin
.GetTypes()
.FirstOrDefault(x => factoryInterface.IsAssignableFrom(x));

if (factoryType != null)
{
Source.FactoryConfigurator = (IFactoryConfigurator)Activator.CreateInstance(factoryType);
}
}
}
}
}
16 changes: 0 additions & 16 deletions src/Datadog.Trace/Configuration/IFactoryConfigurator.cs

This file was deleted.

9 changes: 9 additions & 0 deletions src/Datadog.Trace/Configuration/JsonConfigurationSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ public IDictionary<string, string> GetDictionary(string key, bool allowOptionalM
return GetDictionaryInternal(key, allowOptionalMappings);
}

/// <summary>
/// Gets the string representation of json config.
/// </summary>
/// <returns>String format.</returns>
public override string ToString()
{
return _configuration.ToString();
}

private IDictionary<string, string> GetDictionaryInternal(string key, bool allowOptionalMappings)
{
var token = _configuration.SelectToken(key, errorWhenNoMatch: false);
Expand Down
16 changes: 16 additions & 0 deletions src/Datadog.Trace/Plugins/IOTelPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Datadog.Trace.Propagation;

namespace Datadog.Trace.Plugins
{
/// <summary>
/// Provides extendibility points for tracer
/// </summary>
public interface IOTelPlugin
{
/// <summary>
/// Get the propagator provider.
/// </summary>
/// <returns>Propagator factory.</returns>
IPropagatorsProvider GetPropagatorsProvider();
}
}
108 changes: 108 additions & 0 deletions src/Datadog.Trace/Plugins/PluginManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Datadog.Trace.Configuration;
using Datadog.Trace.Logging;
using Datadog.Trace.Util;
using Datadog.Trace.Vendors.Newtonsoft.Json.Linq;

namespace Datadog.Trace.Plugins
{
internal static class PluginManager
{
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(PluginManager));

public static IReadOnlyCollection<IOTelPlugin> Loaded { get; private set; } = ArrayHelper.Empty<IOTelPlugin>();

internal static void TryLoadPlugins(JsonConfigurationSource pluginsConfig)
{
if (pluginsConfig == null)
{
return;
}

var runtimeName = GetPluginsRuntimeName();

Log.Debug("Executing plugins configuration: {0}", pluginsConfig);
Log.Information("Trying to load plugins for '{0}' runtime.", runtimeName);

// TODO: Detect if objects instead of path
var pluginFiles = pluginsConfig?.GetValue<JToken>($"['{runtimeName}']").ToObject<string[]>();

if (pluginFiles == null || !pluginFiles.Any())
{
Log.Information("Skipping plugins load. Could not find any plugins for '{0}' runtime.", runtimeName);

return;
}

var loadedPlugins = TryLoadPlugins(pluginFiles);

Log.Information("Successfully loaded '{0}' plugin(s).", property: loadedPlugins.Count);

Loaded = loadedPlugins;
}

private static IReadOnlyCollection<IOTelPlugin> TryLoadPlugins(string[] pluginFiles)
{
var loaded = new List<IOTelPlugin>();

foreach (string file in pluginFiles)
{
string fullPath = Path.GetFullPath(file);

if (File.Exists(fullPath))
{
try
{
Assembly pluginAssembly = Assembly.LoadFrom(fullPath);
IOTelPlugin plugin = ConvertToPlugin(pluginAssembly);

if (plugin != null)
{
loaded.Add(plugin);

Log.Information("Plugin assembly loaded '{0}'.", pluginAssembly.FullName);
}
else
{
Log.Warning("Could not load {0} from '{1}'.", nameof(IOTelPlugin), pluginAssembly.FullName);
}
}
catch (Exception ex)
{
Log.Warning(ex, "Plugin assembly could not be loaded.");
Log.Information("Skipping vendor plugin load.");
}
}
else
{
Log.Warning("Plugin path is defined but could not find the path '{0}'.", fullPath);
}
}

return loaded;
}

private static IOTelPlugin ConvertToPlugin(Assembly assembly)
{
var pluginType = typeof(IOTelPlugin);

var pluginInstance = assembly
.GetTypes()
.Where(p => pluginType.IsAssignableFrom(p))
.Select(p => (IOTelPlugin)Activator.CreateInstance(p))
.FirstOrDefault();

return pluginInstance;
}

private static string GetPluginsRuntimeName()
{
// returns the runtime directory of the current running tracer
return Directory.GetParent(typeof(PluginManager).Assembly.Location).Name;
}
}
}
Loading

0 comments on commit e6e4874

Please sign in to comment.