Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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,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.FEATURE_FLAG_CONTENT_TYPE;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
Expand All @@ -24,6 +22,7 @@
import com.azure.data.appconfiguration.models.SecretReferenceConfigurationSetting;
import com.azure.data.appconfiguration.models.SettingSelector;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.FEATURE_FLAG_CONTENT_TYPE;

/**
* Azure App Configuration PropertySource unique per Store Label(Profile) combo.
Expand Down Expand Up @@ -68,6 +67,8 @@ class AppConfigurationApplicationSettingPropertySource extends AppConfigurationP
@Override
public void initProperties(List<String> keyPrefixTrimValues, Context context) throws InvalidConfigurationPropertyValueException {

replicaClient.getTracingInfo().resetAiConfigurationTracing();

List<String> labels = Arrays.asList(labelFilters);
// Reverse labels so they have the right priority order.
Collections.reverse(labels);
Expand All @@ -89,6 +90,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,34 @@ 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 snapshot reference usage.
*/
public static final String SNAPSHOT_REFERENCE_TAG = "SnapshotRef";
Comment thread
mrm9084 marked this conversation as resolved.

/**
* 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 @@ -22,6 +22,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 @@ -43,6 +44,8 @@ class AppConfigurationReplicaClient {

private final ConfigurationClient client;

private final TracingInfo tracingInfo;

private Instant backoffEndTime;

private int failedAttempts;
Expand All @@ -52,11 +55,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 @@ -98,6 +104,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 @@ -176,6 +176,9 @@ List<AppConfigurationReplicaClient> buildClients(ConfigStore configStore) {

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

return clients;
Expand Down Expand Up @@ -213,7 +216,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);
}

private ConfigurationClientBuilder createBuilderInstance() {
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 @@ -325,6 +325,10 @@ private AppConfigurationReplicaClient handleReplicaFailure(AppConfigurationRepli
AppConfigurationReplicaClient nextClient = replicaClientFactory.getNextActiveClient(resource.getEndpoint(),
false);

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

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 @@ -203,6 +203,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(true);
}
}

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

// 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,58 @@ public void updateFeatureFilterTelemetry(String filterName) {
}
}

/**
* Sets whether telemetry is used.
* @param usesTelemetry whether telemetry is used
*/
public void setUsesTelemetry(Boolean usesTelemetry) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is all feature tracing being reset after each server call? This applies to any telemetry that is tracked on the key-value (or ff) level. App-level telemetry like "uses load balancing" or "uses key vault" doesn't change, so it doesn't need to be reset. But kv-level telemetry can change based on the value in AppConfig

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is FeatureManagement telemetry, not store wide.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, so this needs to be reset based on what feature flags are loaded by the application.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is reset via resetFeatureFilterTelemetry which if you follow the code path resets every time we load/refresh. Actually now that I think about it this might actually be a bug. To de-dup everything all stores use the same FeatureFlagClient object, and they all use that one tracing. If a customer uses multiple stores we will be 1 reporting everything to the wrong store, And then reset before we can send to this store.

I'll create an issue to track this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Actually this might not be an issue. Here is how it works. we have a class called AzureAppConfigDataLoader this method is called once per store we are loading keys/feature flags from. One of the first things it does is reset this telemetry.

We then send the feature flag telemetry if and only if refresh is enabled. As we don't make any requests after this otherwise.

We use to load feature flags first to solve this, but that broke when we added support for Snapshots if I remember correctly as snapshots have negative priority order for feature flags.

this.usesTelemetry = usesTelemetry;
}

/**
* Sets whether seed is used.
* @param usesSeed whether seed is used
*/
public void setUsesSeed(Boolean usesSeed) {
this.usesSeed = usesSeed;
}

/**
* 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