Skip to content

[dotnet] Enable flag eval metrics tests#6689

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 9 commits into
mainfrom
sameerank/FFL-1946/enable-dotnet-flag-eval-metrics
May 8, 2026
Merged

[dotnet] Enable flag eval metrics tests#6689
gh-worker-dd-mergequeue-cf854d[bot] merged 9 commits into
mainfrom
sameerank/FFL-1946/enable-dotnet-flag-eval-metrics

Conversation

@sameerank

@sameerank sameerank commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Motivation

Prepare the .NET weblog for test_flag_eval_metrics.py tests to validate the new feature_flag.evaluations OTel metric implementation.

Related PRs:

Changes

Weblog (utils/build/docker/dotnet/weblog/):

  • Fix JsonElement handling in FeatureFlagEvaluatorController.cs

    • ASP.NET Core's System.Text.Json deserializes object properties as JsonElement, not native types
    • Added helper methods to extract actual values from JsonElement before calling OpenFeature APIs
    • This prevents InvalidCastException that was occurring before OpenFeature hooks could fire
  • Upgrade OpenFeature packages in app.csproj:

    • OpenFeature: 2.0.0 → 2.3.0 (required for FinallyAsync hook signature with FlagEvaluationDetails<T>)
    • Datadog.FeatureFlags.OpenFeature: Use wildcard version to pick up local/CI builds

Note: The tests/ffe/test_flag_eval_metrics.py tests remain marked as missing_feature in the manifest until dd-trace-dotnet v3.42.0 is released with flag eval metrics support.

Workflow

  1. ⚠️ Create your PR as draft ⚠️
  2. Work on you PR until the CI passes
  3. Mark it as ready for review
    • Test logic is modified? -> Get a review from RFC owner.
    • Framework is modified, or non obvious usage of it -> get a review from R&P team

🚀 Once your PR is reviewed and the CI green, you can merge it!

🛟 #apm-shared-testing 🛟

Reviewer checklist

  • Anything but tests/ or manifests/ is modified ? I have the approval from R&P team
  • A docker base image is modified?
    • the relevant build-XXX-image label is present
  • A scenario is added, removed or renamed?

@github-actions

github-actions Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

CODEOWNERS have been resolved as:

manifests/dotnet.yml                                                    @DataDog/apm-dotnet @DataDog/asm-dotnet
utils/build/docker/dotnet/poc.Dockerfile                                @DataDog/apm-dotnet @DataDog/asm-dotnet @DataDog/system-tests-core
utils/build/docker/dotnet/weblog/Controllers/FeatureFlagEvaluatorController.cs  @DataDog/apm-dotnet @DataDog/asm-dotnet @DataDog/system-tests-core
utils/build/docker/dotnet/weblog/app.csproj                             @DataDog/apm-dotnet @DataDog/asm-dotnet @DataDog/system-tests-core

@sameerank sameerank changed the title Enable dotnet flag eval metrics tests Enable dotnet flag eval metrics tests [dotnet@sameerank/FFL-1946/add-flag-eval-metrics] Apr 4, 2026
@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Apr 4, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: ad85a71 | Docs | Datadog PR Page | Give us feedback!

{
value = request.VariationType?.ToUpper() switch
{
"BOOLEAN" => await _client.GetBooleanValueAsync(request.Flag, Convert.ToBoolean(request.DefaultValue), context),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Problem

The EvaluateRequest class has a DefaultValue property typed as object:

public class EvaluateRequest
{
    public object DefaultValue { get; set; }  // Can be bool, int, string, etc.
}

When ASP.NET Core's System.Text.Json deserializes JSON into an object property, it does not convert to native .NET types. Instead, it wraps the value in a JsonElement:

{ "defaultValue": true }     // Becomes JsonElement, not bool
{ "defaultValue": 42 }       // Becomes JsonElement, not int
{ "defaultValue": "hello" }  // Becomes JsonElement, not string

The original code assumed native types:

Convert.ToBoolean(request.DefaultValue)  // Throws! DefaultValue is JsonElement

Convert.ToBoolean() has no knowledge of JsonElement and throws InvalidCastException.

Why Previous Tests Didn't Fail

The original code had a catch block that swallowed all exceptions:

try
{
    value = ... Convert.ToBoolean(request.DefaultValue) ...
}
catch (Exception ex)
{
    value = request.DefaultValue;
    reason = "ERROR";
}

Previous tests (test_dynamic_evaluation.py, test_exposures.py) were asserting on:

  • The evaluated flag value (returned by the endpoint)
  • The reason field in the response

When InvalidCastException was thrown, the catch block returned reason = "ERROR" and value = request.DefaultValue. If a test expected an error case or didn't care about the reason, it would pass.

Why Flag Eval Metrics Tests Failed

The metrics tests need OpenFeature hooks to fire. The exception was thrown before calling the OpenFeature API:

  1. Convert.ToBoolean(JsonElement) throws
  2. The catch block catches it and returns "ERROR"
  3. OpenFeature is never called → hooks never fire → no metrics emitted

The tests assert on metrics received by the OTLP intake, not on the HTTP response. No OpenFeature call = no hooks = no metrics = test failure.

The Fix

Check if the value is a JsonElement and extract the actual value:

private static bool GetDefaultValueAsBool(object defaultValue)
{
    if (defaultValue is JsonElement jsonElement)
    {
        return jsonElement.ValueKind switch
        {
            JsonValueKind.True => true,
            JsonValueKind.False => false,
            JsonValueKind.String => bool.Parse(jsonElement.GetString()),
            _ => false
        };
    }
    return Convert.ToBoolean(defaultValue);  // Fallback for non-JsonElement
}

@sameerank sameerank force-pushed the sameerank/FFL-1946/enable-dotnet-flag-eval-metrics branch from 136dee4 to 84f94a9 Compare April 7, 2026 13:50
@sameerank sameerank changed the title Enable dotnet flag eval metrics tests [dotnet@sameerank/FFL-1946/add-flag-eval-metrics] Enable dotnet flag eval metrics tests [dotnet@b2040ea748a07f13cf4d6cb3a78b592e6108f577] Apr 7, 2026
@sameerank sameerank force-pushed the sameerank/FFL-1946/enable-dotnet-flag-eval-metrics branch from 84f94a9 to 05a4aed Compare April 7, 2026 14:18
@sameerank sameerank changed the title Enable dotnet flag eval metrics tests [dotnet@b2040ea748a07f13cf4d6cb3a78b592e6108f577] Enable dotnet flag eval metrics tests [dotnet@c448add68042e4dbf059960db74c28c42556c07f] Apr 7, 2026
@sameerank sameerank changed the title Enable dotnet flag eval metrics tests [dotnet@c448add68042e4dbf059960db74c28c42556c07f] [dotnet] Enable FFE flag eval metrics tests (dd-trace-dotnet@9c81be358efcf3eb5d4f4e6bc494c73e8f39d8a1) Apr 7, 2026
@sameerank sameerank changed the title [dotnet] Enable FFE flag eval metrics tests (dd-trace-dotnet@9c81be358efcf3eb5d4f4e6bc494c73e8f39d8a1) [dotnet] Enable flag eval metrics tests (dd-trace-dotnet@5cf5bc37f758dfdb95aa9b83244d6b9c76762f48) Apr 7, 2026
@sameerank sameerank changed the title [dotnet] Enable flag eval metrics tests (dd-trace-dotnet@5cf5bc37f758dfdb95aa9b83244d6b9c76762f48) [dotnet] Enable flag eval metrics tests [dotnet@5cf5bc37f758dfdb95aa9b83244d6b9c76762f48] Apr 7, 2026
@sameerank sameerank changed the title [dotnet] Enable flag eval metrics tests [dotnet@5cf5bc37f758dfdb95aa9b83244d6b9c76762f48] [dotnet] Enable flag eval metrics tests Apr 8, 2026
@sameerank

sameerank commented Apr 8, 2026

Copy link
Copy Markdown
Contributor Author

Leaving the test disabled for now because the changes in DataDog/dd-trace-dotnet#8367 haven't been published yet. Tests pass when run this locally: DataDog/dd-trace-dotnet#8367 (comment)

Copy-pasted from the referenced comment:

This is what I did (not 100% that this is the most efficient approach, but it works). I downloaded and extracted these .zip files:

Screenshot 2026-04-08 at 12 30 58 PM

and

Screenshot 2026-04-08 at 12 31 26 PM

And extracted datadog-dotnet-apm-3.42.0.arm64.tar.gz and copied the contents to
/Users/.../system-tests/binaries along with two *.nupkg files from nuget-packages:

Screenshot 2026-04-08 at 12 41 31 PM

And then did the following steps to run system tests locally

cd /Users/../system-tests
# Build the docker image
./build.sh --library dotnet --weblog-variant poc --images weblog
# Run tests
./run.sh FEATURE_FLAGGING_AND_EXPERIMENTATION --library dotnet -k "test_ffe_eval"
# Output shows that we are using the not-yet-published version 3.42.0 and tests are passing
=============================== test context ================================
Scenario: FEATURE_FLAGGING_AND_EXPERIMENTATION
Logs folder: ./logs_feature_flagging_and_experimentation
Starting containers...
Agent: 7.77.2
Backend: datad0g.com
Library: dotnet@3.42.0
Weblog variant: poc
Weblog system: Linux weblog 6.12.76-linuxkit #1 SMP Fri Mar  6 10:10:19 UTC 2026 aarch64 GNU/Linux

============================ test session starts ============================
collected 2427 items / 2410 deselected / 17 selected                        
-------------------------------- tests setup --------------------------------

tests/ffe/test_flag_eval_metrics.py .................

--------------------- Wait for library interface (40s) ----------------------
---------------------- Wait for agent interface (30s) -----------------------
---------------------- Wait for backend interface (0s) ----------------------

tests/ffe/test_flag_eval_metrics.py .................                 [100%]

- generated xml file: /Users/.../system-tests/logs_feature_flagging_and_experimentation/reportJunit.xml -
============== 17 passed, 2410 deselected in 175.48s (0:02:55) ==============

RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y curl

# install dd-trace-dotnet (must be done before setting LD_PRELOAD)
COPY utils/build/docker/dotnet/install_ddtrace.sh binaries/ /binaries/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why run COPY binaries/ commands twice?

Stage 1: build-app (SDK image)

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-app

# NEW: Copy binaries for NuGet package resolution
COPY binaries/ /binaries/
RUN if ls /binaries/*.nupkg 1> /dev/null 2>&1; then \
        dotnet nuget add source /binaries --name local-packages; \
    fi

# NuGet packages are resolved HERE
RUN dotnet restore
RUN dotnet publish

Purpose: Build the weblog application. NuGet packages (like Datadog.FeatureFlags.OpenFeature.*.nupkg) must be available as a NuGet source before dotnet restore runs.

Stage 2: runtime (ASP.NET image)

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime

# EXISTING: Copy binaries for tracer installation
COPY utils/build/docker/dotnet/install_ddtrace.sh binaries/ /binaries/
RUN /binaries/install_ddtrace.sh

Purpose: Install the Datadog tracer into the runtime image. The install_ddtrace.sh script uses:

  • Native profiler: Datadog.Trace.ClrProfiler.Native.so
  • Managed tracer DLLs: net6.0/Datadog.Trace.dll, etc.

@sameerank sameerank marked this pull request as ready for review April 9, 2026 07:19
@sameerank sameerank requested review from a team as code owners April 9, 2026 07:19

@nccatoni nccatoni left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM (for @DataDog/system-tests-core) but you should get a review from someone more familiar with the weblog

System.Text.Json deserializes JSON values into Dictionary<string, object>
as JsonElement wrappers rather than native strings. The previous code
used `attr.Value as string` which returned null for JsonElement values,
causing targeting rules to fail.

This fix properly extracts string values from JsonElement, enabling
targeting rule evaluation to work correctly.
Remove irrelevant skips for:
- Test_FFE_Eval_Metric_Parse_Error_Invalid_Regex
- Test_FFE_Eval_No_Config_Loaded

The .NET SDK uses a managed evaluator (not libdatadog), and now
correctly returns PARSE_ERROR for invalid regex patterns and
PROVIDER_NOT_READY when no config is loaded.

Related: DataDog/dd-trace-dotnet#8367
- Add local NuGet source from binaries folder if nupkg files present
- Update OpenFeature to 2.3.0 (required by Datadog.FeatureFlags.OpenFeature 2.0.1)
- Use wildcard version for Datadog.FeatureFlags.OpenFeature to pick up local builds
The flag eval metrics feature is implemented in dd-trace-dotnet PR #8367
but not yet released. Mark as missing_feature until v3.42.0 is published.
@sameerank sameerank force-pushed the sameerank/FFL-1946/enable-dotnet-flag-eval-metrics branch from bf0159e to ef91458 Compare April 22, 2026 21:10
@sameerank sameerank changed the title [dotnet] Enable flag eval metrics tests [dotnet] Enable flag eval metrics tests [dotnet@sameerank/FFL-1945/add-flag-eval-metrics] Apr 22, 2026
@sameerank sameerank changed the title [dotnet] Enable flag eval metrics tests [dotnet@sameerank/FFL-1945/add-flag-eval-metrics] [dotnet] Enable flag eval metrics tests [dotnet@sameerank/FFL-1946/add-flag-eval-metrics] Apr 22, 2026
@sameerank sameerank force-pushed the sameerank/FFL-1946/enable-dotnet-flag-eval-metrics branch from a2caf44 to 5654890 Compare April 22, 2026 22:24
@sameerank sameerank force-pushed the sameerank/FFL-1946/enable-dotnet-flag-eval-metrics branch 2 times, most recently from a9d24b8 to 5e836ba Compare April 23, 2026 07:34
@sameerank sameerank force-pushed the sameerank/FFL-1946/enable-dotnet-flag-eval-metrics branch from 5e836ba to 855e812 Compare April 24, 2026 14:17
@sameerank

Copy link
Copy Markdown
Contributor Author

The tests pass when I run locally with the artifacts in https://dev.azure.com/datadoghq/dd-trace-dotnet/_build/results?buildId=200327&view=artifacts&pathAsName=false&type=publishedArtifacts

I believe the failures in CI will resolve after DataDog/dd-trace-dotnet#8367 is merged and a new package is published. If I understand correctly, CI Docker image only includes the tracer tar.gz and the weblog restores Datadog.FeatureFlags.OpenFeature from nuget.org, which doesn't have the metrics hook yet

@cbeauchesne cbeauchesne left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You'll nee to remove the branch flag in PR title and re-run the CI to be able to merge it.

sameerank added a commit to DataDog/dd-trace-dotnet that referenced this pull request May 8, 2026
## Summary of changes

Adds flag evaluation metrics support to the Datadog OpenFeature
provider. When a feature flag is evaluated, a counter metric is emitted
with relevant attributes.

## Reason for change

Part of the Feature Flags Evaluation (FFE) initiative to provide
visibility into flag evaluations across all Datadog server SDKs. This
brings parity with implementations in dd-trace-py and dd-trace-go.

## Implementation details

- **FlagEvalMetrics.cs**: Core metrics recording class using
`System.Diagnostics.Metrics`
  - Meter name: `Datadog.FeatureFlags.OpenFeature`
  - Metric: `feature_flag.evaluations` (Counter<long>)
  - Unit: `{evaluation}`
- Tags: `feature_flag.key`, `feature_flag.result.variant`,
`feature_flag.result.reason`, `error.type` (optional),
`feature_flag.result.allocation_key` (optional)

- **FlagEvalMetricsHook.cs**: OpenFeature hook that implements
`FinallyAsync` to record metrics after each evaluation completes
  - Converts reason to lowercase for consistency
  - Maps `ErrorType` enum to snake_case strings
  - Extracts allocation key from flag metadata when present

- **DatadogProvider.cs**: Registers the metrics hook with the
OpenFeature API

- Updated OpenFeature SDK dependency from 2.0.0 to 2.3.0

- **Package version bumped to 2.3.0** to match OpenFeature SDK
dependency version. This signals the breaking changes from OpenFeature
2.3.0:
  - .NET 6 support dropped (now requires .NET 8+)
  - Hook `finally` signature changed to include evaluation details

**Conditional compilation**: Only enabled for .NET 6+ (`#if
NET6_0_OR_GREATER`) since `System.Diagnostics.Metrics` requires .NET 6+.

## Test coverage

- Feature is validated through system-tests
(`tests/ffe/test_flag_eval_metrics.py`)
- Also checked in DataDog/ffe-dogfooding#56
- Unit tests were explored but deferred due to type conflicts between
shared source files in `Datadog.FeatureFlags.OpenFeature` and
`Datadog.Trace` assemblies

## Other details

Jira: https://datadoghq.atlassian.net/browse/FFL-1946

Related PRs:
- dd-trace-py: DataDog/dd-trace-py#17029
- dd-trace-go: DataDog/dd-trace-go#3381
- system-tests: DataDog/system-tests#6689

---------

Co-authored-by: Andrew Lock <andrew.lock@datadoghq.com>
@sameerank sameerank changed the title [dotnet] Enable flag eval metrics tests [dotnet@sameerank/FFL-1946/add-flag-eval-metrics] [dotnet] Enable flag eval metrics tests May 8, 2026
…shed

The tests pass locally but CI fails because Datadog.FeatureFlags.OpenFeature
2.3.0 hasn't been published to NuGet.org yet (current version is 2.0.1).

Tracked by FFL-2257.
Comment thread manifests/dotnet.yml
tests/ffe/test_dynamic_evaluation.py: v3.36.0
tests/ffe/test_exposures.py: v3.36.0
tests/ffe/test_flag_eval_metrics.py: missing_feature
tests/ffe/test_flag_eval_metrics.py: missing_feature (FFL-2257 - Requires Datadog.FeatureFlags.OpenFeature 2.3.0 NuGet package to be published)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The -dev suffix doesn't work if the feature depends are NuGet package dependency updates, which are pulled from NuGet.org. The latest_snapshot Docker image doesn't include the .nupkg files.

I imagine the proper fix is to update create-system-test-docker-base-images.yml to include .nupkg files in the image, but I wasn't planning to make anything more than minor changes to the existing testing infra, so I think the simplest thing for now is to disable the test and wait for the official release to enable it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've verified that the tests pass locally for now

@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot merged commit 362df5d into main May 8, 2026
110 checks passed
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot deleted the sameerank/FFL-1946/enable-dotnet-flag-eval-metrics branch May 8, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants