diff --git a/Datadog.Trace.Samples.g.sln b/Datadog.Trace.Samples.g.sln
index 4efa4b7a2d2a..117de35c9664 100644
--- a/Datadog.Trace.Samples.g.sln
+++ b/Datadog.Trace.Samples.g.sln
@@ -447,6 +447,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.Quartz", "tracer\te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.HostLogsDisabled", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.HostLogsDisabled\Samples.AzureFunctions.V4Isolated.HostLogsDisabled.csproj", "{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.FeatureFlags", "tracer\test\test-applications\integrations\Samples.FeatureFlags\Samples.FeatureFlags.csproj", "{D5A8ABB9-CB23-974E-F338-5D9172D96CD8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.OpenFeature", "tracer\test\test-applications\integrations\Samples.OpenFeature\Samples.OpenFeature.csproj", "{021D714E-1764-D76B-15B5-C9114FE934C2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -673,6 +677,10 @@ Global
{95613224-C1D7-4D4A-8926-F70DA26371CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95613224-C1D7-4D4A-8926-F70DA26371CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95613224-C1D7-4D4A-8926-F70DA26371CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1234567-C1D7-4D4A-8926-F70DA26371CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1234567-C1D7-4D4A-8926-F70DA26371CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1234567-C1D7-4D4A-8926-F70DA26371CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1234567-C1D7-4D4A-8926-F70DA26371CA}.Release|Any CPU.Build.0 = Release|Any CPU
{BF1E5BA6-C0E5-4472-9D5D-2622231DD275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF1E5BA6-C0E5-4472-9D5D-2622231DD275}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF1E5BA6-C0E5-4472-9D5D-2622231DD275}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -957,6 +965,10 @@ Global
{56DE0D44-E9E5-48DA-BAEA-2934B1E28D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56DE0D44-E9E5-48DA-BAEA-2934B1E28D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56DE0D44-E9E5-48DA-BAEA-2934B1E28D4E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BC44A41F-1BED-4438-9F66-0EA5607906D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BC44A41F-1BED-4438-9F66-0EA5607906D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BC44A41F-1BED-4438-9F66-0EA5607906D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BC44A41F-1BED-4438-9F66-0EA5607906D7}.Release|Any CPU.Build.0 = Release|Any CPU
{D59C5649-BE0E-4A33-B868-B652D8614534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D59C5649-BE0E-4A33-B868-B652D8614534}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D59C5649-BE0E-4A33-B868-B652D8614534}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -1073,6 +1085,14 @@ Global
{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {021D714E-1764-D76B-15B5-C9114FE934C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {021D714E-1764-D76B-15B5-C9114FE934C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {021D714E-1764-D76B-15B5-C9114FE934C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {021D714E-1764-D76B-15B5-C9114FE934C2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9518425A-36A5-4B8F-B0B8-6137DB88441D} = {8CEC2042-F11C-49F5-A674-2355793B600A}
@@ -1252,5 +1272,7 @@ Global
{59A9EDCD-6892-4817-8957-54DE84BDCAFB} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{CF69BC17-1527-425A-9B02-8E223BC31DB8} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
+ {021D714E-1764-D76B-15B5-C9114FE934C2} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
EndGlobalSection
EndGlobal
diff --git a/Datadog.Trace.sln b/Datadog.Trace.sln
index 0a6909c96d58..d1fd19cbe9bd 100644
--- a/Datadog.Trace.sln
+++ b/Datadog.Trace.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.1.31903.286
+# Visual Studio Version 18
+VisualStudioVersion = 18.0.11205.157
MinimumVisualStudioVersion = 15.0.26124.0
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Datadog.Tracer.Native", "tracer\src\Datadog.Tracer.Native\Datadog.Tracer.Native.vcxproj", "{91B6272F-5780-4C94-8071-DBBA7B4F67F3}"
ProjectSection(ProjectDependencies) = postProject
@@ -625,6 +625,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.Quartz", "tracer\te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.HostLogsDisabled", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.HostLogsDisabled\Samples.AzureFunctions.V4Isolated.HostLogsDisabled.csproj", "{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.FeatureFlags", "tracer\test\test-applications\integrations\Samples.FeatureFlags\Samples.FeatureFlags.csproj", "{D5A8ABB9-CB23-974E-F338-5D9172D96CD8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.OpenFeature", "tracer\test\test-applications\integrations\Samples.OpenFeature\Samples.OpenFeature.csproj", "{021D714E-1764-D76B-15B5-C9114FE934C2}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Datadog.Trace.Tools.Analyzers.CodeFixes", "tracer\src\Datadog.Trace.Tools.Analyzers.CodeFixes\Datadog.Trace.Tools.Analyzers.CodeFixes.csproj", "{32521F0A-D52D-4DB1-86C4-3D72DEDA6E55}"
EndProject
Global
@@ -945,6 +949,10 @@ Global
{95613224-C1D7-4D4A-8926-F70DA26371CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95613224-C1D7-4D4A-8926-F70DA26371CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95613224-C1D7-4D4A-8926-F70DA26371CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1234567-C1D7-4D4A-8926-F70DA26371CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1234567-C1D7-4D4A-8926-F70DA26371CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1234567-C1D7-4D4A-8926-F70DA26371CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1234567-C1D7-4D4A-8926-F70DA26371CA}.Release|Any CPU.Build.0 = Release|Any CPU
{78004AA7-26DD-44DB-A2C7-C287A5BBE5D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78004AA7-26DD-44DB-A2C7-C287A5BBE5D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF1E5BA6-C0E5-4472-9D5D-2622231DD275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -1343,6 +1351,10 @@ Global
{56DE0D44-E9E5-48DA-BAEA-2934B1E28D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56DE0D44-E9E5-48DA-BAEA-2934B1E28D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56DE0D44-E9E5-48DA-BAEA-2934B1E28D4E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BC44A41F-1BED-4438-9F66-0EA5607906D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BC44A41F-1BED-4438-9F66-0EA5607906D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BC44A41F-1BED-4438-9F66-0EA5607906D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BC44A41F-1BED-4438-9F66-0EA5607906D7}.Release|Any CPU.Build.0 = Release|Any CPU
{B28A33A4-C694-4514-BC30-2680605B0B3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B28A33A4-C694-4514-BC30-2680605B0B3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B28A33A4-C694-4514-BC30-2680605B0B3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -1507,6 +1519,14 @@ Global
{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {021D714E-1764-D76B-15B5-C9114FE934C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {021D714E-1764-D76B-15B5-C9114FE934C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {021D714E-1764-D76B-15B5-C9114FE934C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {021D714E-1764-D76B-15B5-C9114FE934C2}.Release|Any CPU.Build.0 = Release|Any CPU
{32521F0A-D52D-4DB1-86C4-3D72DEDA6E55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32521F0A-D52D-4DB1-86C4-3D72DEDA6E55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32521F0A-D52D-4DB1-86C4-3D72DEDA6E55}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -1757,18 +1777,20 @@ Global
{59A9EDCD-6892-4817-8957-54DE84BDCAFB} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{CF69BC17-1527-425A-9B02-8E223BC31DB8} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{C770F9F8-0430-587D-EB7A-8BEC2FE9B61C} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
+ {D5A8ABB9-CB23-974E-F338-5D9172D96CD8} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
+ {021D714E-1764-D76B-15B5-C9114FE934C2} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{32521F0A-D52D-4DB1-86C4-3D72DEDA6E55} = {9E5F0022-0A50-40BF-AC6A-C3078585ECAB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F}
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{021d714e-1764-d76b-15b5-c9114fe934c2}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{021efba6-c4ba-4de5-bf3f-c263ee9e20db}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{061ab58b-8235-4dae-8d56-5f081dd78f5e}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{086ff8a0-9cee-470a-9751-78b0f1340649}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0980bcdd-a231-42d1-b689-41a41bba161a}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0c0578cb-3b67-4f95-8547-206cd2a560cd}*SharedItemsImports = 5
- tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0d996eee-7c04-4888-af48-9c1e2f261a00}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0df4363a-0df4-4882-a39f-3c9f404b8de5}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0e036453-2c80-4fc9-a517-771f0071734b}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0f0f7d45-0e13-42b0-a158-8f303bbe8358}*SharedItemsImports = 5
@@ -1788,12 +1810,10 @@ Global
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{268b6d05-b6d5-4d20-b2b1-0b9422a92d73}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{2a6d3042-c675-4ea3-a8e7-5bdd3c5758ea}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{2ca0d70c-dfc1-458a-871b-328ab6e87e3a}*SharedItemsImports = 5
- tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{2cc63aeb-0098-4d3b-9606-f07692c03e90}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{2d1ff937-3237-4a1b-9c6c-82fa5e22cad7}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{2f3b6271-b9a3-48a3-9db6-847f3ef41f0a}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{303f8e41-691f-4453-ab7d-88a0036c0465}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{31d192af-5454-4d91-97e1-889723aad309}*SharedItemsImports = 5
- tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{32193a01-04dd-463b-a84a-9a93167958a4}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{3493346b-44f6-4f50-8fb4-51d0090df544}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{34b67004-7249-4ef1-8e12-6e6da37ea6be}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{3538ef5e-377e-430a-afb8-f2db5faede95}*SharedItemsImports = 5
@@ -1856,6 +1876,7 @@ Global
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{959e9599-8d99-43bc-8038-b91f76179c1c}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{99a62ccf-8e7f-4d57-8383-d38c371c8087}*SharedItemsImports = 4
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{9d5935cb-2df2-46cb-a5e1-98be134cafcc}*SharedItemsImports = 5
+ tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{a1234567-c1d7-4d4a-8926-f70da26371ca}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{a82eb6f8-d8d0-4763-b252-08ca3f39d153}*SharedItemsImports = 4
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{a996927a-9222-43f4-8552-810b69ff04df}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{aa88952e-9393-4a4b-85b5-cc7f03629ce1}*SharedItemsImports = 5
@@ -1874,6 +1895,8 @@ Global
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{bbad4449-d414-4a20-bca2-de9c40e4a866}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{bbb60b0f-bf01-4499-936a-4a299a9acfd4}*SharedItemsImports = 4
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{bc44a41f-1bed-4438-9f66-0ea5607906d5}*SharedItemsImports = 5
+ tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{bc44a41f-1bed-4438-9f66-0ea5607906d6}*SharedItemsImports = 5
+ tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{bc44a41f-1bed-4438-9f66-0ea5607906d7}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{bc998acd-353b-4a56-8a56-df6200e141b6}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{bd46efcc-177c-466e-81df-39314b780ada}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{bdee131f-ccf5-49bd-9764-9c4a8864ce4e}*SharedItemsImports = 5
@@ -1891,9 +1914,11 @@ Global
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{c98950b1-dc4b-43da-974f-ef2cf325ec2b}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{ca3d605f-8dd7-4041-b024-70a24036afa1}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{cb56ac5a-d2c1-40de-99d5-dcf9f44c9482}*SharedItemsImports = 5
+ tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{cf69bc17-1527-425a-9b02-8e223bc31db8}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{d00ddbda-66f5-490d-8c1c-16cc5e142170}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{d141bd06-dd95-4caf-85cd-657116e0dad4}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{d59c5649-be0e-4a33-b868-b652d8614534}*SharedItemsImports = 5
+ tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{d5a8abb9-cb23-974e-f338-5d9172d96cd8}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{d6155f26-8245-4b66-8944-79c3df9f9da3}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{d79491f0-ca92-439b-98ce-7af9f57ebeb0}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{da0a44fb-d562-4776-aafb-8266e78aa1a6}*SharedItemsImports = 5
diff --git a/tracer/build/PackageVersionsGeneratorDefinitions.json b/tracer/build/PackageVersionsGeneratorDefinitions.json
index 5a931094d8ee..d1e1b289b06d 100644
--- a/tracer/build/PackageVersionsGeneratorDefinitions.json
+++ b/tracer/build/PackageVersionsGeneratorDefinitions.json
@@ -963,5 +963,16 @@
"4.20.*",
"4.*.*"
]
+ },
+ {
+ "IntegrationName": "OpenFeature",
+ "SampleProjectName": "Samples.OpenFeature",
+ "NugetPackageSearchName": "OpenFeature",
+ "MinVersion": "2.0.0",
+ "MaxVersionExclusive": "3.0.0",
+ "SpecificVersions": [
+ "2.0.*",
+ "2.10.*"
+ ]
}
]
\ No newline at end of file
diff --git a/tracer/build/supported_calltargets.g.json b/tracer/build/supported_calltargets.g.json
index c32f0a38a9e5..763a2ba232e8 100644
--- a/tracer/build/supported_calltargets.g.json
+++ b/tracer/build/supported_calltargets.g.json
@@ -6682,6 +6682,80 @@
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
+ {
+ "IntegrationName": "DatadogTraceManual",
+ "AssemblyName": "Datadog.Trace.Manual",
+ "TargetTypeName": "Datadog.Trace.FeatureFlags.FeatureFlagsSdk",
+ "TargetMethodName": "Evaluate",
+ "TargetReturnType": "Datadog.Trace.FeatureFlags.IEvaluation",
+ "TargetParameterTypes": [
+ "System.String",
+ "Datadog.Trace.FeatureFlags.ValueType",
+ "System.Object",
+ "System.String",
+ "System.Collections.Generic.IDictionary`2[System.String,System.Object]"
+ ],
+ "MinimumVersion": {
+ "Item1": 3,
+ "Item2": 31,
+ "Item3": 0
+ },
+ "MaximumVersion": {
+ "Item1": 3,
+ "Item2": 65535,
+ "Item3": 65535
+ },
+ "InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.Datadog_Trace_Manual.FeatureFlagsSdkEvaluateIntegration",
+ "IntegrationKind": 0,
+ "IsAdoNetIntegration": false,
+ "InstrumentationCategory": 1
+ },
+ {
+ "IntegrationName": "DatadogTraceManual",
+ "AssemblyName": "Datadog.Trace.Manual",
+ "TargetTypeName": "Datadog.Trace.FeatureFlags.FeatureFlagsSdk",
+ "TargetMethodName": "IsAvailable",
+ "TargetReturnType": "System.Boolean",
+ "TargetParameterTypes": [],
+ "MinimumVersion": {
+ "Item1": 3,
+ "Item2": 31,
+ "Item3": 0
+ },
+ "MaximumVersion": {
+ "Item1": 3,
+ "Item2": 65535,
+ "Item3": 65535
+ },
+ "InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.Datadog_Trace_Manual.FeatureFlagsSdkIsAvailableIntegration",
+ "IntegrationKind": 0,
+ "IsAdoNetIntegration": false,
+ "InstrumentationCategory": 1
+ },
+ {
+ "IntegrationName": "DatadogTraceManual",
+ "AssemblyName": "Datadog.Trace.Manual",
+ "TargetTypeName": "Datadog.Trace.FeatureFlags.FeatureFlagsSdk",
+ "TargetMethodName": "RegisterOnNewConfigEventHandler",
+ "TargetReturnType": "System.Void",
+ "TargetParameterTypes": [
+ "System.Action"
+ ],
+ "MinimumVersion": {
+ "Item1": 3,
+ "Item2": 31,
+ "Item3": 0
+ },
+ "MaximumVersion": {
+ "Item1": 3,
+ "Item2": 65535,
+ "Item3": 65535
+ },
+ "InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.Datadog_Trace_Manual.FeatureFlagsSdkRegisterOnNewConfigEventHandlerIntegration",
+ "IntegrationKind": 0,
+ "IsAdoNetIntegration": false,
+ "InstrumentationCategory": 1
+ },
{
"IntegrationName": "DatadogTraceManual",
"AssemblyName": "Datadog.Trace.Manual",
diff --git a/tracer/src/Datadog.Trace.Manual/Datadog.Trace.Manual.csproj b/tracer/src/Datadog.Trace.Manual/Datadog.Trace.Manual.csproj
index df523ed9a80b..3334b2103e94 100644
--- a/tracer/src/Datadog.Trace.Manual/Datadog.Trace.Manual.csproj
+++ b/tracer/src/Datadog.Trace.Manual/Datadog.Trace.Manual.csproj
@@ -24,11 +24,13 @@
-
-
-
+
+
+
+
+
+
diff --git a/tracer/src/Datadog.Trace.Manual/FeatureFlags/EvaluationContext.cs b/tracer/src/Datadog.Trace.Manual/FeatureFlags/EvaluationContext.cs
new file mode 100644
index 000000000000..c2b2ea1d8b64
--- /dev/null
+++ b/tracer/src/Datadog.Trace.Manual/FeatureFlags/EvaluationContext.cs
@@ -0,0 +1,22 @@
+//
+// 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.
+//
+
+using System.ComponentModel;
+
+namespace Datadog.Trace.FeatureFlags;
+
+/// Standard implementation of a EvaluationContext
+/// Targeting Key
+/// Context optional parameters
+[EditorBrowsable(EditorBrowsableState.Never)]
+[Browsable(false)]
+public sealed class EvaluationContext(string? key, IDictionary? values = null)
+{
+ /// Gets the Context Targeting Key
+ public string? TargetingKey { get; } = key;
+
+ /// Gets the Context optional Values
+ public IDictionary? Attributes { get; } = values;
+}
diff --git a/tracer/src/Datadog.Trace.Manual/FeatureFlags/FeatureFlagsSdk.cs b/tracer/src/Datadog.Trace.Manual/FeatureFlags/FeatureFlagsSdk.cs
new file mode 100644
index 000000000000..f6e880dcaa99
--- /dev/null
+++ b/tracer/src/Datadog.Trace.Manual/FeatureFlags/FeatureFlagsSdk.cs
@@ -0,0 +1,56 @@
+//
+// 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.
+//
+
+#nullable enable
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Datadog.Trace.SourceGenerators;
+
+namespace Datadog.Trace.FeatureFlags;
+
+///
+/// Functions to retrieve FeatureFlags from server
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+[Browsable(false)]
+public static class FeatureFlagsSdk
+{
+ /// Gets a value indicating whether FeatureFlags framework is available or not
+ /// True if FeatureFlagsSDK is instrumented
+ [Instrumented]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static bool IsAvailable() => false;
+
+ /// Installs an event handler to be fired when a new config has been received
+ /// Action to be called when the event is fired
+ [Instrumented]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void RegisterOnNewConfigEventHandler(Action onNewConfig)
+ {
+ }
+
+ ///
+ /// Returns the evaluation of the requested flag key
+ ///
+ /// Returns the evaluation result
+ /// The feature flag key to evaluate
+ /// The desired result type
+ /// The default value
+ /// The evaluation context
+ public static IEvaluation? Evaluate(string flagKey, ValueType targetType, object? defaultValue, EvaluationContext? context)
+ => Evaluate(flagKey, targetType, defaultValue, context?.TargetingKey, context?.Attributes);
+
+ [Instrumented]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static IEvaluation? Evaluate(string flagKey, ValueType targetType, object? defaultValue, string? targetingKey, IDictionary? attributes)
+ {
+ if (flagKey is null)
+ {
+ throw new ArgumentNullException(nameof(flagKey));
+ }
+
+ return null;
+ }
+}
diff --git a/tracer/src/Datadog.Trace.Manual/FeatureFlags/IEvaluation.Manual.cs b/tracer/src/Datadog.Trace.Manual/FeatureFlags/IEvaluation.Manual.cs
new file mode 100644
index 000000000000..daf3ed42d3b0
--- /dev/null
+++ b/tracer/src/Datadog.Trace.Manual/FeatureFlags/IEvaluation.Manual.cs
@@ -0,0 +1,15 @@
+//
+// 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.
+//
+
+using Datadog.Trace.DuckTyping;
+
+namespace Datadog.Trace.FeatureFlags;
+
+/// A Evaluate result.
+[DuckType("Datadog.Trace.FeatureFlags.Evaluation", "Datadog.Trace")]
+[DuckAsClass]
+public partial interface IEvaluation
+{
+}
diff --git a/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManager.cs b/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManager.cs
index 3fbab5649d5c..9c1ec01908ad 100644
--- a/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManager.cs
+++ b/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManager.cs
@@ -1,4 +1,4 @@
-//
+//
// 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.
//
@@ -14,6 +14,7 @@
using Datadog.Trace.Configuration;
using Datadog.Trace.DataStreamsMonitoring;
using Datadog.Trace.DogStatsd;
+using Datadog.Trace.FeatureFlags;
using Datadog.Trace.Logging;
using Datadog.Trace.Logging.DirectSubmission;
using Datadog.Trace.Logging.TracerFlare;
@@ -45,7 +46,8 @@ public TestOptimizationTracerManager(
IRemoteConfigurationManager remoteConfigurationManager,
IDynamicConfigurationManager dynamicConfigurationManager,
ITracerFlareManager tracerFlareManager,
- ISpanEventsManager spanEventsManager)
+ ISpanEventsManager spanEventsManager,
+ FeatureFlagsModule featureFlags)
: base(
settings,
agentWriter,
@@ -63,6 +65,7 @@ public TestOptimizationTracerManager(
dynamicConfigurationManager,
tracerFlareManager,
spanEventsManager,
+ featureFlags,
GetProcessors(settings.PartialFlushEnabled, agentWriter is CIVisibilityProtocolWriter))
{
}
@@ -161,7 +164,8 @@ public LockedManager(
IRemoteConfigurationManager remoteConfigurationManager,
IDynamicConfigurationManager dynamicConfigurationManager,
ITracerFlareManager tracerFlareManager,
- ISpanEventsManager spanEventsManager)
+ ISpanEventsManager spanEventsManager,
+ FeatureFlagsModule featureFlags)
: base(
settings,
agentWriter,
@@ -178,7 +182,8 @@ public LockedManager(
remoteConfigurationManager,
dynamicConfigurationManager,
tracerFlareManager,
- spanEventsManager)
+ spanEventsManager,
+ featureFlags)
{
}
}
diff --git a/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManagerFactory.cs b/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManagerFactory.cs
index 795311ea7ece..30c94f363757 100644
--- a/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManagerFactory.cs
+++ b/tracer/src/Datadog.Trace/Ci/TestOptimizationTracerManagerFactory.cs
@@ -1,4 +1,4 @@
-//
+//
// 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.
//
@@ -15,6 +15,7 @@
using Datadog.Trace.Configuration;
using Datadog.Trace.DataStreamsMonitoring;
using Datadog.Trace.DogStatsd;
+using Datadog.Trace.FeatureFlags;
using Datadog.Trace.Logging.DirectSubmission;
using Datadog.Trace.Logging.TracerFlare;
using Datadog.Trace.RemoteConfigurationManagement;
@@ -54,15 +55,16 @@ protected override TracerManager CreateTracerManagerFrom(
IRemoteConfigurationManager remoteConfigurationManager,
IDynamicConfigurationManager dynamicConfigurationManager,
ITracerFlareManager tracerFlareManager,
- ISpanEventsManager spanEventsManager)
+ ISpanEventsManager spanEventsManager,
+ FeatureFlagsModule featureFlags)
{
telemetry.RecordTestOptimizationSettings(_settings);
if (_testOptimizationTracerManagement.UseLockedTracerManager)
{
- return new TestOptimizationTracerManager.LockedManager(settings, agentWriter, scopeManager, statsd, runtimeMetrics, logSubmissionManager, telemetry, discoveryService, dataStreamsManager, gitMetadataTagsProvider, traceSampler, spanSampler, remoteConfigurationManager, dynamicConfigurationManager, tracerFlareManager, spanEventsManager);
+ return new TestOptimizationTracerManager.LockedManager(settings, agentWriter, scopeManager, statsd, runtimeMetrics, logSubmissionManager, telemetry, discoveryService, dataStreamsManager, gitMetadataTagsProvider, traceSampler, spanSampler, remoteConfigurationManager, dynamicConfigurationManager, tracerFlareManager, spanEventsManager, featureFlags);
}
- return new TestOptimizationTracerManager(settings, agentWriter, scopeManager, statsd, runtimeMetrics, logSubmissionManager, telemetry, discoveryService, dataStreamsManager, gitMetadataTagsProvider, traceSampler, spanSampler, remoteConfigurationManager, dynamicConfigurationManager, tracerFlareManager, spanEventsManager);
+ return new TestOptimizationTracerManager(settings, agentWriter, scopeManager, statsd, runtimeMetrics, logSubmissionManager, telemetry, discoveryService, dataStreamsManager, gitMetadataTagsProvider, traceSampler, spanSampler, remoteConfigurationManager, dynamicConfigurationManager, tracerFlareManager, spanEventsManager, featureFlags);
}
protected override TelemetrySettings CreateTelemetrySettings(TracerSettings settings)
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkEvaluateIntegration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkEvaluateIntegration.cs
new file mode 100644
index 000000000000..d00628c581c6
--- /dev/null
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkEvaluateIntegration.cs
@@ -0,0 +1,56 @@
+//
+// 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.
+//
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Threading;
+using Datadog.Trace.ClrProfiler.CallTarget;
+using Datadog.Trace.Configuration;
+using Datadog.Trace.DuckTyping;
+using Datadog.Trace.FeatureFlags;
+
+namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Datadog_Trace_Manual;
+
+///
+/// Datadog.Trace.FeatureFlags.IEvaluation Datadog.Trace.FeatureFlags.FeatureFlagsSdk::Evaluate(System.String,System.Type,System.Object,Datadog.Trace.FeatureFlags.IEvaluationContext) calltarget instrumentation
+///
+[InstrumentMethod(
+ AssemblyName = "Datadog.Trace.Manual",
+ TypeName = "Datadog.Trace.FeatureFlags.FeatureFlagsSdk",
+ MethodName = "Evaluate",
+ ReturnTypeName = "Datadog.Trace.FeatureFlags.IEvaluation",
+ ParameterTypeNames = [ClrNames.String, "Datadog.Trace.FeatureFlags.ValueType", ClrNames.Object, ClrNames.String, "System.Collections.Generic.IDictionary`2[System.String,System.Object]"],
+ MinimumVersion = "3.31.0",
+ MaximumVersion = "3.*.*",
+ IntegrationName = nameof(IntegrationId.DatadogTraceManual))]
+[Browsable(false)]
+[EditorBrowsable(EditorBrowsableState.Never)]
+public sealed class FeatureFlagsSdkEvaluateIntegration
+{
+ internal static CallTargetState OnMethodBegin(ref string flagKey, FeatureFlags.ValueType targetType, ref object? defaultValue, ref string? targetingKey, ref IDictionary? attributes)
+ {
+ return new CallTargetState(null, new State(flagKey, targetType, defaultValue, targetingKey, attributes));
+ }
+
+ internal static CallTargetReturn OnMethodEnd(TReturn? returnValue, Exception? exception, in CallTargetState state)
+ {
+ if (exception is not null)
+ {
+ // invalid call to the API e.g. non-null args were null. Just let it bubble up.
+ return new CallTargetReturn(returnValue);
+ }
+
+ var parameters = (State)state.State!;
+ var res = TracerManager.Instance.FeatureFlags?.Evaluate(parameters.FlagKey, parameters.TargetType, parameters.DefaultValue, parameters.TargetingKey ?? string.Empty, parameters.Attributes);
+ return new CallTargetReturn(res.DuckCast());
+ }
+
+ private sealed record State(string FlagKey, FeatureFlags.ValueType TargetType, object? DefaultValue, string? TargetingKey, IDictionary? Attributes)
+ {
+ }
+}
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkIsAvailableIntegration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkIsAvailableIntegration.cs
new file mode 100644
index 000000000000..540975b96881
--- /dev/null
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkIsAvailableIntegration.cs
@@ -0,0 +1,36 @@
+//
+// 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.
+//
+#nullable enable
+
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Threading;
+using Datadog.Trace.ClrProfiler.CallTarget;
+using Datadog.Trace.Configuration;
+
+namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Datadog_Trace_Manual;
+
+///
+/// System.Boolean Datadog.Trace.FeatureFlags.FeatureFlagsSdk::IsAvailable() calltarget instrumentation
+///
+[InstrumentMethod(
+ AssemblyName = "Datadog.Trace.Manual",
+ TypeName = "Datadog.Trace.FeatureFlags.FeatureFlagsSdk",
+ MethodName = "IsAvailable",
+ ReturnTypeName = ClrNames.Bool,
+ ParameterTypeNames = [],
+ MinimumVersion = "3.31.0",
+ MaximumVersion = "3.*.*",
+ IntegrationName = nameof(IntegrationId.DatadogTraceManual))]
+[Browsable(false)]
+[EditorBrowsable(EditorBrowsableState.Never)]
+public sealed class FeatureFlagsSdkIsAvailableIntegration
+{
+ internal static CallTargetReturn OnMethodEnd(bool returnValue, Exception? exception, in CallTargetState state)
+ {
+ return new CallTargetReturn(true);
+ }
+}
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkRegisterOnNewConfigEventHandlerIntegration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkRegisterOnNewConfigEventHandlerIntegration.cs
new file mode 100644
index 000000000000..d2e70fd95f95
--- /dev/null
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/FeatureFlags/FeatureFlagsSdkRegisterOnNewConfigEventHandlerIntegration.cs
@@ -0,0 +1,38 @@
+//
+// 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.
+//
+#nullable enable
+
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Threading;
+using Datadog.Trace.ClrProfiler.CallTarget;
+using Datadog.Trace.Configuration;
+using Datadog.Trace.FeatureFlags;
+
+namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Datadog_Trace_Manual;
+
+///
+/// System.Void Datadog.Trace.FeatureFlags.FeatureFlagsSdk::RegisterOnNewConfigEventHandler(System.Action) calltarget instrumentation
+///
+[InstrumentMethod(
+ AssemblyName = "Datadog.Trace.Manual",
+ TypeName = "Datadog.Trace.FeatureFlags.FeatureFlagsSdk",
+ MethodName = "RegisterOnNewConfigEventHandler",
+ ReturnTypeName = ClrNames.Void,
+ ParameterTypeNames = ["System.Action"],
+ MinimumVersion = "3.31.0",
+ MaximumVersion = "3.*.*",
+ IntegrationName = nameof(IntegrationId.DatadogTraceManual))]
+[Browsable(false)]
+[EditorBrowsable(EditorBrowsableState.Never)]
+public sealed class FeatureFlagsSdkRegisterOnNewConfigEventHandlerIntegration
+{
+ internal static CallTargetState OnMethodBegin(ref Action? onNewConfig)
+ {
+ TracerManager.Instance.FeatureFlags?.RegisterOnNewConfigEventHandler(onNewConfig);
+ return CallTargetState.GetDefault();
+ }
+}
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs b/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs
index 33ec90c848eb..b564f9d19781 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs
@@ -16,13 +16,9 @@
using Datadog.Trace.Configuration;
using Datadog.Trace.ContinuousProfiler;
using Datadog.Trace.Debugger;
-using Datadog.Trace.Debugger.ExceptionAutoInstrumentation;
using Datadog.Trace.Debugger.Helpers;
using Datadog.Trace.DiagnosticListeners;
-using Datadog.Trace.Iast.Dataflow;
using Datadog.Trace.Logging;
-using Datadog.Trace.Processors;
-using Datadog.Trace.RemoteConfigurationManagement;
using Datadog.Trace.ServiceFabric;
using Datadog.Trace.Telemetry;
using Datadog.Trace.Telemetry.Metrics;
diff --git a/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs b/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs
index 9a1d42c2ddec..452f906fb4ef 100644
--- a/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs
+++ b/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs
@@ -610,6 +610,9 @@ not null when string.Equals(value, "otlp", StringComparison.OrdinalIgnoreCase) =
DisabledAdoNetCommandTypes.UnionWith(userSplit);
}
+ IsFlaggingProviderEnabled = config.WithKeys(ConfigurationKeys.FeatureFlags.FlaggingProviderEnabled)
+ .AsBool(false);
+
if (source is CompositeConfigurationSource compositeSource)
{
foreach (var nestedSource in compositeSource)
@@ -1249,6 +1252,11 @@ not null when string.Equals(value, "otlp", StringComparison.OrdinalIgnoreCase) =
///
internal HashSet DisabledAdoNetCommandTypes { get; }
+ ///
+ /// Gets a value indicating whether remote Feature Flags Provider is enabled
+ ///
+ internal bool IsFlaggingProviderEnabled { get; }
+
///
/// Gets a value indicating whether partial flush is enabled
///
diff --git a/tracer/src/Datadog.Trace/Configuration/configuration_keys_mapping.json b/tracer/src/Datadog.Trace/Configuration/configuration_keys_mapping.json
index 3feeb5979b85..a16abce52211 100644
--- a/tracer/src/Datadog.Trace/Configuration/configuration_keys_mapping.json
+++ b/tracer/src/Datadog.Trace/Configuration/configuration_keys_mapping.json
@@ -832,6 +832,10 @@
"env_var": "DD_TRACE_INJECT_CONTEXT_INTO_STORED_PROCEDURES_ENABLED",
"const_name": "InjectContextIntoStoredProceduresEnabled"
},
+ {
+ "env_var": "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED",
+ "const_name": "FlaggingProviderEnabled"
+ },
{
"env_var": "DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED",
"const_name": "KafkaCreateConsumerScopeEnabled"
diff --git a/tracer/src/Datadog.Trace/Configuration/supported-configurations-docs.yaml b/tracer/src/Datadog.Trace/Configuration/supported-configurations-docs.yaml
index fed47608e486..2b48a7b4d1d9 100644
--- a/tracer/src/Datadog.Trace/Configuration/supported-configurations-docs.yaml
+++ b/tracer/src/Datadog.Trace/Configuration/supported-configurations-docs.yaml
@@ -873,7 +873,7 @@ DD_TRACE_HTTP_SERVER_ERROR_STATUSES: |
DD_TRACE_INJECT_CONTEXT_INTO_STORED_PROCEDURES_ENABLED: |
Configuration key to enable or disable the injection of the Datadog trace context into stored procedures.
- Default value is false (enabled).
+ Default value is false (disabled).
When enabled, Datadog trace context will be injected into individual stored procedure calls when the following requirements are met:
- The database is Microsoft SQL Server and is set to
@@ -882,6 +882,11 @@ DD_TRACE_INJECT_CONTEXT_INTO_STORED_PROCEDURES_ENABLED: |
- The stored procedure call does not have Output, InputOutput, or Return ADO.NET command parameters.
+DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED: |
+ Enables Feature Flags Provider (Experimental).
+ Default value is false (disabled).
+
+
DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED: |
Configuration key to enable or disable the creation of a span context on exiting a successful Kafka
Consumer.Consume() call, and closing the scope on entering Consumer.Consume().
diff --git a/tracer/src/Datadog.Trace/Configuration/supported-configurations.json b/tracer/src/Datadog.Trace/Configuration/supported-configurations.json
index bb98a7992ce4..06a4299a8089 100644
--- a/tracer/src/Datadog.Trace/Configuration/supported-configurations.json
+++ b/tracer/src/Datadog.Trace/Configuration/supported-configurations.json
@@ -1193,6 +1193,12 @@
],
"product": "FeatureFlags"
},
+ "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED": {
+ "version": [
+ "A"
+ ],
+ "product": "FeatureFlags"
+ },
"DD_TRACE_KAFKA_CREATE_CONSUMER_SCOPE_ENABLED": {
"version": [
"A"
@@ -1409,8 +1415,7 @@
],
"product": "OpenTelemetry"
},
- "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT":
- {
+ "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": {
"version": [
"A"
],
@@ -1428,8 +1433,7 @@
],
"product": "OpenTelemetry"
},
- "OTEL_EXPORTER_OTLP_LOGS_HEADERS":
- {
+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS": {
"version": [
"A"
],
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/Evaluation.cs b/tracer/src/Datadog.Trace/FeatureFlags/Evaluation.cs
new file mode 100644
index 000000000000..6ea76f781430
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/Evaluation.cs
@@ -0,0 +1,31 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace Datadog.Trace.FeatureFlags;
+
+internal sealed class Evaluation(string flagKey, object? value, EvaluationReason reason, string? variant = null, string? error = null, IDictionary? metadata = null)
+ : IEvaluation
+{
+ public string FlagKey { get; } = flagKey;
+
+ public object? Value { get; } = value;
+
+ public EvaluationReason Reason { get; } = reason;
+
+ public string? Variant { get; } = variant;
+
+ public string? Error { get; } = error;
+
+ public IDictionary? FlagMetadata { get; } = metadata;
+}
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/EvaluationContext.cs b/tracer/src/Datadog.Trace/FeatureFlags/EvaluationContext.cs
new file mode 100644
index 000000000000..48246570b678
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/EvaluationContext.cs
@@ -0,0 +1,31 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Datadog.Trace.FeatureFlags
+{
+ /// Standard implementation of a EvaluationContext
+ /// Targeting Key
+ /// Context optional attributes
+ internal sealed class EvaluationContext(string key, IDictionary? attributes = null)
+ {
+ /// Gets the Context Targeting Key
+ public string TargetingKey { get; } = key;
+
+ /// Gets the Context optional Values
+ public IDictionary Attributes { get; } = attributes ?? new Dictionary();
+
+ /// Get the Context value if existent
+ /// Value key
+ /// Returns Context Value or null
+ public object? GetAttribute(string key)
+ => Attributes.TryGetValue(key, out var res) ? res : null;
+ }
+}
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/EvaluationReason.cs b/tracer/src/Datadog.Trace/FeatureFlags/EvaluationReason.cs
new file mode 100644
index 000000000000..8553630f699e
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/EvaluationReason.cs
@@ -0,0 +1,40 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System.ComponentModel;
+
+namespace Datadog.Trace.FeatureFlags;
+
+/// Evaluation result reason
+[EditorBrowsable(EditorBrowsableState.Never)]
+[Browsable(false)]
+public enum EvaluationReason
+{
+ /// Default value
+ Default,
+
+ /// Static value
+ Static,
+
+ /// Targeting match
+ TargetingMatch,
+
+ /// Split match
+ Split,
+
+ /// Target disabled
+ Disabled,
+
+ /// Cached result
+ Cached,
+
+ /// Unknown reason
+ Unknown,
+
+ /// Error
+ Error
+}
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/Exposure/ExposureApi.cs b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/ExposureApi.cs
new file mode 100644
index 000000000000..d172355e6d8f
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/ExposureApi.cs
@@ -0,0 +1,172 @@
+//
+// 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.
+//
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Datadog.Trace.Agent;
+using Datadog.Trace.Agent.Transports;
+using Datadog.Trace.Configuration;
+using Datadog.Trace.FeatureFlags.Exposure.Model;
+using Datadog.Trace.HttpOverStreams;
+using Datadog.Trace.Logging;
+using Datadog.Trace.Vendors.Newtonsoft.Json;
+
+namespace Datadog.Trace.FeatureFlags.Exposure;
+
+internal sealed class ExposureApi : IDisposable
+{
+ internal static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(ExposureApi));
+
+ private const int DefaultCapacity = 1 << 16; // 65536 elements
+ public const string ExposurePath = "evp_proxy/v2/api/v2/exposure";
+ private readonly TaskCompletionSource _processExit = new();
+ private readonly TimeSpan _sendInterval = TimeSpan.FromSeconds(10);
+ private readonly Queue _exposures = new Queue();
+
+ private ExposureCache _exposureCache = new ExposureCache(DefaultCapacity);
+ private IApiRequestFactory _apiRequestFactory;
+ private Dictionary _context;
+ private int _started = 0;
+
+ internal ExposureApi(TracerSettings tracerSettings)
+ {
+ UpdateApi(tracerSettings.Manager.InitialExporterSettings);
+ UpdateContext(tracerSettings.Manager.InitialMutableSettings);
+
+ tracerSettings.Manager.SubscribeToChanges(changes =>
+ {
+ if (changes.UpdatedExporter is { } exporter)
+ {
+ UpdateApi(exporter);
+ }
+
+ if (changes.UpdatedMutable is { } mutable)
+ {
+ UpdateContext(mutable);
+ }
+ });
+
+ [MemberNotNull(nameof(_apiRequestFactory))]
+ void UpdateApi(ExporterSettings exporterSettings)
+ {
+ Log.Debug("ExposureApi::UpdateApi-> Applying settings");
+ var apiRequestFactory = AgentTransportStrategy.Get(
+ exporterSettings,
+ productName: "FeatureFlags exposure",
+ tcpTimeout: TimeSpan.FromSeconds(5),
+ AgentHttpHeaderNames.MinimalHeaders,
+ () => new MinimalAgentHeaderHelper(),
+ uri => uri);
+ Interlocked.Exchange(ref _apiRequestFactory!, apiRequestFactory);
+ }
+
+ [MemberNotNull(nameof(_context))]
+ void UpdateContext(MutableSettings settings)
+ {
+ Log.Debug("ExposureApi::UpdateContext -> Applying settings");
+ var context = new Dictionary
+ {
+ { "service", settings.DefaultServiceName },
+ { "env", settings.Environment ?? "unknown" },
+ { "version", settings.ServiceVersion ?? "unknown" }
+ };
+ Interlocked.Exchange(ref _context!, context);
+ }
+ }
+
+ public void Dispose()
+ {
+ _processExit.TrySetResult(true);
+ }
+
+ public void TryToStartSendLoopIfNotStarted()
+ {
+ if (Interlocked.CompareExchange(ref _started, 1, 0) != 0)
+ {
+ return;
+ }
+
+ _ = Task.Run(SendLoopAsync).ContinueWith(t => { Log.Error(t.Exception, "FeatureFlags Exposure send loop failed"); }, TaskContinuationOptions.OnlyOnFaulted);
+ }
+
+ private async Task SendLoopAsync()
+ {
+ Log.Debug("ExposureApi::SendLoopAsync -> Enter");
+ while (!_processExit.Task.IsCompleted)
+ {
+ try
+ {
+ var apiRequestFactory = _apiRequestFactory;
+ var uri = apiRequestFactory.GetEndpoint(ExposurePath);
+ var payload = TryGetPayload();
+ if (payload.Count != 0)
+ {
+ var request = apiRequestFactory.Create(uri);
+ using var response = await request.PostAsync(payload, MimeTypes.Json).ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Error while sending Feature Flags exposures to the agent");
+ }
+
+ try
+ {
+ await Task.WhenAny(_processExit.Task, Task.Delay(_sendInterval)).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ // We are shutting down, so don't do anything about it
+ }
+
+ Log.Debug("ExposureApi::SendLoopAsync -> Exit");
+ }
+ }
+
+ private ArraySegment TryGetPayload()
+ {
+ List exposures;
+ lock (_exposures)
+ {
+ if (_exposures.Count == 0)
+ {
+ // nothing to do, skip send
+ return default;
+ }
+
+ exposures = [.. _exposures];
+ _exposures.Clear();
+ }
+
+ var request = new ExposuresRequest(_context, exposures);
+ var json = JsonConvert.SerializeObject(request);
+ return new ArraySegment(Encoding.UTF8.GetBytes(json));
+ }
+
+ public void SendExposure(in ExposureEvent exposure)
+ {
+ if (_exposureCache.Add(exposure))
+ {
+ lock (_exposures)
+ {
+ _exposures.Enqueue(exposure);
+ }
+ }
+
+ TryToStartSendLoopIfNotStarted();
+ }
+
+ private sealed class ExposuresRequest(Dictionary context, List exposures)
+ {
+ public Dictionary Context { get; } = context;
+
+ public List Exposures { get; } = exposures;
+ }
+}
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/Exposure/ExposureCache.cs b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/ExposureCache.cs
new file mode 100644
index 000000000000..a1a1d9815ab1
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/ExposureCache.cs
@@ -0,0 +1,117 @@
+//
+// 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.
+//
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using Datadog.Trace.FeatureFlags.Exposure.Model;
+
+namespace Datadog.Trace.FeatureFlags.Exposure;
+
+/// LRU ExposureEvents cache
+internal sealed class ExposureCache
+{
+ private readonly int _capacity;
+ private readonly Dictionary _cache;
+ private readonly LinkedList _lruList;
+
+ public ExposureCache(int capacity)
+ {
+ _capacity = capacity;
+ _cache = new Dictionary(capacity);
+ _lruList = new LinkedList();
+ }
+
+ public int Size => _lruList.Count;
+
+ public bool Add(ExposureEvent exposureEvent)
+ {
+ lock (_cache)
+ {
+ var key = new Key(exposureEvent);
+ var value = new Value(exposureEvent);
+
+ bool exists = _cache.TryGetValue(key, out var oldValue);
+
+ if (exists)
+ {
+ // Update LRU priority (move element to the begining of the queue)
+ _lruList.Remove(key);
+ _lruList.AddFirst(key);
+
+ if (oldValue == value)
+ {
+ return false;
+ }
+ }
+
+ // New key or different value
+ if (!exists && _cache.Count >= _capacity)
+ {
+ RemoveLeastRecentlyUsed();
+ }
+
+ _cache[key] = value;
+ if (!exists)
+ {
+ _lruList.AddFirst(key);
+ }
+
+ return true;
+ }
+ }
+
+ public Value? Get(Key key)
+ {
+ lock (_cache)
+ {
+ if (_cache.TryGetValue(key, out var value))
+ {
+ // Get operation should refresh LRU priority
+ _lruList.Remove(key);
+ _lruList.AddFirst(key);
+ return value;
+ }
+
+ return null;
+ }
+ }
+
+ private void RemoveLeastRecentlyUsed()
+ {
+ var last = _lruList.Last;
+ if (last != null)
+ {
+ _cache.Remove(last.Value);
+ _lruList.RemoveLast();
+ }
+ }
+
+ public sealed record Key
+ {
+ public Key(ExposureEvent exposureEvent)
+ {
+ Flag = exposureEvent.Flag.Key;
+ Subject = exposureEvent.Subject.Id;
+ }
+
+ public string Flag { get; }
+
+ public string Subject { get; }
+ }
+
+ public sealed record Value
+ {
+ public Value(ExposureEvent exposureEvent)
+ {
+ Variant = exposureEvent.Variant.Key;
+ Allocation = exposureEvent.Allocation.Key;
+ }
+
+ public string Variant { get; }
+
+ public string Allocation { get; }
+ }
+}
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Allocation.cs b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Allocation.cs
new file mode 100644
index 000000000000..a5120acede8d
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Allocation.cs
@@ -0,0 +1,14 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using Datadog.Trace.ClrProfiler.AutoInstrumentation.Aerospike;
+
+namespace Datadog.Trace.FeatureFlags.Exposure.Model;
+
+internal readonly record struct Allocation(string Key);
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/ExposureEvent.cs b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/ExposureEvent.cs
new file mode 100644
index 000000000000..24ab9f44659d
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/ExposureEvent.cs
@@ -0,0 +1,14 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Datadog.Trace.FeatureFlags.Exposure.Model;
+
+internal readonly record struct ExposureEvent(long TimeStamp, Allocation Allocation, Flag Flag, Variant Variant, Subject Subject);
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Flag.cs b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Flag.cs
new file mode 100644
index 000000000000..4d46172896e7
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Flag.cs
@@ -0,0 +1,14 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using Datadog.Trace.ClrProfiler.AutoInstrumentation.Aerospike;
+
+namespace Datadog.Trace.FeatureFlags.Exposure.Model;
+
+internal readonly record struct Flag(string Key);
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Subject.cs b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Subject.cs
new file mode 100644
index 000000000000..6733f9bf2e1b
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Subject.cs
@@ -0,0 +1,14 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using Datadog.Trace.ClrProfiler.AutoInstrumentation.Aerospike;
+
+namespace Datadog.Trace.FeatureFlags.Exposure.Model;
+
+internal readonly record struct Subject(string Id, IDictionary Attributes);
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Variant.cs b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Variant.cs
new file mode 100644
index 000000000000..d6bfa27aa31f
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/Exposure/Model/Variant.cs
@@ -0,0 +1,14 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using Datadog.Trace.ClrProfiler.AutoInstrumentation.Aerospike;
+
+namespace Datadog.Trace.FeatureFlags.Exposure.Model;
+
+internal readonly record struct Variant(string Key);
diff --git a/tracer/src/Datadog.Trace/FeatureFlags/FeatureFlagsEvaluator.cs b/tracer/src/Datadog.Trace/FeatureFlags/FeatureFlagsEvaluator.cs
new file mode 100644
index 000000000000..f63f5e535df8
--- /dev/null
+++ b/tracer/src/Datadog.Trace/FeatureFlags/FeatureFlagsEvaluator.cs
@@ -0,0 +1,716 @@
+//
+// 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.
+//
+
+#nullable enable
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+using Datadog.Trace.FeatureFlags.Rcm.Model;
+using Datadog.Trace.Logging;
+using Datadog.Trace.Vendors.Newtonsoft.Json;
+using Datadog.Trace.Vendors.Newtonsoft.Json.Linq;
+
+namespace Datadog.Trace.FeatureFlags
+{
+ internal sealed class FeatureFlagsEvaluator
+ {
+ internal const string DateFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z'";
+ internal const string MetadataAllocationKey = "dd_allocationKey";
+
+ internal static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(FeatureFlagsEvaluator));
+
+ private readonly ReportExposureDelegate? _onExposureEvent;
+ private readonly ServerConfiguration? _config;
+
+ public FeatureFlagsEvaluator(ReportExposureDelegate? onExposureEvent, ServerConfiguration? config)
+ {
+ _onExposureEvent = onExposureEvent;
+ _config = config;
+ if (_config is null)
+ {
+ Log.Debug("Creating Evaluator without config");
+ }
+ else
+ {
+ Log.Debug("Creating Evaluator with {Flags} flags", _config.Flags?.Count ?? 0);
+ }
+ }
+
+ private delegate bool NumberEquality(double a, double b);
+
+ public Evaluation Evaluate(string flagKey, ValueType resultType, object? defaultValue, EvaluationContext? context)
+ {
+ try
+ {
+ var config = _config;
+ if (config == null)
+ {
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Error,
+ error: "PROVIDER_NOT_READY",
+ metadata: new Dictionary
+ {
+ ["errorCode"] = "PROVIDER_NOT_READY"
+ });
+ }
+
+ if (config.Flags is null || !config.Flags.TryGetValue(flagKey, out var flag) || flag is null)
+ {
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Error,
+ error: "FLAG_NOT_FOUND",
+ metadata: new Dictionary
+ {
+ ["errorCode"] = "FLAG_NOT_FOUND"
+ });
+ }
+
+ if (flag.Enabled != true)
+ {
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Disabled);
+ }
+
+ if (flag.VariationType != resultType)
+ {
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Error,
+ error: "TYPE_MISMATCH",
+ metadata: new Dictionary
+ {
+ ["errorCode"] = "TYPE_MISMATCH"
+ });
+ }
+
+ if (flag.Allocations is null or { Count: 0 })
+ {
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Default);
+ }
+
+ var now = DateTime.UtcNow;
+ var targetingKey = context?.TargetingKey;
+
+ foreach (var allocation in flag.Allocations)
+ {
+ if (!IsAllocationActive(allocation, now))
+ {
+ continue;
+ }
+
+ if (allocation.Rules is { Count: > 0 } allocationRules)
+ {
+ if (!EvaluateRules(allocationRules, context))
+ {
+ continue;
+ }
+ }
+
+ if (allocation.Splits is { Count: > 0 })
+ {
+ foreach (var split in allocation.Splits)
+ {
+ if (StringUtil.IsNullOrEmpty(split.VariationKey))
+ {
+ throw new FormatException($"Empty variation key in allocation {allocation.Key}");
+ }
+
+ var allShardsMatch = true;
+ if (split.Shards is { Count: > 0 } splitShards)
+ {
+ foreach (var shard in splitShards)
+ {
+ if (!MatchesShard(shard, targetingKey))
+ {
+ allShardsMatch = false;
+ break;
+ }
+ }
+ }
+
+ if (allShardsMatch)
+ {
+ return ResolveVariant(flagKey, resultType, defaultValue, flag, split.VariationKey, allocation, now, context);
+ }
+ }
+ }
+ }
+
+ // No allocation / split matched – use default
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Default);
+ }
+ catch (FormatException ex)
+ {
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Error,
+ error: "PARSE_ERROR",
+ metadata: new Dictionary
+ {
+ ["errorCode"] = "PARSE_ERROR",
+ ["message"] = ex.Message
+ });
+ }
+ catch (MissingTargetingKeyException)
+ {
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Error,
+ error: "TARGETING_KEY_MISSING",
+ metadata: new Dictionary
+ {
+ ["errorCode"] = "TARGETING_KEY_MISSING"
+ });
+ }
+ catch (Exception ex)
+ {
+ return new Evaluation(
+ flagKey,
+ defaultValue,
+ EvaluationReason.Error,
+ error: ex.Message,
+ metadata: new Dictionary
+ {
+ ["errorCode"] = "GENERAL",
+ ["message"] = ex.Message
+ });
+ }
+ }
+
+ private static bool IsAllocationActive(Allocation allocation, DateTime now)
+ {
+ var startDate = ParseDate(allocation.StartAt);
+ if (startDate.HasValue && now < startDate.Value)
+ {
+ return false;
+ }
+
+ var endDate = ParseDate(allocation.EndAt);
+ if (endDate.HasValue && now >= endDate.Value)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool EvaluateRules(List rules, EvaluationContext? context)
+ {
+ foreach (var rule in rules)
+ {
+ if (rule.Conditions is null || rule.Conditions.Count == 0)
+ {
+ continue;
+ }
+
+ var allConditionsMatch = true;
+ foreach (var condition in rule.Conditions)
+ {
+ if (!EvaluateCondition(condition, context))
+ {
+ allConditionsMatch = false;
+ break;
+ }
+ }
+
+ if (allConditionsMatch)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool EvaluateCondition(ConditionConfiguration condition, EvaluationContext? context)
+ {
+ if (condition.Operator is null)
+ {
+ throw new FormatException("Condition operator can not be null");
+ }
+
+ if (condition.Operator == ConditionOperator.IS_NULL)
+ {
+ var value = ResolveAttribute(condition.Attribute, context);
+ var isNull = value == null;
+ var expectedNull = condition.Value is bool b ? b : throw new FormatException("Bool value expected");
+ return isNull == expectedNull;
+ }
+
+ var attributeValue = ResolveAttribute(condition.Attribute, context);
+ if (attributeValue == null)
+ {
+ return false;
+ }
+
+ switch (condition.Operator)
+ {
+ case ConditionOperator.MATCHES:
+ return MatchesRegex(attributeValue, condition.Value);
+ case ConditionOperator.NOT_MATCHES:
+ return !MatchesRegex(attributeValue, condition.Value);
+ case ConditionOperator.ONE_OF:
+ return IsOneOf(attributeValue, condition.Value);
+ case ConditionOperator.NOT_ONE_OF:
+ return !IsOneOf(attributeValue, condition.Value);
+ case ConditionOperator.GTE:
+ return CompareNumber(attributeValue, condition.Value, (a, b) => a >= b);
+ case ConditionOperator.GT:
+ return CompareNumber(attributeValue, condition.Value, (a, b) => a > b);
+ case ConditionOperator.LTE:
+ return CompareNumber(attributeValue, condition.Value, (a, b) => a <= b);
+ case ConditionOperator.LT:
+ return CompareNumber(attributeValue, condition.Value, (a, b) => a < b);
+ default:
+ throw new FormatException($"Unknown condition operator {condition.Operator.ToString()}");
+ }
+ }
+
+ private static bool MatchesRegex(object attributeValue, object? conditionValue)
+ {
+ if (conditionValue is null)
+ {
+ throw new FormatException("Condition value can not be null");
+ }
+
+ try
+ {
+ var pattern = conditionValue?.ToString() ?? string.Empty;
+ var regex = new Regex(pattern);
+ return regex.IsMatch(ToString(attributeValue));
+ }
+ catch
+ {
+ return false;
+ }
+
+ static string ToString(object attributeValue)
+ {
+ if (attributeValue is null) { return string.Empty; }
+ if (attributeValue is bool boolValue) { return boolValue ? "true" : "false"; }
+ return Convert.ToString(attributeValue, CultureInfo.InvariantCulture) ?? string.Empty;
+ }
+ }
+
+ private static bool IsOneOf(object attributeValue, object? conditionValue)
+ {
+ if (conditionValue is not IEnumerable enumerable)
+ {
+ throw new FormatException($"Condition value is not an array {Convert.ToString(conditionValue ?? "")}");
+ }
+
+ foreach (var value in enumerable)
+ {
+ if (CompareString(attributeValue, value))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool CompareString(object a, object? b)
+ {
+ if (b is null)
+ {
+ return false;
+ }
+
+ if (Equals(a, b))
+ {
+ return true;
+ }
+
+ if (a is string aTxt && b is string bTxt)
+ {
+ return aTxt.Equals(bTxt);
+ }
+
+ return string.Equals(ToString(a), ToString(b), StringComparison.Ordinal);
+
+ static string? ToString(object obj)
+ {
+ if (obj is bool boolObj)
+ {
+ return boolObj switch
+ {
+ true => "true",
+ _ => "false",
+ };
+ }
+
+ return Convert.ToString(obj);
+ }
+ }
+
+ private static bool CompareNumber(object attributeValue, object? conditionValue, NumberEquality comparator)
+ {
+ if (conditionValue is null)
+ {
+ throw new FormatException("Condition value must not be null");
+ }
+
+ var a = ParseDouble(attributeValue);
+ var b = ParseDouble(conditionValue);
+ return comparator(a, b);
+ }
+
+ private static bool MatchesShard(Shard shard, string? targetingKey)
+ {
+ if (shard.Ranges is null)
+ {
+ return false;
+ }
+
+ var assignedShard = GetShard(shard.Salt ?? string.Empty, targetingKey, shard.TotalShards);
+ foreach (var range in shard.Ranges)
+ {
+ if (assignedShard >= range.Start && assignedShard < range.End)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static int GetShard(string salt, string? targetingKey, int totalShards)
+ {
+ if (StringUtil.IsNullOrEmpty(targetingKey))
+ {
+ throw new MissingTargetingKeyException();
+ }
+
+ var hashKey = $"{salt}-{targetingKey}";
+ var md5Hash = GetMd5Hash(hashKey);
+ var first8Chars = md5Hash.Substring(0, Math.Min(8, md5Hash.Length));
+ var intFromHash = Convert.ToInt64(first8Chars, 16);
+ return (int)(intFromHash % totalShards);
+ }
+
+ private static string GetMd5Hash(string input)
+ {
+ using var md5 = MD5.Create();
+ var bytes = Encoding.UTF8.GetBytes(input);
+ var hashBytes = md5.ComputeHash(bytes);
+ var sb = new StringBuilder();
+ foreach (var b in hashBytes)
+ {
+ sb.Append(b.ToString("x2"));
+ }
+
+ return sb.ToString();
+ }
+
+ private static DateTime? ParseDate(string? dateString)
+ {
+ if (dateString == null)
+ {
+ return null;
+ }
+
+ if (DateTime.TryParseExact(
+ dateString,
+ DateFormat,
+ System.Globalization.CultureInfo.InvariantCulture,
+ System.Globalization.DateTimeStyles.AdjustToUniversal,
+ out var dt))
+ {
+ return dt;
+ }
+
+ throw new FormatException("Wrong date format");
+ }
+
+ private static object? ResolveAttribute(string? name, EvaluationContext? context)
+ {
+ if (name == null || context is null)
+ {
+ return null;
+ }
+
+ // Special case "id": if not present, use targeting key
+ if (name == "id" && !context.Attributes.ContainsKey(name))
+ {
+ if (StringUtil.IsNullOrEmpty(context.TargetingKey))
+ {
+ throw new MissingTargetingKeyException();
+ }
+
+ return context.TargetingKey;
+ }
+
+ return context.GetAttribute(name);
+ }
+
+ internal static object? MapValue(ValueType target, object? value)
+ {
+ if (value is null)
+ {
+ return default!;
+ }
+
+ if (target == ValueType.String)
+ {
+ return Convert.ToString(value, CultureInfo.InvariantCulture);
+ }
+
+ if (target == ValueType.Boolean)
+ {
+ if (value is IConvertible)
+ {
+ if (value is IFormattable && value is not bool)
+ {
+ return (ParseDouble(value) != 0);
+ }
+
+ return Convert.ToBoolean(value);
+ }
+
+ return bool.Parse(value.ToString()!);
+ }
+
+ if (target == ValueType.Integer)
+ {
+ var number = ParseInteger(value);
+ return (int)number;
+ }
+
+ if (target == ValueType.Numeric)
+ {
+ var number = ParseDouble(value);
+ return (double)number;
+ }
+
+ if (target == ValueType.Json)
+ {
+ if (value is JObject)
+ {
+ return value.ToString();
+ }
+
+ var json = JsonConvert.SerializeObject(value);
+ return json;
+ }
+
+ throw new ArgumentException($"Type not supported: {target}");
+ }
+
+ private static double ParseDouble(object value)
+ {
+ if (value is string txt)
+ {
+ return double.Parse(txt, CultureInfo.InvariantCulture);
+ }
+ else if (value is IConvertible)
+ {
+ return Convert.ToDouble(value, CultureInfo.InvariantCulture);
+ }
+
+ return double.Parse(Convert.ToString(value)!, CultureInfo.InvariantCulture);
+ }
+
+ private static double ParseInteger(object value)
+ {
+ if (value is string txt)
+ {
+ return int.Parse(txt, CultureInfo.InvariantCulture);
+ }
+ else if (value is IConvertible)
+ {
+ return Convert.ToInt32(value, CultureInfo.InvariantCulture);
+ }
+
+ return int.Parse(Convert.ToString(value)!, CultureInfo.InvariantCulture);
+ }
+
+ private static string? AllocationKey(Evaluation evaluation)
+ {
+ if (evaluation.FlagMetadata == null)
+ {
+ return null;
+ }
+
+ return evaluation.FlagMetadata.TryGetValue(MetadataAllocationKey, out var key) ? key : null;
+ }
+
+ internal static IDictionary FlattenContext(EvaluationContext? context)
+ {
+ var result = new Dictionary();
+ if (context is not null)
+ {
+ var seen = new HashSet