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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.*" />
<PackageReference Include="LaunchDarkly.ServerSdk" Version="[8.7, 9.0)" />
Copy link
Member Author

Choose a reason for hiding this comment

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

Hook 1.1.0 will require SDK 8.7.0 or greater. I constrained to less than 9 for now.

</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net462'">
Expand Down
35 changes: 33 additions & 2 deletions pkgs/telemetry/src/TracingHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ public class TracingHookBuilder
{
private bool _createActivities;
private bool _includeVariant;
private string _environmentId;

internal TracingHookBuilder()
{
_createActivities = false;
_includeVariant = false;
_environmentId = null;
}

/// <summary>
Expand Down Expand Up @@ -51,6 +53,22 @@ public TracingHookBuilder IncludeVariant(bool includeVariant = true)
return this;
}

/// <summary>
/// The environment ID associated with the SDK configuration. In typical usage the environment ID should not be
/// specified. The environment ID only needs to be manually specified if it cannot be retrieved from the SDK.
/// <para>
/// This is not the same as the SDK key. The environment ID is equivalent to the client-side ID in the
/// LaunchDarkly UI and documentation.
/// </para>
/// </summary>
/// <param name="environmentId">The environment the SDK is configured to connect to.</param>
/// <returns>this builder</returns>
public TracingHookBuilder EnvironmentId(string environmentId)
{
_environmentId = environmentId;
return this;
}

/// <summary>
/// Builds the <see cref="TracingHook"/> with the configured options.
///
Expand All @@ -59,7 +77,7 @@ public TracingHookBuilder IncludeVariant(bool includeVariant = true)
/// <returns>the new hook</returns>
public TracingHook Build()
{
return new TracingHook(new TracingHook.Options(_createActivities, _includeVariant));
return new TracingHook(new TracingHook.Options(_createActivities, _includeVariant, _environmentId));
}
}

Expand Down Expand Up @@ -92,17 +110,21 @@ private static class SemanticAttributes
public const string FeatureFlagProviderName = "feature_flag.provider_name";
public const string FeatureFlagVariant = "feature_flag.variant";
public const string FeatureFlagContextKeyAttributeName = "feature_flag.context.key";
public const string FeatureFlagSetId = "feature_flag.set.id";
}

internal struct Options
{
public bool CreateActivities { get; }
public bool IncludeVariant { get; }

public Options(bool createActivities, bool includeVariant)
public string EnvironmentId { get; }

public Options(bool createActivities, bool includeVariant, string environmentId = null)
{
CreateActivities = createActivities;
IncludeVariant = includeVariant;
EnvironmentId = environmentId;
}
}

Expand Down Expand Up @@ -185,6 +207,15 @@ public override SeriesData AfterEvaluation(EvaluationSeriesContext context, Seri
{SemanticAttributes.FeatureFlagContextKeyAttributeName, context.Context.FullyQualifiedKey},
};

if (_options.EnvironmentId != null)
{
attributes[SemanticAttributes.FeatureFlagSetId] = _options.EnvironmentId;
}
else if (context.EnvironmentId != null)
{
attributes[SemanticAttributes.FeatureFlagSetId] = context.EnvironmentId;
}

if (_options.IncludeVariant)
{
attributes.Add(SemanticAttributes.FeatureFlagVariant, detail.Value.ToJsonString());
Expand Down
143 changes: 137 additions & 6 deletions pkgs/telemetry/test/TracingHookTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ public void CanConstructTracingHook()
Assert.Equal("LaunchDarkly Tracing Hook", hook.Metadata.Name);
}

[Fact]
public void CanConstructTracingHookWithEnvironmentId()
{
var hook = TracingHook.Builder().EnvironmentId("env-123").Build();
Assert.NotNull(hook);
}

[Fact]
public void CanRetrieveActivitySourceName()
{
Expand Down Expand Up @@ -64,12 +71,14 @@ public void TracingHookCreatesRootSpans(bool createSpans)
var featureKey = "feature-key";
var context = Context.New("foo");

var evalContext1 = new EvaluationSeriesContext(featureKey, context, LdValue.Of(true), "LdClient.BoolVariation");
var evalContext1 =
new EvaluationSeriesContext(featureKey, context, LdValue.Of(true), "LdClient.BoolVariation");
var data1 = hookUnderTest.BeforeEvaluation(evalContext1, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext1, data1,
new EvaluationDetail<LdValue>(LdValue.Of(true), 0, EvaluationReason.FallthroughReason));

var evalContext2 = new EvaluationSeriesContext(featureKey, context, LdValue.Of("default"), "LdClient.StringVariation");
var evalContext2 =
new EvaluationSeriesContext(featureKey, context, LdValue.Of("default"), "LdClient.StringVariation");
var data2 = hookUnderTest.BeforeEvaluation(evalContext2, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext2, data2,
new EvaluationDetail<LdValue>(LdValue.Of("default"), 0, EvaluationReason.FallthroughReason));
Expand Down Expand Up @@ -117,12 +126,14 @@ public void TracingHookCreatesChildSpans(bool createSpans)

var rootActivity = testSource.StartActivity("root-activity");

var evalContext1 = new EvaluationSeriesContext(featureKey, context, LdValue.Of(true), "LdClient.BoolVariation");
var evalContext1 =
new EvaluationSeriesContext(featureKey, context, LdValue.Of(true), "LdClient.BoolVariation");
var data1 = hookUnderTest.BeforeEvaluation(evalContext1, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext1, data1,
new EvaluationDetail<LdValue>(LdValue.Of(true), 0, EvaluationReason.FallthroughReason));

var evalContext2 = new EvaluationSeriesContext(featureKey, context, LdValue.Of("default"), "LdClient.StringVariation");
var evalContext2 =
new EvaluationSeriesContext(featureKey, context, LdValue.Of("default"), "LdClient.StringVariation");
var data2 = hookUnderTest.BeforeEvaluation(evalContext2, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext2, data2,
new EvaluationDetail<LdValue>(LdValue.Of("default"), 0, EvaluationReason.FallthroughReason));
Expand Down Expand Up @@ -173,12 +184,14 @@ public void TracingHookIncludesVariant(bool includeVariant)

var rootActivity = testSource.StartActivity("root-activity");

var evalContext1 = new EvaluationSeriesContext(featureKey, context, LdValue.Of(true), "LdClient.BoolVariation");
var evalContext1 =
new EvaluationSeriesContext(featureKey, context, LdValue.Of(true), "LdClient.BoolVariation");
var data1 = hookUnderTest.BeforeEvaluation(evalContext1, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext1, data1,
new EvaluationDetail<LdValue>(LdValue.Of(true), 0, EvaluationReason.FallthroughReason));

var evalContext2 = new EvaluationSeriesContext(featureKey, context, LdValue.Of("default"), "LdClient.StringVariation");
var evalContext2 =
new EvaluationSeriesContext(featureKey, context, LdValue.Of("default"), "LdClient.StringVariation");
var data2 = hookUnderTest.BeforeEvaluation(evalContext2, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext2, data2,
new EvaluationDetail<LdValue>(LdValue.Of("default"), 0, EvaluationReason.FallthroughReason));
Expand Down Expand Up @@ -207,5 +220,123 @@ public void TracingHookIncludesVariant(bool includeVariant)
Assert.All(items, i => i.Events.All(e => e.Tags.All(kvp => kvp.Key != "feature_flag.variant")));
}
}


[Fact]
public void TracingHookIncludesEnvironmentIdWhenSpecified()
{
ICollection<Activity> exportedItems = new Collection<Activity>();

_ = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
.AddSource("test-source")
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(serviceName: "test-source", serviceVersion: "1.0.0"))
.AddInMemoryExporter(exportedItems)
.Build();

var testSource = new ActivitySource("test-source", "1.0.0");

var hookUnderTest = TracingHook.Builder().EnvironmentId("env-123").Build();
var featureKey = "feature-key";
var context = Context.New("foo");

var rootActivity = testSource.StartActivity("root-activity");

var evalContext1 =
new EvaluationSeriesContext(featureKey, context, LdValue.Of(true), "LdClient.BoolVariation");
var data1 = hookUnderTest.BeforeEvaluation(evalContext1, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext1, data1,
new EvaluationDetail<LdValue>(LdValue.Of(true), 0, EvaluationReason.FallthroughReason));

rootActivity.Stop();

var items = exportedItems.ToList();

Assert.Single(items);
Assert.Equal("root-activity", items[0].OperationName);

var events = items[0].Events;
Assert.Single(events.Where(e =>
e.Tags.Contains(new KeyValuePair<string, object>("feature_flag.set.id", "env-123"))));
}

[Fact]
public void TracingHookUsesEnvironmentIdFromContext()
{
ICollection<Activity> exportedItems = new Collection<Activity>();

_ = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
.AddSource("test-source")
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(serviceName: "test-source", serviceVersion: "1.0.0"))
.AddInMemoryExporter(exportedItems)
.Build();

var testSource = new ActivitySource("test-source", "1.0.0");

var hookUnderTest = TracingHook.Builder().Build();
var featureKey = "feature-key";
var context = Context.New("foo");

var rootActivity = testSource.StartActivity("root-activity");

var evalContext1 = new EvaluationSeriesContext(featureKey, context, LdValue.Of(true),
"LdClient.BoolVariation", "env-456");
var data1 = hookUnderTest.BeforeEvaluation(evalContext1, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext1, data1,
new EvaluationDetail<LdValue>(LdValue.Of(true), 0, EvaluationReason.FallthroughReason));

rootActivity.Stop();

var items = exportedItems.ToList();

Assert.Single(items);
Assert.Equal("root-activity", items[0].OperationName);

var events = items[0].Events;
Assert.Single(events.Where(e =>
e.Tags.Contains(new KeyValuePair<string, object>("feature_flag.set.id", "env-456"))));
}

[Fact]
public void TracingHookPrioritizesEnvironmentIdFromOptions()
{
ICollection<Activity> exportedItems = new Collection<Activity>();

_ = OpenTelemetry.Sdk.CreateTracerProviderBuilder()
.AddSource("test-source")
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(serviceName: "test-source", serviceVersion: "1.0.0"))
.AddInMemoryExporter(exportedItems)
.Build();

var testSource = new ActivitySource("test-source", "1.0.0");

var hookUnderTest = TracingHook.Builder().EnvironmentId("env-123").Build();
var featureKey = "feature-key";
var context = Context.New("foo");

var rootActivity = testSource.StartActivity("root-activity");

var evalContext1 = new EvaluationSeriesContext(featureKey, context, LdValue.Of(true),
"LdClient.BoolVariation", "env-456");
var data1 = hookUnderTest.BeforeEvaluation(evalContext1, new SeriesDataBuilder().Build());
hookUnderTest.AfterEvaluation(evalContext1, data1,
new EvaluationDetail<LdValue>(LdValue.Of(true), 0, EvaluationReason.FallthroughReason));

rootActivity.Stop();

var items = exportedItems.ToList();

Assert.Single(items);
Assert.Equal("root-activity", items[0].OperationName);

var events = items[0].Events;
Assert.Single(events.Where(e =>
e.Tags.Contains(new KeyValuePair<string, object>("feature_flag.set.id", "env-123"))));
}
}
}
Loading