Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions pkgs/sdk/server/src/Components.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using LaunchDarkly.Sdk.Server.Integrations;
using LaunchDarkly.Sdk.Server.Interfaces;
using LaunchDarkly.Sdk.Server.Internal;
using LaunchDarkly.Sdk.Server.Plugins;
using LaunchDarkly.Sdk.Server.Subsystems;

namespace LaunchDarkly.Sdk.Server
Expand Down Expand Up @@ -218,6 +219,27 @@ public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) =>
/// <returns>a configuration builder</returns>
public static HookConfigurationBuilder Hooks(IEnumerable<Hook> hooks) => new HookConfigurationBuilder(hooks);

/// <summary>
/// Returns a configuration builder for the SDK's plugin configuration.
/// </summary>
/// <example>
/// <code>
/// var config = Configuration.Builder(sdkKey)
/// .Add(new MyPlugin(...))
/// .Add(new MyOtherPlugin(...))
/// ).Build();
/// </code>
/// </example>
/// <returns>a configuration builder</returns>
public static PluginConfigurationBuilder Plugins() => new PluginConfigurationBuilder();

/// <summary>
/// Returns a configuration builder for the SDK's plugin configuration, with an initial set of plugins.
/// </summary>
/// <param name="plugins">a collection of plugins</param>
/// <returns>a configuration builder</returns>
public static PluginConfigurationBuilder Plugins(IEnumerable<Plugin> plugins) => new PluginConfigurationBuilder(plugins);

/// <summary>
/// Returns a configuration object that disables analytics events.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions pkgs/sdk/server/src/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public class Configuration
/// </summary>
public HookConfigurationBuilder Hooks { get; }

/// <summary>
/// Contains methods for configuring the SDK's 'plugins' to extend or customize SDK behavior.
/// </summary>
public PluginConfigurationBuilder Plugins { get; }

#endregion

#region Public methods
Expand Down Expand Up @@ -189,6 +194,7 @@ internal Configuration(ConfigurationBuilder builder)
ApplicationInfo = builder._applicationInfo;
WrapperInfo = builder._wrapperInfo;
Hooks = builder._hooks;
Plugins = builder._plugins;
}

#endregion
Expand Down
13 changes: 13 additions & 0 deletions pkgs/sdk/server/src/ConfigurationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public sealed class ConfigurationBuilder
internal bool _diagnosticOptOut = false;
internal IComponentConfigurer<IEventProcessor> _events = null;
internal HookConfigurationBuilder _hooks = null;
internal PluginConfigurationBuilder _plugins = null;
internal IComponentConfigurer<HttpConfiguration> _http = null;
internal IComponentConfigurer<LoggingConfiguration> _logging = null;
internal bool _offline = false;
Expand All @@ -65,6 +66,7 @@ internal ConfigurationBuilder(Configuration copyFrom)
_diagnosticOptOut = copyFrom.DiagnosticOptOut;
_events = copyFrom.Events;
_hooks = copyFrom.Hooks;
_plugins = copyFrom.Plugins;
_http = copyFrom.Http;
_logging = copyFrom.Logging;
_offline = copyFrom.Offline;
Expand Down Expand Up @@ -291,6 +293,17 @@ public ConfigurationBuilder Hooks(HookConfigurationBuilder hooksConfig)
return this;
}

/// <summary>
/// Configures the SDK's plugins.
/// </summary>
/// <param name="pluginsConfig">the plugin configuration</param>
/// <returns>the same builder</returns>
public ConfigurationBuilder Plugins(PluginConfigurationBuilder pluginsConfig)
{
_plugins = pluginsConfig;
return this;
}

/// <summary>
/// Sets whether or not this client is offline. If true, no calls to Launchdarkly will be made.
/// </summary>
Expand Down
42 changes: 42 additions & 0 deletions pkgs/sdk/server/src/Integrations/PluginConfigurationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
using LaunchDarkly.Sdk.Server.Plugins;
using LaunchDarkly.Sdk.Server.Subsystems;

namespace LaunchDarkly.Sdk.Server.Integrations
{
/// <summary>
/// PluginConfigurationBuilder is a builder for the SDK's plugin configuration.
/// </summary>
public sealed class PluginConfigurationBuilder
{
private readonly List<Plugin> _plugins;

/// <summary>
/// Constructs a configuration from an existing collection of plugins.
/// </summary>
public PluginConfigurationBuilder(IEnumerable<Plugin> plugins = null)
{
_plugins = plugins is null ? new List<Plugin>() : plugins.ToList();
}

/// <summary>
/// Adds a plugin to the configuration.
/// </summary>
/// <returns>the builder</returns>
public PluginConfigurationBuilder Add(Plugin plugin)
{
_plugins.Add(plugin);
return this;
}

/// <summary>
/// Builds the configuration.
/// </summary>
/// <returns>the built configuration</returns>
public PluginConfiguration Build()
{
return new PluginConfiguration(_plugins.ToList());
}
}
}
5 changes: 2 additions & 3 deletions pkgs/sdk/server/src/LaunchDarkly.ServerSdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@

<ItemGroup>
<PackageReference Include="LaunchDarkly.Cache" Version="1.0.2" />
<PackageReference Include="LaunchDarkly.CommonSdk" Version="7.0.1" />
<PackageReference Include="LaunchDarkly.CommonSdk" Version="7.1.0" />
<PackageReference Include="LaunchDarkly.EventSource" Version="5.2.0" />
<PackageReference Include="LaunchDarkly.InternalSdk" Version="3.5.1" />
<PackageReference Include="LaunchDarkly.InternalSdk" Version="3.5.3" />
<PackageReference Include="LaunchDarkly.Logging" Version="2.0.0" />
</ItemGroup>

Expand All @@ -51,7 +51,6 @@
<!-- it's a built-in package in net8.0 -->
</ItemGroup>


<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AssemblyOriginatorKeyFile>../../../../LaunchDarkly.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
Expand Down
103 changes: 71 additions & 32 deletions pkgs/sdk/server/src/LdClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using LaunchDarkly.Sdk.Server.Hooks;
using LaunchDarkly.Sdk.Server.Interfaces;
using LaunchDarkly.Sdk.Server.Internal;
using LaunchDarkly.Sdk.Integrations.Plugins;
using LaunchDarkly.Sdk.Server.Internal.BigSegments;
using LaunchDarkly.Sdk.Server.Internal.DataSources;
using LaunchDarkly.Sdk.Server.Internal.DataStores;
Expand Down Expand Up @@ -130,7 +131,7 @@ public LdClient(Configuration config)
{
_configuration = config;

var logConfig = (config.Logging ?? Components.Logging()).Build(new LdClientContext(config.SdkKey));
var logConfig = (_configuration.Logging ?? Components.Logging()).Build(new LdClientContext(_configuration.SdkKey));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of config and _configuration was mixed throughout this method. Unifying it to use the instance property once it is set.


_log = logConfig.LogAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.DefaultBase);
_log.Info("Starting LaunchDarkly client {0}",
Expand All @@ -140,24 +141,24 @@ public LdClient(Configuration config)
var taskExecutor = new TaskExecutor(this, _log);

var clientContext = new LdClientContext(
config.SdkKey,
_configuration.SdkKey,
null,
null,
null,
_log,
config.Offline,
config.ServiceEndpoints,
_configuration.Offline,
_configuration.ServiceEndpoints,
null,
taskExecutor,
config.ApplicationInfo?.Build() ?? new ApplicationInfo(),
config.WrapperInfo?.Build()
_configuration.ApplicationInfo?.Build() ?? new ApplicationInfo(),
_configuration.WrapperInfo?.Build()
);

var httpConfig = (config.Http ?? Components.HttpConfiguration()).Build(clientContext);
var httpConfig = (_configuration.Http ?? Components.HttpConfiguration()).Build(clientContext);
clientContext = clientContext.WithHttp(httpConfig);

var diagnosticStore = _configuration.DiagnosticOptOut ? null :
new ServerDiagnosticStore(config, clientContext);
new ServerDiagnosticStore(_configuration, clientContext);
clientContext = clientContext.WithDiagnosticStore(diagnosticStore);

var dataStoreUpdates = new DataStoreUpdatesImpl(taskExecutor, _log.SubLogger(LogNames.DataStoreSubLog));
Expand Down Expand Up @@ -185,25 +186,33 @@ public LdClient(Configuration config)
);

var eventProcessorFactory =
config.Offline ? Components.NoEvents :
(_configuration.Events?? Components.SendEvents());
_configuration.Offline ? Components.NoEvents :
(_configuration.Events ?? Components.SendEvents());
_eventProcessor = eventProcessorFactory.Build(clientContext);

var dataSourceUpdates = new DataSourceUpdatesImpl(_dataStore, _dataStoreStatusProvider,
taskExecutor, _log, logConfig.LogDataSourceOutageAsErrorAfter);
IComponentConfigurer<IDataSource> dataSourceFactory =
config.Offline ? Components.ExternalUpdatesOnly :
_configuration.Offline ? Components.ExternalUpdatesOnly :
(_configuration.DataSource ?? Components.StreamingDataSource());
_dataSource = dataSourceFactory.Build(clientContext.WithDataSourceUpdates(dataSourceUpdates));
_dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdates);
_flagTracker = new FlagTrackerImpl(dataSourceUpdates,
(string key, Context context) => JsonVariation(key, context, LdValue.Null));

var hookConfig = (config.Hooks ?? Components.Hooks()).Build();
_hookExecutor = hookConfig.Hooks.Any() ?
(IHookExecutor) new Executor(_log.SubLogger(LogNames.HooksSubLog), hookConfig.Hooks)
var allHooks = new List<Hook>();

var hookConfig = (_configuration.Hooks ?? Components.Hooks()).Build();
allHooks.AddRange(hookConfig.Hooks);

var pluginConfig = (_configuration.Plugins ?? Components.Plugins()).Build();
EnvironmentMetadata environmentMetadata = CreateEnvironmentMetadata(clientContext);
allHooks.AddRange(this.GetPluginHooks(pluginConfig.Plugins, environmentMetadata, _log));
_hookExecutor = allHooks.Any() ?
(IHookExecutor)new Executor(_log.SubLogger(LogNames.HooksSubLog), allHooks)
: new NoopExecutor();

this.RegisterPlugins(pluginConfig.Plugins, environmentMetadata, _log);

var initTask = _dataSource.Start();

Expand Down Expand Up @@ -323,7 +332,7 @@ public MigrationVariation MigrationVariation(string key, Context context, Migrat
var (detail, flag) = EvaluateWithHooks(Method.MigrationVariation, key, context, LdValue.Of(defaultStage.ToDataModelString()),
LdValue.Convert.String, true, EventFactory.Default);

var nullableStage = MigrationStageExtensions.FromDataModelString(detail.Value);
var nullableStage = MigrationStageExtensions.FromDataModelString(detail.Value);
var stage = nullableStage ?? defaultStage;
if (nullableStage == null)
{
Expand Down Expand Up @@ -648,24 +657,34 @@ public Logger GetLogger()

#region Private methods

private FeatureFlag GetFlag(string key)
/// <summary>
/// Creates metadata about the environment, including SDK and application information.
/// </summary>
/// <param name="clientContext">The client context containing application and wrapper information.</param>
/// <returns>An <see cref="EnvironmentMetadata"/> instance containing the environment metadata.</returns>
/// <remarks>
/// This method constructs the environment metadata using the SDK key, application ID, and version,
/// along with any wrapper information if available. It is used to provide context for plugins and
/// hooks that may need to interact with the environment.
/// </remarks>
private EnvironmentMetadata CreateEnvironmentMetadata(LdClientContext clientContext)
{
var maybeItem = _dataStore.Get(DataModel.Features, key);
if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is FeatureFlag f)
{
return f;
}
return null;
}

private Segment GetSegment(string key)
{
var maybeItem = _dataStore.Get(DataModel.Segments, key);
if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is Segment s)
{
return s;
}
return null;
var applicationInfo = clientContext.ApplicationInfo;
var wrapperInfo = _configuration.WrapperInfo?.Build();

var sdkMetadata = new SdkMetadata(
"dotnet-server-sdk",
AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)),
wrapperInfo?.Name,
wrapperInfo?.Version
);

var applicationMetadata = new ApplicationMetadata(
applicationInfo.ApplicationId,
applicationInfo.ApplicationVersion
);

return new EnvironmentMetadata(sdkMetadata, _configuration.SdkKey, CredentialType.SdkKey, applicationMetadata);
}

private void Dispose(bool disposing)
Expand Down Expand Up @@ -694,6 +713,26 @@ private string GetEnvironmentId()
return null;
}

private FeatureFlag GetFlag(string key)
{
var maybeItem = _dataStore.Get(DataModel.Features, key);
if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is FeatureFlag f)
{
return f;
}
return null;
}

private Segment GetSegment(string key)
{
var maybeItem = _dataStore.Get(DataModel.Segments, key);
if (maybeItem.HasValue && maybeItem.Value.Item != null && maybeItem.Value.Item is Segment s)
{
return s;
}
return null;
}

#endregion
}
}
23 changes: 23 additions & 0 deletions pkgs/sdk/server/src/Plugins/Plugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using LaunchDarkly.Sdk.Server.Hooks;
using LaunchDarkly.Sdk.Server.Interfaces;
using LaunchDarkly.Sdk.Integrations.Plugins;

namespace LaunchDarkly.Sdk.Server.Plugins
{
/// <summary>
/// Abstract base class for extending SDK functionality via plugins in the server-side SDK.
/// All provided server-side plugin implementations MUST inherit from this class.
/// </summary>
public abstract class Plugin : PluginBase<ILdClient, Hook>
{
/// <summary>
/// Initializes a new instance of the <see cref="Plugin"/> class with the specified name.
/// </summary>
/// <param name="name">The name of the plugin.</param>
protected Plugin(string name)
: base(name)
{
}
}
}
25 changes: 25 additions & 0 deletions pkgs/sdk/server/src/Subsystems/PluginConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using LaunchDarkly.Sdk.Server.Plugins;

namespace LaunchDarkly.Sdk.Server.Subsystems
{
/// <summary>
/// Configuration containing plugins for the SDK.
/// </summary>
public sealed class PluginConfiguration
{
/// <summary>
/// The collection of plugins.
/// </summary>
public IEnumerable<Plugin> Plugins { get; }

/// <summary>
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class with the specified plugins.
/// </summary>
/// <param name="plugins">The plugins to include in this configuration.</param>
public PluginConfiguration(IEnumerable<Plugin> plugins)
{
Plugins = plugins;
}
}
}
Loading
Loading