Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a4fdc98
Add Microsoft.FeatureManagement.Telemetry.OpenTelemetry package with …
yuanqu72 Aug 26, 2026
5fa02de
Clarify OpenTelemetryDemo README wording and recommend OpenTelemetry …
yuanqu72 Aug 26, 2026
4b9439f
Merge branch 'main' into open-telemetry-update
yuanqu72 Aug 26, 2026
1aa1bb8
Add TargetingLogProcessor and migrate OpenTelemetryDemo to the Azure …
yuanqu72 Aug 27, 2026
8b8454a
Fix README OpenTelemetryDemo sample description to ASP.NET Core Web App.
yuanqu72 Aug 27, 2026
1c339a0
Make install-dotnet.ps1 fail loudly on a silent .NET install failure …
yuanqu72 Aug 27, 2026
b0a5b18
Merge upstream/main into open-telemetry-update, resolving install-dot…
yuanqu72 Aug 27, 2026
441bd57
Update OpenTelemetryDemo README description to mention Variants.
yuanqu72 Aug 27, 2026
8efecf0
Bump the github-actions group with 2 updates
dependabot[bot] Sep 1, 2026
4b3bae3
Reuse OpenTelemetry histogram instruments, await sign-in, avoid overw…
yuanqu72 Sep 3, 2026
a19cbde
Merge pull request #620 from microsoft/dependabot/github_actions/gith…
mrm9084 Sep 4, 2026
b0a5a09
Bump the github-actions group with 2 updates (#622)
dependabot[bot] Sep 8, 2026
7f66f1d
Fix feature evaluation log sampling
yuanqu72 Sep 14, 2026
71026b0
Remove sampling note from README
yuanqu72 Sep 14, 2026
040a5b2
Simplify OpenTelemetry integration and rename the feature evaluation …
yuanqu72 Sep 15, 2026
84731d8
Refactor OpenTelemetry registration and shared constants while clarif…
yuanqu72 Sep 16, 2026
1dd498f
Rename OpenTelemetry integration to AddFeatureManagementProcessors
yuanqu72 Sep 18, 2026
097256f
Merge branch 'main' into open-telemetry-update
yuanqu72 Sep 21, 2026
2f75919
Merge pull request #617 from yuanqu72/open-telemetry-update
yuanqu72 Sep 21, 2026
c0afaa0
Bump the github-actions group with 2 updates (#624)
dependabot[bot] Sep 22, 2026
0c8a327
Make precedence source ordered (#623)
linglingye001 Sep 22, 2026
0c03f6e
version bump 4.8.0 (#625)
linglingye001 Sep 23, 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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
run: pwsh build.ps1 -RestoreOnly

- name: Initialize CodeQL
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
with:
languages: 'csharp'

Expand All @@ -50,4 +50,4 @@ jobs:
path: ${{ github.workspace }}/tests/**/*.trx

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
Expand Down Expand Up @@ -87,6 +87,6 @@ jobs:
exit 1

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
with:
category: "/language:${{matrix.language}}"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,6 @@ __pycache__/
# Secrets file
appsettings.Secrets.json


# Local, machine-specific build notes (not for public repo)
BUILD-NOTES.local.md
147 changes: 147 additions & 0 deletions Microsoft.FeatureManagement.sln

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ Feature management provides a way to develop and expose application functionalit
* [Blazor Server App](./examples/BlazorServerApp)
* [ASP.NET Core Web App with Variants and Telemetry](./examples/VariantAndTelemetryDemo)
* [ASP.NET Core Web App with Variant Service](./examples/VariantServiceDemo)
* [ASP.NET Core Web App with Variants and OpenTelemetry](./examples/OpenTelemetryDemo)

## OpenTelemetry integration

`Microsoft.FeatureManagement.Telemetry.OpenTelemetry` provides feature-evaluation
event publishing and targeting enrichment for spans and logs. Register feature
management with `services.AddFeatureManagement()`, then call
`AddFeatureManagementProcessors()` on the builder returned by `services.AddOpenTelemetry()`.
The integration automatically attaches its targeting processors; applications
configure their own providers and exporters.

Register the integration before exporters, including exporters configured
separately through logging APIs. An exporter integration with an explicit
exporter-last guarantee may be registered in either order. This library does not
reorder existing OpenTelemetry registrations.

See the [OpenTelemetry sample](./examples/OpenTelemetryDemo) for complete setup.

## Contributing

Expand Down
24 changes: 22 additions & 2 deletions build/install-dotnet.ps1
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
# Installs .NET 8 and .NET 10 for CI/CD environment
# see: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script#examples

$ErrorActionPreference = "Stop"

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;

&([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Channel 8.0
$installScript = [scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))

$channels = @("8.0", "10.0")

foreach ($channel in $channels)
{
& $installScript -Channel $channel
}

# Fail fast with a clear error instead of letting a silent install failure surface later as a
# confusing "You must install or update .NET to run this application" error during test/build.
$dotnet = & "$PSScriptRoot/resolve-dotnet.ps1"
$installedSdks = & $dotnet --list-sdks

&([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Channel 10.0
foreach ($channel in $channels)
{
if (-not ($installedSdks | Select-String -Pattern "^$([regex]::Escape($channel))\."))
{
throw "Verification failed: .NET SDK for channel $channel was not found after installation. Installed SDKs:`n$installedSdks"
}
}
39 changes: 39 additions & 0 deletions examples/OpenTelemetryDemo/LoggerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Microsoft.Extensions.Logging;

namespace OpenTelemetryDemo
{
/// <summary>
/// High-performance, source-generator-style logging (<see cref="LoggerMessage.Define"/>) for
/// this app's custom events. Each event's message template includes the
/// "microsoft.custom_event.name" placeholder, which becomes a `LogRecord` attribute that the
/// Azure Monitor OpenTelemetry Exporter (if configured) uses to classify the log as a custom
/// event in Application Insights, rather than a plain trace.
/// </summary>
public static class LoggerExtensions
{
private const string AzureMonitorCustomEventNameKey = "microsoft.custom_event.name";

private static readonly Action<ILogger, string, int, Exception> _vote = LoggerMessage.Define<string, int>(
LogLevel.Information,
new EventId(1, AzureMonitorCustomEventNameKey),
"{" + AzureMonitorCustomEventNameKey + "} {ImageRating}");

private static readonly Action<ILogger, string, string, long, Exception> _checkout = LoggerMessage.Define<string, string, long>(
LogLevel.Information,
new EventId(2, AzureMonitorCustomEventNameKey),
"{" + AzureMonitorCustomEventNameKey + "} {success} {checkoutAmount}");

public static void LogVote(this ILogger logger, int rating)
{
_vote(logger, "Vote", rating, null);
}

public static void LogCheckout(this ILogger logger, long amount)
{
_checkout(logger, "checkout", "yes", amount, null);
}
}
}
17 changes: 17 additions & 0 deletions examples/OpenTelemetryDemo/OpenTelemetryDemo.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.6.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.FeatureManagement.AspNetCore\Microsoft.FeatureManagement.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\Microsoft.FeatureManagement.Telemetry.OpenTelemetry\Microsoft.FeatureManagement.Telemetry.OpenTelemetry.csproj" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions examples/OpenTelemetryDemo/Pages/Checkout.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@page
@model CheckoutModel
@{
ViewData["Title"] = "Checkout";
}
<h1>@ViewData["Title"]</h1>

<p>Click Below To Check Out!</p>

<form method="post">
@Html.AntiForgeryToken()
<button id="checkout" name="checkout" type="submit" class="btn btn-primary btn-sm">
Check Out
</button>
</form>

@if (Model.CheckedOut)
{
<p class="mt-3">Checked Out! Amount: @Model.CheckoutAmount</p>
}
44 changes: 44 additions & 0 deletions examples/OpenTelemetryDemo/Pages/Checkout.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Diagnostics.Metrics;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace OpenTelemetryDemo.Pages
{
public class CheckoutModel : PageModel
{
private readonly Meter _meter;
private readonly Histogram<long> _checkoutAmountHistogram;
private readonly ILogger<CheckoutModel> _logger;

public CheckoutModel(IMeterFactory meterFactory, ILogger<CheckoutModel> logger)
{
_meter = meterFactory?.Create("OpenTelemetryDemo") ?? throw new ArgumentNullException(nameof(meterFactory));
_checkoutAmountHistogram = _meter.CreateHistogram<long>("checkoutAmount");
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public bool CheckedOut { get; set; }

public int CheckoutAmount { get; set; }

public void OnPost()
{
CheckoutAmount = Random.Shared.Next(1, 100);

//
// Track the checkout amount metric using the OpenTelemetry metrics API
// (System.Diagnostics.Metrics), exported via WithMetrics/AddMeter.
_checkoutAmountHistogram.Record(CheckoutAmount);

//
// Emits a log-based custom event, the OpenTelemetry equivalent of
// TelemetryClient.TrackEvent.
_logger.LogCheckout(CheckoutAmount);

CheckedOut = true;
}
}
}
26 changes: 26 additions & 0 deletions examples/OpenTelemetryDemo/Pages/Error.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
30 changes: 30 additions & 0 deletions examples/OpenTelemetryDemo/Pages/Error.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;

namespace OpenTelemetryDemo.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

private readonly ILogger<ErrorModel> _logger;

public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}

public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}
72 changes: 72 additions & 0 deletions examples/OpenTelemetryDemo/Pages/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

<div class="text-center mb-40">
<h1 class="display-4">Welcome, @Model.Username !</h1>
</div>

<div class="text-center">
<div style="display:inline-block;width:500px;">
<img class="mb-40" src="@ViewData["ImageUri"]" alt="Dinosaur selected by the ImageRating feature" />
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">
<span class="glyphicon glyphicon-arrow-right"></span>Rate the image on a scale of 1 - 5 !<span class="glyphicon glyphicon-new-window"></span>
</h3>
</div>
<form action="/" method="post">
<div class="panel-body mb-5">
<ul class="list-group">
<li class="list-group-item">
<div class="radio">
<label>
<input type="radio" name="imageScore" value="5">
5
</label>
</div>
</li>
<li class="list-group-item">
<div class="radio">
<label>
<input type="radio" name="imageScore" value="4">
4
</label>
</div>
</li>
<li class="list-group-item">
<div class="radio">
<label>
<input type="radio" name="imageScore" value="3">
3
</label>
</div>
</li>
<li class="list-group-item">
<div class="radio">
<label>
<input type="radio" name="imageScore" value="2">
2
</label>
</div>
</li>
<li class="list-group-item">
<div class="radio">
<label>
<input type="radio" name="imageScore" value="1">
1
</label>
</div>
</li>
</ul>
</div>
@Html.AntiForgeryToken()
<div class="panel-footer">
<input type="submit" class="btn btn-primary btn-sm" value="Vote" />
</div>
</form>
</div>
</div>
</div>
Loading
Loading