Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
543c9b1
feat(feature-flags): add agentless configuration keys to supported-co…
pavlokhrebto Aug 13, 2026
4753079
feat(feature-flags): add FeatureFlagsSettings with source resolution …
pavlokhrebto Aug 13, 2026
25e168f
feat(feature-flags): add AgentlessEndpoint for CDN URL derivation
pavlokhrebto Aug 13, 2026
9c3a236
test(feature-flags): add unit tests for FeatureFlagsSettings and Agen…
pavlokhrebto Aug 13, 2026
95bf50b
fix(tests): correct AgentlessEndpointTests invalid URL expectations
pavlokhrebto Aug 13, 2026
4e6232b
fix(feature-flags): redact agentless base URL telemetry and reject ma…
pavlokhrebto Aug 14, 2026
7ee3cf3
fix: remove unnecessary null-forgiving operators in AgentlessEndpoint
pavlokhrebto Aug 17, 2026
02a6ac6
fix: drop redundant HasWhitespace check, defer to Uri.TryCreate per r…
pavlokhrebto Aug 17, 2026
7f72e7f
fix: reword credential comment and remove unnecessary null-forgiving …
pavlokhrebto Aug 17, 2026
fb32e7d
refactor(feature-flags): use config framework for source resolution a…
pavlokhrebto Aug 17, 2026
2911d78
fix: mark DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL as…
pavlokhrebto Aug 17, 2026
7af7bc5
refactor: pass site/env/apiKey via constructor instead of re-reading …
pavlokhrebto Aug 17, 2026
8f406d9
[FeatureFlags] Resolve the configuration source in a single telemetry…
pavlokhrebto Aug 18, 2026
ada921d
[FeatureFlags] Make AgentlessEndpoint a class so a failed TryCreate y…
pavlokhrebto Aug 18, 2026
c224e2d
feat(feature-flags): add UfcConfigurationParser for JSON:API envelope…
pavlokhrebto Aug 13, 2026
6a5f49a
test(feature-flags): add UfcConfigurationParserTests for JSON:API env…
pavlokhrebto Aug 13, 2026
388ccfa
test(feature-flags): add AgentlessConfigurationSourceTests for poller…
pavlokhrebto Aug 13, 2026
376b0ad
fix(feature-flags): reject non-string data.type, log failures as errors
pavlokhrebto Aug 14, 2026
2f2682f
fix(feature-flags): add explicit request timeout race for net461 asyn…
pavlokhrebto Aug 14, 2026
7a29735
feat(feature-flags): wire source-agnostic activation and InitializeAs…
pavlokhrebto Aug 13, 2026
aeea2cb
feat(feature-flags): add OpenFeature InitializeAsync CallTarget instr…
pavlokhrebto Aug 13, 2026
5647ccb
test(feature-flags): add FeatureFlagsModuleTests for activation, Init…
pavlokhrebto Aug 13, 2026
a31e084
fix(feature-flags): add missing using directives for Task and Cancell…
pavlokhrebto Aug 13, 2026
a31ba4c
fix(feature-flags): commit regenerated native calltarget definitions …
pavlokhrebto Aug 13, 2026
0a0e68f
fix(feature-flags): make ExposureApi construction lazy to avoid eager…
pavlokhrebto Aug 14, 2026
58ae7f4
fix(feature-flags): remove unused locals, defer RC subscription, and …
pavlokhrebto Aug 14, 2026
4b925d7
[FeatureFlags] Drop the duplicate FeatureFlags settings left by the r…
pavlokhrebto Aug 18, 2026
261d402
[FeatureFlags] Guard activation and disposal with a single lock
pavlokhrebto Aug 18, 2026
2f725b0
[FeatureFlags] Report an error status when no delivery source could s…
pavlokhrebto Aug 19, 2026
126d201
[FeatureFlags] Emit typed provider events instead of an implicit Prov…
pavlokhrebto Aug 19, 2026
8c4f3b3
Merge branch 'master' of github.com:DataDog/dd-trace-dotnet into pavl…
pavlokhrebto Aug 20, 2026
154ef58
[FeatureFlags] Apply dd_env per request instead of baking it into the…
pavlokhrebto Aug 20, 2026
dbae9dc
[FeatureFlags] Normalize dd_env the same way span tags are normalized
pavlokhrebto Aug 20, 2026
7c7d7e6
[FeatureFlags] Report an unrecognised configuration source as a parsi…
pavlokhrebto Aug 20, 2026
f4a87d5
Merge branch 'pavlo.khrebto/EX-2703/ffe-config-and-endpoint' of githu…
pavlokhrebto Aug 20, 2026
8935780
[FeatureFlags] Poll for the current environment instead of the one ca…
pavlokhrebto Aug 20, 2026
d174938
Merge branch 'pavlo.khrebto/EX-2703/ffe-agentless-poller' of github.c…
pavlokhrebto Aug 20, 2026
d6aafaf
Merge branch 'master' of github.com:DataDog/dd-trace-dotnet into pavl…
pavlokhrebto Aug 25, 2026
437b9dd
Merge branch 'pavlo.khrebto/EX-2703/ffe-config-and-endpoint' of githu…
pavlokhrebto Aug 25, 2026
424de1a
Merge branch 'pavlo.khrebto/EX-2703/ffe-agentless-poller' of github.c…
pavlokhrebto Aug 25, 2026
2317ea8
[FeatureFlags] Fix double question mark in agentless request URI on .…
pavlokhrebto Aug 25, 2026
bfab9d8
Merge branch 'pavlo.khrebto/EX-2703/ffe-config-and-endpoint' of githu…
pavlokhrebto Aug 25, 2026
d644ab5
Merge branch 'pavlo.khrebto/EX-2703/ffe-agentless-poller' of github.c…
pavlokhrebto Aug 25, 2026
574fa0d
[FeatureFlags] Subscribe to FFE_FLAGS at startup for the remote_confi…
pavlokhrebto Aug 26, 2026
d868f5a
[FeatureFlags] Align the provider initialization timeout default with…
pavlokhrebto Aug 26, 2026
b2f012a
Fix comments
pavlokhrebto Aug 26, 2026
fc6185d
Merge branch 'pavlo.khrebto/EX-2703/ffe-config-and-endpoint' of githu…
pavlokhrebto Aug 26, 2026
5654f49
[FeatureFlags] Signal agentless poller shutdown with a TaskCompletion…
pavlokhrebto Aug 26, 2026
0eadda9
[FeatureFlags] Use ThreadSafeRandom.Shared for agentless retry jitter
pavlokhrebto Aug 26, 2026
e2a6f9a
[FeatureFlags] Log agentless delivery failures as warnings
pavlokhrebto Aug 26, 2026
4f892ab
[FeatureFlags] Fix stream disposal when reading the agentless respons…
pavlokhrebto Aug 26, 2026
a522bfb
[FeatureFlags] Make the agentless apply step synchronous and pass Pol…
pavlokhrebto Aug 26, 2026
ffcfc15
[FeatureFlags] Start the agentless poll loop with Task.Run
pavlokhrebto Aug 26, 2026
da215db
[FeatureFlags] Rebuild the agentless request URI only when the enviro…
pavlokhrebto Aug 26, 2026
0ad52ca
[FeatureFlags] Parse the agentless payload straight from the response…
pavlokhrebto Aug 26, 2026
e9936a2
[FeatureFlags] Deserialize the UFC envelope without building a JSON tree
pavlokhrebto Aug 26, 2026
0bcb494
[FeatureFlags] Subscribe to settings changes in the agentless constru…
pavlokhrebto Aug 26, 2026
97fdde3
[FeatureFlags] Add dd_env to the managed agentless endpoint only
pavlokhrebto Aug 26, 2026
eba74ae
[FeatureFlags] Name the offline source after the value that selects it
pavlokhrebto Aug 26, 2026
1c063c3
[FeatureFlags] Address review comments on source resolution and endpo…
pavlokhrebto Aug 26, 2026
85eacdb
FeatureFlags] Document the agentless base URL contract
pavlokhrebto Aug 26, 2026
4149d7b
[FeatureFlags] Fail closed on an unrecognised configuration source
pavlokhrebto Aug 26, 2026
3bfd025
[FeatureFlags] Reject a Datadog site that can redirect the managed en…
pavlokhrebto Aug 26, 2026
d8e1083
[FeatureFlags] Reject a Datadog site that can redirect the managed en…
pavlokhrebto Aug 26, 2026
aa8fc5b
[FeatureFlags] Separate whether Feature Flags run from where configur…
pavlokhrebto Aug 27, 2026
b3f5318
Merge branch 'master' of github.com:DataDog/dd-trace-dotnet into pavl…
pavlokhrebto Aug 27, 2026
24eea14
Merge branch 'pavlo.khrebto/EX-2703/ffe-config-and-endpoint' of githu…
pavlokhrebto Aug 27, 2026
c5cb6c0
Merge branch 'pavlo.khrebto/EX-2703/ffe-agentless-poller' of github.c…
pavlokhrebto Aug 27, 2026
42aaffc
feat(feature-flags): add UfcConfigurationParser for JSON:API envelope…
pavlokhrebto Aug 13, 2026
5c81862
test(feature-flags): add UfcConfigurationParserTests for JSON:API env…
pavlokhrebto Aug 13, 2026
c7a0d5f
test(feature-flags): add AgentlessConfigurationSourceTests for poller…
pavlokhrebto Aug 13, 2026
e084a93
fix(feature-flags): reject non-string data.type, log failures as errors
pavlokhrebto Aug 14, 2026
1f8f03d
fix(feature-flags): add explicit request timeout race for net461 asyn…
pavlokhrebto Aug 14, 2026
d3b307e
[FeatureFlags] Poll for the current environment instead of the one ca…
pavlokhrebto Aug 20, 2026
eae179d
[FeatureFlags] Signal agentless poller shutdown with a TaskCompletion…
pavlokhrebto Aug 26, 2026
f21deeb
[FeatureFlags] Use ThreadSafeRandom.Shared for agentless retry jitter
pavlokhrebto Aug 26, 2026
8aeefb2
[FeatureFlags] Log agentless delivery failures as warnings
pavlokhrebto Aug 26, 2026
3640e9d
[FeatureFlags] Fix stream disposal when reading the agentless respons…
pavlokhrebto Aug 26, 2026
7f9b72b
[FeatureFlags] Make the agentless apply step synchronous and pass Pol…
pavlokhrebto Aug 26, 2026
c01d3f2
[FeatureFlags] Start the agentless poll loop with Task.Run
pavlokhrebto Aug 26, 2026
0610bea
[FeatureFlags] Rebuild the agentless request URI only when the enviro…
pavlokhrebto Aug 26, 2026
c3a452b
[FeatureFlags] Parse the agentless payload straight from the response…
pavlokhrebto Aug 26, 2026
1f2d697
[FeatureFlags] Deserialize the UFC envelope without building a JSON tree
pavlokhrebto Aug 26, 2026
1717780
[FeatureFlags] Subscribe to settings changes in the agentless constru…
pavlokhrebto Aug 26, 2026
2edc435
[FeatureFlags] Let the HTTP abstraction own the agentless request tim…
pavlokhrebto Aug 27, 2026
2701dd3
Merge branch 'master' of github.com:DataDog/dd-trace-dotnet into pavl…
pavlokhrebto Aug 27, 2026
d73689f
Merge branch 'pavlo.khrebto/EX-2703/ffe-agentless-poller' of github.c…
pavlokhrebto Aug 27, 2026
e9e9cc4
Merge branch 'pavlo.khrebto/EX-2703/ffe-agentless-poller' of github.c…
pavlokhrebto Aug 27, 2026
3b2844c
Merge branch 'master' of github.com:DataDog/dd-trace-dotnet into pavl…
pavlokhrebto Aug 27, 2026
d72f107
[FeatureFlags] Signal initialization cancellation with a TaskCompleti…
pavlokhrebto Aug 28, 2026
57ff5cc
Merge branch 'master' of github.com:DataDog/dd-trace-dotnet into pavl…
pavlokhrebto Aug 28, 2026
6518981
[FeatureFlags] Trigger system-tests base image build
pavlokhrebto Aug 28, 2026
4bbcfc4
Merge branch 'master' of github.com:DataDog/dd-trace-dotnet into pavl…
pavlokhrebto Sep 7, 2026
f742338
Merge master into pavlo.khrebto/EX-2703/ffe-module-wiring
pavlokhrebto Sep 8, 2026
f853cdd
Merge master into pavlo.khrebto/EX-2703/ffe-module-wiring
pavlokhrebto Sep 8, 2026
9d9c452
[FeatureFlags] Skip the state lock once activation has happened
pavlokhrebto Sep 10, 2026
cefe935
[FeatureFlags] Document that initialization blocks the caller until t…
pavlokhrebto Sep 10, 2026
73cce16
[FeatureFlags] Keep a throwing config handler from holding back the a…
pavlokhrebto Sep 10, 2026
44a629f
[FeatureFlags] Keep a throwing config handler from holding back the a…
pavlokhrebto Sep 10, 2026
77cfab0
[FeatureFlags] Emit the provider event before the application's confi…
pavlokhrebto Sep 10, 2026
cd03d2a
[FeatureFlags] Test the module's agentless activation and disposal
pavlokhrebto Sep 10, 2026
05490c3
[FeatureFlags] Address review comments on naming, comments and test-o…
pavlokhrebto Sep 10, 2026
d264e64
[FeatureFlags] Report withdrawn configuration as a provider error
pavlokhrebto Sep 10, 2026
94a9706
[FeatureFlags] Correct the initialization-timeout documentation
pavlokhrebto Sep 10, 2026
0a92d2a
[FeatureFlags] Emit provider status events from initialization
pavlokhrebto Sep 10, 2026
1ba423f
[FeatureFlags] Delegate the first provider ready event to the OpenFea…
pavlokhrebto Sep 11, 2026
41092bc
Merge branch 'master' of github.com:DataDog/dd-trace-dotnet into pavl…
pavlokhrebto Sep 14, 2026
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
46 changes: 46 additions & 0 deletions tracer/build/supported_calltargets.g.json
Original file line number Diff line number Diff line change
Expand Up @@ -14164,6 +14164,52 @@
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenFeature",
"AssemblyName": "Datadog.FeatureFlags.OpenFeature",
"TargetTypeName": "Datadog.FeatureFlags.OpenFeature.FeatureFlagsSdk",
"TargetMethodName": "HasConfiguration",
"TargetReturnType": "System.Boolean",
"TargetParameterTypes": [],
"MinimumVersion": {
"Item1": 2,
"Item2": 0,
"Item3": 0
},
"MaximumVersion": {
"Item1": 2,
"Item2": 65535,
"Item3": 65535
},
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.ManualInstrumentation.OpenFeature.OpenFeatureSdkHasConfigurationIntegration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenFeature",
"AssemblyName": "Datadog.FeatureFlags.OpenFeature",
"TargetTypeName": "Datadog.FeatureFlags.OpenFeature.FeatureFlagsSdk",
"TargetMethodName": "InitializeAsync",
"TargetReturnType": "System.Threading.Tasks.Task",
"TargetParameterTypes": [
"System.Threading.CancellationToken"
],
"MinimumVersion": {
"Item1": 2,
"Item2": 0,
"Item3": 0
},
"MaximumVersion": {
"Item1": 2,
"Item2": 65535,
"Item3": 65535
},
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.ManualInstrumentation.OpenFeature.OpenFeatureSdkInitializeAsyncIntegration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenFeature",
"AssemblyName": "Datadog.FeatureFlags.OpenFeature",
Expand Down
140 changes: 133 additions & 7 deletions tracer/src/Datadog.FeatureFlags.OpenFeature/DatadogProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Threading.Tasks;
using OpenFeature;
using OpenFeature.Constant;
using OpenFeature.Error;
using OpenFeature.Model;

namespace Datadog.FeatureFlags.OpenFeature;
Expand All @@ -23,6 +24,13 @@ namespace Datadog.FeatureFlags.OpenFeature;
/// </summary>
public sealed class DatadogProvider : global::OpenFeature.FeatureProvider, IDisposable
{
// The status this provider last reported, which decides the transitions it owns. The first ready
// event is not one of them: OpenFeature synthesizes one as soon as InitializeAsync returns, so
// emitting it here as well would run every application handler twice.
private const int StatusInitializing = 0;
private const int StatusReady = 1;
private const int StatusError = 2;

private static Action? _onNewConfig = null;
private readonly Metadata _metadata = new Metadata("datadog-openfeature-provider");
#if NET6_0_OR_GREATER
Expand All @@ -33,6 +41,8 @@ public sealed class DatadogProvider : global::OpenFeature.FeatureProvider, IDisp
// nothing is allocated/registered when the feature is disabled.
private readonly SpanEnrichmentHook? _spanEnrichmentHook;

private int _status = StatusInitializing;

/// <summary> Initializes a new instance of the <see cref="DatadogProvider"/> class. </summary>
public DatadogProvider()
{
Expand All @@ -58,19 +68,135 @@ public static void RegisterOnNewConfigEventHandler(Action onNewConfig)

private void SignalGeneralUpdate()
{
// The status event is written before anything the application supplied runs: a handler that
// throws must not be able to suppress it.
try
{
_onNewConfig?.Invoke();

// You don't have to provide specific flag keys
var payload = new ProviderEventPayload
if (!FeatureFlagsSdk.HasConfiguration())
{
Message = "A backend update occurred, but specific changes are unknown."
};
SignalConfigurationUnavailable();
}
else if (Interlocked.CompareExchange(ref _status, StatusReady, StatusError) == StatusError)
{
// Configuration is back after a withdrawal, and nothing else promotes the provider:
// OpenFeature keeps the error status until a ready event replaces it.
SignalReady();
}
else if (Volatile.Read(ref _status) == StatusReady)
{
// Specific flag keys are unknown, so this payload only reports that something changed.
// An event already queued says exactly the same thing, which is why a full channel is
// left alone: the notification is on its way regardless.
EventChannel.Writer.TryWrite(CreatePayload(ProviderEventTypes.ProviderConfigurationChanged, "A backend update occurred, but specific changes are unknown."));
}

EventChannel.Writer.TryWrite((object)payload);
// Otherwise initialization has not returned yet, and the ready event it produces already
// accounts for this configuration.
}
catch { }

try
{
_onNewConfig?.Invoke();
}
catch { }
}

private void SignalConfigurationUnavailable()
{
// Configuration was withdrawn, so every evaluation now returns its default value. Leaving the
// status at READY would report a provider that resolves nothing, so an error is emitted; the
// next configuration promotes the provider back.
if (Interlocked.CompareExchange(ref _status, StatusError, StatusReady) != StatusReady)
{
// Not reported as ready, so there is no status to correct.
return;
}

var payload = CreatePayload(ProviderEventTypes.ProviderError, "Feature flag configuration is unavailable.");
payload.ErrorType = ErrorType.ProviderNotReady;

if (!EventChannel.Writer.TryWrite(payload))
{
// A status transition cannot be dropped, for the same reason the ready event cannot.
_ = WriteWhenRoomAvailableAsync(payload);
}
}

private void SignalReady()
Comment thread
pavlokhrebto marked this conversation as resolved.
{
var payload = CreatePayload(ProviderEventTypes.ProviderReady, "Feature flag configuration was received.");

if (EventChannel.Writer.TryWrite(payload))
{
return;
}

// Unlike a change notification, this one cannot be dropped: losing it would leave the provider
// reported as errored even though it can now resolve flags. The channel holds a single item, so
// waiting for room happens in the background rather than on the thread applying the
// configuration.
_ = WriteWhenRoomAvailableAsync(payload);
}

private async Task WriteWhenRoomAvailableAsync(ProviderEventPayload payload)
{
try
{
await EventChannel.Writer.WriteAsync(payload).ConfigureAwait(false);
}
catch
{
// The channel is completed when the provider is replaced or the SDK shuts down, at which
// point this provider's status no longer matters.
}
}

private ProviderEventPayload CreatePayload(ProviderEventTypes type, string message)
=> new ProviderEventPayload
{
Type = type,
Message = message,
ProviderName = _metadata.Name,
};

/// <summary>
/// Starts flag configuration delivery and waits for the first configuration, so that a ready
/// provider can actually resolve flags.
/// <para>
/// It returns normally when the wait times out, because slow delivery is transient and the
/// configuration still arrives afterwards. It throws <see cref="ProviderFatalException"/> when no
/// source could start delivery at all, which leaves the provider unable to resolve anything for
/// the rest of the process: that is irrecoverable rather than merely not-ready. The exception does
/// not reach the application, because OpenFeature handles it while setting the provider.
/// </para>
/// <para>
/// No status event is emitted here. OpenFeature derives the provider's initial status from how
/// this method returns, so the provider only reports the transitions that happen afterwards.
/// </para>
/// </summary>
/// <param name="context"> Evaluation context </param>
/// <param name="cancellationToken"> Async cancellation token </param>
/// <returns> A task that completes when initialization is complete </returns>
public override async Task InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default)
{
try
{
await FeatureFlagsSdk.InitializeAsync(cancellationToken).ConfigureAwait(false);
}
catch (Exception exception)
{
Volatile.Write(ref _status, StatusError);

// Irrecoverable: no source can be started later in this process, so no configuration will
// ever arrive and every evaluation returns its default value. OpenFeature reports the error
// status from this exception.
throw new ProviderFatalException(exception.Message, exception);
}

// Recorded rather than signalled, because OpenFeature marks the provider ready as soon as this
// returns. It is what lets a later withdrawal be reported as a transition out of READY.
Volatile.Write(ref _status, StatusReady);
Comment thread
pavlokhrebto marked this conversation as resolved.
}

/// <summary> Gets provider metadata </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Datadog.Trace.FeatureFlags;
using OpenFeature.Constant;
using OpenFeature.Model;
Expand All @@ -32,6 +34,24 @@ internal static class FeatureFlagsSdk
[MethodImpl(MethodImplOptions.NoInlining)]
internal static bool IsSpanEnrichmentEnabled() => false;

/// <summary>
/// Gets a value indicating whether flag configuration is currently held, so the provider can
/// resolve flags. Goes back to <c>false</c> when configuration is withdrawn.
/// </summary>
/// <returns> True while configuration is held </returns>
[MethodImpl(MethodImplOptions.NoInlining)]
internal static bool HasConfiguration() => false;

/// <summary>
/// Activates flag configuration delivery and waits for the first configuration to arrive.
/// Delivery only starts here, because requesting configuration is billable and installing the
/// tracer alone must not do it.
/// </summary>
/// <param name="cancellationToken"> Cancellation token. OpenFeature 2.3.0 does not forward one through SetProviderAsync, so only a direct caller supplies it </param>
/// <returns> A task that completes once configuration has arrived or the initialization timeout has elapsed, and that faults when no source could start delivery at all </returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask;

/// <summary> Installs an event handler to be fired when a new config has been received </summary>
/// <param name="onNewConfig"> Action to be called when the event is fired </param>
[MethodImpl(MethodImplOptions.NoInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ internal static CallTargetState OnMethodBegin<TTarget>(ref string flagKey, Featu
return new CallTargetReturn<TReturn?>(returnValue);
}

// Manual API users may not call InitializeAsync before Evaluate. Activate delivery as a
// fallback so the agentless poller starts on first evaluation, not never.
flags.Activate();

var parameters = (State)state.State!;
var res = flags.Evaluate(parameters.FlagKey, parameters.TargetType, parameters.DefaultValue, parameters.TargetingKey ?? string.Empty, parameters.Attributes);
var traceContext = tracer.InternalActiveScope?.Span?.Context.TraceContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ internal static CallTargetState OnMethodBegin<TTarget>(ref string flagKey, Featu
}

var parameters = (State)state.State!;
var res = TracerManager.Instance.FeatureFlags?.Evaluate(parameters.FlagKey, parameters.TargetType, parameters.DefaultValue, parameters.TargetingKey ?? string.Empty, parameters.Attributes);
var flags = TracerManager.Instance.FeatureFlags;

// Fallback activation: if the user calls Evaluate without InitializeAsync (for example,
// via the manual API or an older OpenFeature package), start delivery on first evaluation.
flags?.Activate();

var res = flags?.Evaluate(parameters.FlagKey, parameters.TargetType, parameters.DefaultValue, parameters.TargetingKey ?? string.Empty, parameters.Attributes);
return new CallTargetReturn<TReturn?>(res.DuckCast<TReturn>());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// <copyright file="OpenFeatureSdkHasConfigurationIntegration.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
#nullable enable

using System;
using System.ComponentModel;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.ManualInstrumentation.OpenFeature;

/// <summary>
/// System.Boolean Datadog.FeatureFlags.OpenFeature.FeatureFlagsSdk::HasConfiguration() calltarget instrumentation
/// </summary>
[InstrumentMethod(
AssemblyName = "Datadog.FeatureFlags.OpenFeature",
TypeName = "Datadog.FeatureFlags.OpenFeature.FeatureFlagsSdk",
MethodName = "HasConfiguration",
ReturnTypeName = ClrNames.Bool,
ParameterTypeNames = [],
MinimumVersion = "2.0.0",
MaximumVersion = "2.*.*",
IntegrationName = nameof(IntegrationId.OpenFeature))]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenFeatureSdkHasConfigurationIntegration
{
internal static CallTargetReturn<bool> OnMethodEnd<TTarget>(bool returnValue, Exception? exception, in CallTargetState state)
{
if (exception is not null)
{
return new CallTargetReturn<bool>(returnValue);
}

return new CallTargetReturn<bool>(TracerManager.Instance.FeatureFlags?.HasConfiguration == true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// <copyright file="OpenFeatureSdkInitializeAsyncIntegration.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
#nullable enable

using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.ManualInstrumentation.OpenFeature;

/// <summary>
/// System.Threading.Tasks.Task Datadog.FeatureFlags.OpenFeature.FeatureFlagsSdk::InitializeAsync(System.Threading.CancellationToken) calltarget instrumentation
/// </summary>
[InstrumentMethod(
AssemblyName = "Datadog.FeatureFlags.OpenFeature",
TypeName = "Datadog.FeatureFlags.OpenFeature.FeatureFlagsSdk",
MethodName = "InitializeAsync",
ReturnTypeName = ClrNames.Task,
ParameterTypeNames = [ClrNames.CancellationToken],
MinimumVersion = "2.0.0",
MaximumVersion = "2.*.*",
IntegrationName = nameof(IntegrationId.OpenFeature))]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenFeatureSdkInitializeAsyncIntegration
{
internal static CallTargetState OnMethodBegin<TTarget>(ref CancellationToken cancellationToken)
=> new CallTargetState(scope: null, state: cancellationToken);

internal static CallTargetReturn<Task> OnMethodEnd<TTarget>(Task returnValue, Exception? exception, in CallTargetState state)
{
if (exception is not null)
{
return new CallTargetReturn<Task>(returnValue);
}

// Agentless polling only starts here, because those requests go straight to Datadog and are
// billable, so the tracer must not start them until application code adopts the provider. The
// Remote Configuration source is already subscribed by then; this call waits for its first
// configuration.
if (TracerManager.Instance.FeatureFlags is { } featureFlags)
{
var cancellationToken = state.State is CancellationToken token ? token : default;
return new CallTargetReturn<Task>(featureFlags.InitializeAsync(cancellationToken));
}

return new CallTargetReturn<Task>(returnValue);
}
}
Loading
Loading