Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ class AppConfigurationApplicationSettingPropertySource extends AppConfigurationP
@Override
public void initProperties(List<String> keyPrefixTrimValues, Context context) throws InvalidConfigurationPropertyValueException {

replicaClient.getTracingInfo().resetAiConfigurationTracing();

List<String> labels = new ArrayList<>(Arrays.asList(labelFilters));

// Reverse labels so they have the right priority order.
Collections.reverse(labels);

Expand All @@ -88,6 +91,7 @@ protected void processConfigurationSettings(List<ConfigurationSetting> settings,
List<String> keyPrefixTrimValues)
throws InvalidConfigurationPropertyValueException {
for (ConfigurationSetting setting : settings) {
replicaClient.getTracingInfo().updateAiConfigurationTracing(setting.getContentType());
if (keyPrefixTrimValues == null && StringUtils.hasText(keyFilter)) {
keyPrefixTrimValues = new ArrayList<>();
keyPrefixTrimValues.add(keyFilter.substring(0, keyFilter.length() - 1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,29 @@ public class AppConfigurationConstants {
public static final String FEATURE_FLAG_REFERENCE = "FeatureFlagReference";

public static final String E_TAG = "ETag";

/**
* AI mime profile identifier in content type.
*/
public static final String APP_CONFIG_AI_MIME_PROFILE = "azconfig.io/mime-profiles/ai";

/**
* AI Chat Completion mime profile identifier in content type.
*/
public static final String APP_CONFIG_AICC_MIME_PROFILE = "azconfig.io/mime-profiles/ai-chat-completion";

/**
* Constant for tracing load balancing usage.
*/
public static final String LOAD_BALANCING_FEATURE = "LB";

/**
* Constant for tracing AI configuration usage.
*/
public static final String AI_CONFIGURATION_FEATURE = "AI";

/**
* Constant for tracing AI Chat Completion configuration usage.
*/
public static final String AI_CHAT_COMPLETION_FEATURE = "AICC";
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.azure.data.appconfiguration.models.SettingSelector;
import com.azure.data.appconfiguration.models.SnapshotComposition;
import com.azure.spring.cloud.appconfiguration.config.implementation.configuration.WatchedConfigurationSettings;
import com.azure.spring.cloud.appconfiguration.config.implementation.http.policy.TracingInfo;

import io.netty.handler.codec.http.HttpResponseStatus;

Expand All @@ -42,6 +43,8 @@ class AppConfigurationReplicaClient {

private final ConfigurationClient client;

private final TracingInfo tracingInfo;

private Instant backoffEndTime;

private int failedAttempts;
Expand All @@ -51,11 +54,14 @@ class AppConfigurationReplicaClient {
* @param endpoint client endpoint
* @param originClient origin client identifier
* @param client Configuration Client to App Configuration store
* @param tracingInfo tracing info for this client
*/
AppConfigurationReplicaClient(String endpoint, String originClient, ConfigurationClient client) {
AppConfigurationReplicaClient(String endpoint, String originClient, ConfigurationClient client,
TracingInfo tracingInfo) {
this.endpoint = endpoint;
this.originClient = originClient;
this.client = client;
this.tracingInfo = tracingInfo;
this.backoffEndTime = Instant.now().minusMillis(INITIAL_BACKOFF_OFFSET_MS);
this.failedAttempts = 0;
}
Expand Down Expand Up @@ -97,6 +103,13 @@ String getOriginClient() {
return originClient;
}

/**
* @return tracingInfo for this client
*/
TracingInfo getTracingInfo() {
return tracingInfo;
}

/**
* Gets the Configuration Setting for the given config store that match the Setting Selector criteria. Follows
* retry-after-ms header.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ List<AppConfigurationReplicaClient> buildClients(ConfigStore configStore) {

if (configStore.isLoadBalancingEnabled()) {
Collections.shuffle(clients);
for (AppConfigurationReplicaClient client : clients) {
client.getTracingInfo().setUsesLoadBalancing();
}
}

return clients;
Expand Down Expand Up @@ -254,7 +257,7 @@ private AppConfigurationReplicaClient modifyAndBuildClient(ConfigurationClientBu
if (clientCustomizer != null) {
clientCustomizer.customize(builder, endpoint);
}
return new AppConfigurationReplicaClient(endpoint, originEndpoint, builder.buildClient());
return new AppConfigurationReplicaClient(endpoint, originEndpoint, builder.buildClient(), tracingInfo);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ final class AppConfigurationSnapshotPropertySource extends AppConfigurationAppli
* @throws InvalidConfigurationPropertyValueException thrown if fails to parse Json content type
*/
public void initProperties(List<String> trim, Context context) throws InvalidConfigurationPropertyValueException {
replicaClient.getTracingInfo().resetAiConfigurationTracing();
processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName, context), null, trim);

WatchedConfigurationSettings featureFlags = new WatchedConfigurationSettings(null, featureFlagsList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.implementation;

import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.PUSH_REFRESH;

import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
Expand All @@ -24,6 +22,7 @@
import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.SettingSelector;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.PUSH_REFRESH;
import com.azure.spring.cloud.appconfiguration.config.implementation.configuration.WatchedConfigurationSettings;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationKeyValueSelector;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
Expand Down Expand Up @@ -325,6 +324,10 @@ private AppConfigurationReplicaClient handleReplicaFailure(AppConfigurationRepli
AppConfigurationReplicaClient nextClient = replicaClientFactory.getNextActiveClient(resource.getEndpoint(),
false);

if (nextClient != null) {
nextClient.getTracingInfo().setFailoverRequest();
}

String scenario = resource.isRefresh() ? "refresh" : "startup";
String nextAction = nextClient != null ? "Trying next replica." : "No more replicas available.";
logger.warn("Azure App Configuration replica " + client.getEndpoint()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,34 @@ private void updateTelemetry(FeatureFlagConfigurationSetting featureFlag) {
for (FeatureFlagFilter filter : featureFlag.getClientFilters()) {
tracing.updateFeatureFilterTelemetry(filter.getName());
}

// Track telemetry and seed usage from the feature flag value
try {
JsonNode node = CASE_INSENSITIVE_MAPPER.readTree(featureFlag.getValue());

// Check for telemetry enabled
JsonNode telemetryNode = node.get(TELEMETRY);
if (telemetryNode != null && !telemetryNode.isEmpty()) {
JsonNode enabledNode = telemetryNode.get("enabled");
if (enabledNode != null && enabledNode.asBoolean()) {
tracing.setUsesTelemetry();
}
}

// Check for allocation seed
JsonNode allocationNode = node.get("allocation");
if (allocationNode != null && allocationNode.has("seed")) {
tracing.setUsesSeed();
}

// Track max variants
JsonNode variantsNode = node.get("variants");
if (variantsNode != null && variantsNode.isArray()) {
tracing.updateMaxVariants(variantsNode.size());
}
} catch (JsonProcessingException e) {
LOGGER.warn("Error parsing feature flag telemetry for key: {}", featureFlag.getKey(), e);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;

final class JsonConfigurationParser {
public final class JsonConfigurationParser {

private static final ObjectMapper MAPPER = JsonMapper.builder().enable(JsonReadFeature.ALLOW_JAVA_COMMENTS).build();

static boolean isJsonContentType(String contentType) {
public static boolean isJsonContentType(String contentType) {
if (!StringUtils.hasText(contentType)) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,37 @@ public enum RequestTracingConstants {
/**
* Constant for number of replicas, not including origin configured.
*/
REPLICA_COUNT("ReplicaCount");
REPLICA_COUNT("ReplicaCount"),

/**
* Constant for tracing the max variants count.
*/
MAX_VARIANTS_KEY("MaxVariants"),

/**
* Constant for tracing feature flag features.
*/
FF_FEATURES_KEY("FFFeatures"),

/**
* Constant for tracing general features.
*/
FEATURES_KEY("Features"),

/**
* Constant for tracing the feature management version.
*/
FM_SPRING_VER_KEY("FMSpVer"),

/**
* Constant for tracing if this is a failover request.
*/
FAILOVER_TAG("Failover"),

/**
* Constant for tracing the filter key.
*/
FILTER_KEY("Filter");

private final String text;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public class FeatureFlagTracing {

private static final String FILTER_TYPE_DELIMITER = "+";

private static final String FEATURE_FLAG_USES_TELEMETRY_TAG = "Telemetry";

private static final String FEATURE_FLAG_USES_SEED_TAG = "Seed";

private static final List<String> PERCENTAGE_FILTER_NAMES = Arrays.asList("Percentage", "Microsoft.Percentage",
"PercentageFilter", "Microsoft.PercentageFilter");

Expand All @@ -34,6 +38,12 @@ public class FeatureFlagTracing {

private Boolean usesTargetingFilter = false;

private Boolean usesTelemetry = false;

private Boolean usesSeed = false;

private Integer maxVariants = null;

boolean usesAnyFilter() {
return usesCustomFilter || usesPercentageFilter || usesTimeWindowFilter || usesTargetingFilter;
}
Expand All @@ -43,6 +53,9 @@ public void resetFeatureFilterTelemetry() {
usesPercentageFilter = false;
usesTimeWindowFilter = false;
usesTargetingFilter = false;
usesTelemetry = false;
usesSeed = false;
maxVariants = null;
}

public void updateFeatureFilterTelemetry(String filterName) {
Expand All @@ -57,6 +70,56 @@ public void updateFeatureFilterTelemetry(String filterName) {
}
}

/**
* Marks telemetry as used.
*/
public void setUsesTelemetry() {
this.usesTelemetry = true;
}

/**
* Marks seed as used.
*/
public void setUsesSeed() {
this.usesSeed = true;
}

/**
* Updates max variants if the new size is larger.
* @param size size of variants
*/
public void updateMaxVariants(int size) {
if (maxVariants == null || size > maxVariants) {
maxVariants = size;
}
}

/**
* Gets the max variants value.
* @return max variants or null if not set
*/
public Integer getMaxVariants() {
return maxVariants;
}

/**
* Creates the FFFeatures string for correlation context.
* @return FFFeatures string with telemetry and seed tags
*/
public String createFFFeaturesString() {
StringBuilder sb = new StringBuilder();
if (Boolean.TRUE.equals(usesSeed)) {
sb.append(FEATURE_FLAG_USES_SEED_TAG);
}
if (Boolean.TRUE.equals(usesTelemetry)) {
if (sb.length() > 0) {
sb.append(FILTER_TYPE_DELIMITER);
}
sb.append(FEATURE_FLAG_USES_TELEMETRY_TAG);
}
return sb.toString();
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Expand Down
Loading
Loading