Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4a7d9dc
add initial content type pattern
amerjusupovic Apr 7, 2025
8c59edb
format fix
amerjusupovic Apr 7, 2025
8faa194
fix comment
amerjusupovic Apr 7, 2025
54a6b1c
update to account for chat completion vs ai profiles
amerjusupovic Apr 11, 2025
92377e3
in progress fix adapter to use existing requesttracingoptions
amerjusupovic Apr 11, 2025
dd1d538
use content type tracing to pass to requesttracingoptions
amerjusupovic Apr 11, 2025
51ecabf
fix comments and naming
amerjusupovic Apr 11, 2025
a75e9ac
remove unneeded file
amerjusupovic Apr 11, 2025
93a5259
add check for request tracing enabled
amerjusupovic Apr 14, 2025
140503f
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Dotn…
amerjusupovic Apr 14, 2025
99d0132
check content type in preparedata
amerjusupovic Apr 14, 2025
5d98e66
remove errors
amerjusupovic Apr 14, 2025
0b11741
fix spacing
amerjusupovic Apr 14, 2025
52942b7
fix test
amerjusupovic Apr 15, 2025
f036f0b
remove unused usings, add back catch for .net framework
amerjusupovic Apr 15, 2025
4ee9199
fix parsing
amerjusupovic Apr 15, 2025
5b06d28
rename constants
amerjusupovic Apr 15, 2025
012256a
fix indent
amerjusupovic Apr 15, 2025
e2cfb46
update for PR comments
amerjusupovic Apr 16, 2025
f483ca7
PR comments, update if conditions
amerjusupovic Apr 16, 2025
ed90256
add isjson check
amerjusupovic Apr 16, 2025
2e0a547
update isjson extension
amerjusupovic Apr 16, 2025
f70f91a
Merge branch 'main' into ajusupovic/contenttype-tracing
amerjusupovic Apr 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT license.
//
using Azure.Core;
using Azure.Core.Pipeline;
using Azure.Core.Pipeline;
using Azure.Data.AppConfiguration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
Expand All @@ -11,7 +11,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
Expand All @@ -25,7 +25,7 @@ public class AzureAppConfigurationOptions
private const int MaxRetries = 2;
private static readonly TimeSpan MaxRetryDelay = TimeSpan.FromMinutes(1);
private static readonly TimeSpan NetworkTimeout = TimeSpan.FromSeconds(10);
private static readonly KeyValueSelector DefaultQuery = new KeyValueSelector { KeyFilter = KeyFilter.Any, LabelFilter = LabelFilter.Null };
private static readonly KeyValueSelector DefaultQuery = new KeyValueSelector { KeyFilter = KeyFilter.Any, LabelFilter = LabelFilter.Null };

private List<KeyValueWatcher> _individualKvWatchers = new List<KeyValueWatcher>();
private List<KeyValueWatcher> _ffWatchers = new List<KeyValueWatcher>();
Expand Down Expand Up @@ -141,7 +141,7 @@ internal IEnumerable<IKeyValueAdapter> Adapters
internal bool IsKeyVaultRefreshConfigured { get; private set; } = false;

/// <summary>
/// Indicates all types of feature filters used by the application.
/// Indicates all feature flag features used by the application.
/// </summary>
internal FeatureFlagTracing FeatureFlagTracing { get; set; } = new FeatureFlagTracing();

Expand Down Expand Up @@ -514,9 +514,9 @@ private static ConfigurationClientOptions GetDefaultClientOptions()
clientOptions.Retry.Mode = RetryMode.Exponential;
clientOptions.AddPolicy(new UserAgentHeaderPolicy(), HttpPipelinePosition.PerCall);
clientOptions.Transport = new HttpClientTransport(new HttpClient()
{
{
Timeout = NetworkTimeout
});
});

return clientOptions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Net.Sockets;
using System.Security;
using System.Text;
Expand Down Expand Up @@ -597,9 +598,45 @@ private async Task<Dictionary<string, string>> PrepareData(Dictionary<string, Co
// Reset old feature flag tracing in order to track the information present in the current response from server.
_options.FeatureFlagTracing.ResetFeatureFlagTracing();

// Reset old request tracing values for content type
if (_requestTracingEnabled && _requestTracingOptions != null)
{
_requestTracingOptions.UsesAIConfiguration = false;
_requestTracingOptions.UsesAIChatCompletionConfiguration = false;
}

foreach (KeyValuePair<string, ConfigurationSetting> kvp in data)
{
IEnumerable<KeyValuePair<string, string>> keyValuePairs = null;

if (_requestTracingEnabled &&
_requestTracingOptions != null &&
!string.IsNullOrWhiteSpace(kvp.Value.ContentType) &&
kvp.Value.ContentType.IsJsonContentType())
{
ContentType contentType = null;

try
{
contentType = new ContentType(kvp.Value.ContentType.Trim());
}
catch (FormatException) { }
catch (IndexOutOfRangeException) { }

if (contentType != null &&
contentType.Parameters.ContainsKey("profile") &&
!string.IsNullOrEmpty(contentType.Parameters["profile"]) &&
contentType.Parameters["profile"].StartsWith(RequestTracingConstants.AIMimeProfile))
{
_requestTracingOptions.UsesAIConfiguration = true;

if (contentType.Parameters["profile"].StartsWith(RequestTracingConstants.AIChatCompletionMimeProfile))
{
_requestTracingOptions.UsesAIChatCompletionConfiguration = true;
}
}
}

keyValuePairs = await ProcessAdapters(kvp.Value, cancellationToken).ConfigureAwait(false);

foreach (KeyValuePair<string, string> kv in keyValuePairs)
Expand Down Expand Up @@ -636,7 +673,7 @@ private async Task LoadAsync(bool ignoreFailures, CancellationToken cancellation
{
IEnumerable<ConfigurationClient> clients = _configClientManager.GetClients();

if (_requestTracingOptions != null)
if (_requestTracingEnabled && _requestTracingOptions != null)
{
_requestTracingOptions.ReplicaCount = clients.Count() - 1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ internal class RequestTracingConstants
public const string ReplicaCountKey = "ReplicaCount";
public const string FeaturesKey = "Features";
public const string LoadBalancingEnabledTag = "LB";
public const string AIConfigurationTag = "AI";
public const string AIChatCompletionConfigurationTag = "AICC";

public const string SignalRUsedTag = "SignalR";
public const string FailoverRequestTag = "Failover";
public const string PushRefreshTag = "PushRefresh";
Expand All @@ -54,5 +57,8 @@ internal class RequestTracingConstants
public const string SignalRAssemblyName = "Microsoft.AspNetCore.SignalR";

public const string Delimiter = "+";

public const string AIMimeProfile = "https://azconfig.io/mime-profiles/ai";
public const string AIChatCompletionMimeProfile = "https://azconfig.io/mime-profiles/ai/chat-completion";
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,67 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Net.Mime;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions
{
internal static class StringExtensions
{
private static readonly IEnumerable<string> ExcludedJsonContentTypes = new[]
{
FeatureManagementConstants.ContentType,
KeyVaultConstants.ContentType
};

public static bool IsJsonContentType(this string contentType)
{
if (string.IsNullOrWhiteSpace(contentType))
{
return false;
}

string acceptedMainType = "application";
string acceptedSubType = "json";
string mediaType;

try
{
mediaType = new ContentType(contentType.Trim()).MediaType;
}
catch (FormatException)
{
return false;
}
catch (IndexOutOfRangeException)
{
// Bug in System.Net.Mime.ContentType throws this if contentType is "xyz/"
// https://github.com/dotnet/runtime/issues/39337
return false;
}

if (!ExcludedJsonContentTypes.Contains(mediaType, StringComparer.OrdinalIgnoreCase))
{
// Since contentType has been validated using System.Net.Mime.ContentType,
// mediaType will always have exactly 2 parts after splitting on '/'
string[] types = mediaType.Split('/');
if (string.Equals(types[0], acceptedMainType, StringComparison.OrdinalIgnoreCase))
{
string[] subTypes = types[1].Split('+');
if (subTypes.Contains(acceptedSubType, StringComparer.OrdinalIgnoreCase))
{
return true;
}
}
}

return false;
}

public static string NormalizeNull(this string s)
{
return s == LabelFilter.Null ? null : s;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
// Licensed under the MIT license.
//
using Azure.Data.AppConfiguration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -16,12 +14,6 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
{
internal class JsonKeyValueAdapter : IKeyValueAdapter
{
private static readonly IEnumerable<string> ExcludedJsonContentTypes = new[]
{
FeatureManagementConstants.ContentType,
KeyVaultConstants.ContentType
};

public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(ConfigurationSetting setting, Uri endpoint, Logger logger, CancellationToken cancellationToken)
{
if (setting == null)
Expand Down Expand Up @@ -58,41 +50,7 @@ public bool CanProcess(ConfigurationSetting setting)
return false;
}

string acceptedMainType = "application";
string acceptedSubType = "json";
string mediaType;

try
{
mediaType = new ContentType(setting.ContentType.Trim()).MediaType;
}
catch (FormatException)
{
return false;
}
catch (IndexOutOfRangeException)
{
// Bug in System.Net.Mime.ContentType throws this if contentType is "xyz/"
// https://github.com/dotnet/runtime/issues/39337
return false;
}

if (!ExcludedJsonContentTypes.Contains(mediaType, StringComparer.OrdinalIgnoreCase))
{
// Since contentType has been validated using System.Net.Mime.ContentType,
// mediaType will always have exactly 2 parts after splitting on '/'
string[] types = mediaType.Split('/');
if (string.Equals(types[0], acceptedMainType, StringComparison.OrdinalIgnoreCase))
{
string[] subTypes = types[1].Split('+');
if (subTypes.Contains(acceptedSubType, StringComparer.OrdinalIgnoreCase))
{
return true;
}
}
}

return false;
return setting.ContentType.IsJsonContentType();
}

public void OnChangeDetected(ConfigurationSetting setting = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,28 @@ internal class RequestTracingOptions
/// </summary>
public bool IsPushRefreshUsed { get; set; } = false;

/// <summary>
/// Flag to indicate whether any key-value uses the json content type and contains
/// a parameter indicating an AI profile.
/// </summary>
public bool UsesAIConfiguration { get; set; } = false;

/// <summary>
/// Flag to indicate whether any key-value uses the json content type and contains
/// a parameter indicating an AI chat completion profile.
/// </summary>
public bool UsesAIChatCompletionConfiguration { get; set; } = false;

/// <summary>
/// Checks whether any tracing feature is used.
/// </summary>
/// <returns>true if any tracing feature is used, otherwise false.</returns>
public bool UsesAnyTracingFeature()
{
return IsLoadBalancingEnabled || IsSignalRUsed;
return IsLoadBalancingEnabled ||
IsSignalRUsed ||
UsesAIConfiguration ||
UsesAIChatCompletionConfiguration;
}

/// <summary>
Expand Down Expand Up @@ -105,6 +120,26 @@ public string CreateFeaturesString()
sb.Append(RequestTracingConstants.SignalRUsedTag);
}

if (UsesAIConfiguration)
{
if (sb.Length > 0)
{
sb.Append(RequestTracingConstants.Delimiter);
}

sb.Append(RequestTracingConstants.AIConfigurationTag);
}

if (UsesAIChatCompletionConfiguration)
{
if (sb.Length > 0)
{
sb.Append(RequestTracingConstants.Delimiter);
}

sb.Append(RequestTracingConstants.AIChatCompletionConfigurationTag);
}

return sb.ToString();
}
}
Expand Down