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 @@ -41,6 +41,8 @@ enum ResolveReason {
RESOLVE_REASON_TARGETING_KEY_ERROR = 5;
// Unknown error occurred during the resolve
RESOLVE_REASON_ERROR = 6;
// The flag could not be resolved because a targeting rule was not recognized
RESOLVE_REASON_UNRECOGNIZED_TARGETING_RULE = 7;
}

enum SdkId {
Expand Down
77 changes: 70 additions & 7 deletions confidence-proto/src/main/proto/confidence/telemetry.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ option java_package = "com.spotify.telemetry.v1";
option java_multiple_files = true;
option java_outer_classname = "TelemetryProto";

import "google/protobuf/descriptor.proto";

message MetricAnnotation {
string name = 1;
string unit = 2;
}

extend google.protobuf.EnumValueOptions {
MetricAnnotation metric = 50641;
}

enum Platform {
PLATFORM_UNSPECIFIED = 0;
PLATFORM_JAVA = 1;
Expand Down Expand Up @@ -32,22 +43,74 @@ message LibraryTraces {

message Trace {
TraceId id = 1;
// only used for timed events.
optional uint64 millisecond_duration = 2;

oneof traceData {
uint64 millisecond_duration = 2 [deprecated = true];
RequestTrace request_trace = 3;
CountTrace count_trace = 4;
EvaluationTrace evaluation_trace = 5;
}

message CountTrace {}

message RequestTrace {
uint64 millisecond_duration = 1;
Status status = 2;

enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_SUCCESS = 1;
STATUS_ERROR = 2;
STATUS_TIMEOUT = 3;
STATUS_CACHED = 4;
}
}

message EvaluationTrace {
EvaluationReason reason = 1;
EvaluationErrorCode error_code = 2;

enum EvaluationReason {
EVALUATION_REASON_UNSPECIFIED = 0;
EVALUATION_REASON_TARGETING_MATCH = 1;
EVALUATION_REASON_DEFAULT = 2;
EVALUATION_REASON_STALE = 3;
EVALUATION_REASON_DISABLED = 4;
EVALUATION_REASON_CACHED = 5;
EVALUATION_REASON_STATIC = 6;
EVALUATION_REASON_SPLIT = 7;
EVALUATION_REASON_ERROR = 8;
}

enum EvaluationErrorCode {
EVALUATION_ERROR_CODE_UNSPECIFIED = 0;
EVALUATION_ERROR_CODE_PROVIDER_NOT_READY = 1;
EVALUATION_ERROR_CODE_FLAG_NOT_FOUND = 2;
EVALUATION_ERROR_CODE_PARSE_ERROR = 3;
EVALUATION_ERROR_CODE_TYPE_MISMATCH = 4;
EVALUATION_ERROR_CODE_TARGETING_KEY_MISSING = 5;
EVALUATION_ERROR_CODE_INVALID_CONTEXT = 6;
EVALUATION_ERROR_CODE_PROVIDER_FATAL = 7;
EVALUATION_ERROR_CODE_GENERAL = 8;

// Confidence-specific errors
EVALUATION_ERROR_CODE_UNRECOGNIZED_TARGETING_RULE = 100;
EVALUATION_ERROR_CODE_MATERIALIZATION_NOT_SUPPORTED = 101;
}
}
}

enum Library {
LIBRARY_UNSPECIFIED = 0;
LIBRARY_UNKNOWN = 0;
LIBRARY_CONFIDENCE = 1;
LIBRARY_OPEN_FEATURE = 2;
LIBRARY_REACT = 3;
}

enum TraceId {
TRACE_ID_UNSPECIFIED = 0;
TRACE_ID_RESOLVE_LATENCY = 1;
TRACE_ID_STALE_FLAG = 2;
TRACE_ID_FLAG_TYPE_MISMATCH = 3;
TRACE_ID_WITH_CONTEXT = 4;
TRACE_ID_RESOLVE_LATENCY = 1 [(metric) = {name: "resolve_latency", unit: "ms"}];
TRACE_ID_STALE_FLAG = 2 [deprecated = true, (metric) = {name: "stale_flag"}];
TRACE_ID_FLAG_EVALUATION = 3 [(metric) = {name: "flag_evaluation"}];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,15 @@ public ProviderEvaluation<Value> getObjectEvaluation(
}

final ResolvedFlag resolvedFlag = resolveFlagResponse.getResolvedFlags(0);
final String reason = resolvedFlag.getReason().toString();

if (resolvedFlag.getVariant().isEmpty()) {
log.debug(
String.format(
"The server returned no assignment for the flag '%s'. Typically, this happens "
+ "if no configured rules matches the given evaluation context.",
flagPath.getFlag()));
confidence.client().trackEvaluation(reason, null);
return ProviderEvaluation.<Value>builder()
.value(defaultValue)
.reason(
Expand All @@ -216,10 +218,10 @@ public ProviderEvaluation<Value> getObjectEvaluation(
value = defaultValue;
}

// regular resolve was successful
confidence.client().trackEvaluation(reason, null);
return ProviderEvaluation.<Value>builder()
.value(value)
.reason(resolvedFlag.getReason().toString())
.reason(reason)
.variant(resolvedFlag.getVariant())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ void beforeEach() {
final FlagResolverClientImpl flagResolver =
new FlagResolverClientImpl(
new GrpcFlagResolver("fake-secret", channel, telemetryInterceptor, 1_000), telemetry);
final Confidence confidence = Confidence.create(fakeEventSender, flagResolver, "clientKey");
final Confidence confidence =
Confidence.create(fakeEventSender, flagResolver, "clientKey", telemetry);
final FeatureProvider featureProvider = new ConfidenceFeatureProvider(confidence);

openFeatureAPI = OpenFeatureAPI.getInstance();
Expand Down Expand Up @@ -525,10 +526,21 @@ public void resolvesContainHeaderWithTelemetryData() {
assertThat(libraryTracesList).hasSize(1);
final LibraryTraces traces = libraryTracesList.get(0);
assertThat(traces.getLibrary()).isEqualTo(LibraryTraces.Library.LIBRARY_OPEN_FEATURE);
assertThat(traces.getTracesList()).hasSize(1);
final LibraryTraces.Trace trace = traces.getTraces(0);
assertThat(trace.getId()).isEqualTo(LibraryTraces.TraceId.TRACE_ID_RESOLVE_LATENCY);
assertThat(trace.getMillisecondDuration()).isNotNegative();
assertThat(traces.getTracesList()).hasSize(2);
final LibraryTraces.Trace latencyTrace = traces.getTraces(0);
assertThat(latencyTrace.getId()).isEqualTo(LibraryTraces.TraceId.TRACE_ID_RESOLVE_LATENCY);
assertThat(latencyTrace.getRequestTrace().getMillisecondDuration()).isNotNegative();
assertThat(latencyTrace.getRequestTrace().getStatus())
.isEqualTo(LibraryTraces.Trace.RequestTrace.Status.STATUS_SUCCESS);
final LibraryTraces.Trace evaluationTrace = traces.getTraces(1);
assertThat(evaluationTrace.getId()).isEqualTo(LibraryTraces.TraceId.TRACE_ID_FLAG_EVALUATION);
assertThat(evaluationTrace.getEvaluationTrace().getReason())
.isEqualTo(
LibraryTraces.Trace.EvaluationTrace.EvaluationReason.EVALUATION_REASON_TARGETING_MATCH);
assertThat(evaluationTrace.getEvaluationTrace().getErrorCode())
.isEqualTo(
LibraryTraces.Trace.EvaluationTrace.EvaluationErrorCode
.EVALUATION_ERROR_CODE_UNSPECIFIED);

client.getIntegerDetails("flag.prop-Y", 1000, SAMPLE_CONTEXT);
}
Expand Down
54 changes: 47 additions & 7 deletions sdk-java/src/main/java/com/spotify/confidence/Confidence.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ public <T> FlagEvaluation<T> getEvaluation(String key, T defaultValue) {
try {
return getEvaluationFuture(key, defaultValue).get();
} catch (Exception e) {
return new FlagEvaluation<>(
defaultValue, "", "ERROR", ErrorType.INTERNAL_ERROR, e.getMessage());
final FlagEvaluation<T> evaluation =
new FlagEvaluation<>(defaultValue, "", "ERROR", ErrorType.INTERNAL_ERROR, e.getMessage());
client().trackEvaluation(evaluation.getReason(), evaluation.getErrorType().orElse(null));
return evaluation;
}
}

Expand Down Expand Up @@ -196,10 +198,27 @@ public <T> CompletableFuture<FlagEvaluation<T>> getEvaluationFuture(String key,
}
}
})
.exceptionally(handleFlagEvaluationError(defaultValue));
.thenApply(
evaluation -> {
client()
.trackEvaluation(
evaluation.getReason(), evaluation.getErrorType().orElse(null));
return evaluation;
})
.exceptionally(
e -> {
final FlagEvaluation<T> evaluation =
handleFlagEvaluationError(defaultValue).apply(e);
client()
.trackEvaluation(
evaluation.getReason(), evaluation.getErrorType().orElse(null));
return evaluation;
});

} catch (Exception e) {
return CompletableFuture.completedFuture(handleFlagEvaluationError(defaultValue).apply(e));
final FlagEvaluation<T> evaluation = handleFlagEvaluationError(defaultValue).apply(e);
client().trackEvaluation(evaluation.getReason(), evaluation.getErrorType().orElse(null));
return CompletableFuture.completedFuture(evaluation);
}
}

Expand Down Expand Up @@ -236,11 +255,20 @@ static Confidence create(
EventSenderEngine eventSenderEngine,
FlagResolverClient flagResolverClient,
String clientSecret) {
return create(eventSenderEngine, flagResolverClient, clientSecret, null);
}

@VisibleForTesting
static Confidence create(
EventSenderEngine eventSenderEngine,
FlagResolverClient flagResolverClient,
String clientSecret,
@Nullable Telemetry telemetry) {
final Closer closer = Closer.create();
closer.register(eventSenderEngine);
closer.register(flagResolverClient);
return new RootInstance(
new ClientDelegate(closer, flagResolverClient, eventSenderEngine, clientSecret));
new ClientDelegate(closer, flagResolverClient, eventSenderEngine, clientSecret, telemetry));
}

public static Confidence.Builder builder(String clientSecret) {
Expand All @@ -252,16 +280,19 @@ static class ClientDelegate implements FlagResolverClient, EventSenderEngine {
private final FlagResolverClient flagResolverClient;
private final EventSenderEngine eventSenderEngine;
private String clientSecret;
@Nullable private final Telemetry telemetry;

ClientDelegate(
Closeable closeable,
FlagResolverClient flagResolverClient,
EventSenderEngine eventSenderEngine,
String clientSecret) {
String clientSecret,
@Nullable Telemetry telemetry) {
this.closeable = closeable;
this.flagResolverClient = flagResolverClient;
this.eventSenderEngine = eventSenderEngine;
this.clientSecret = clientSecret;
this.telemetry = telemetry;
}

@Override
Expand All @@ -281,6 +312,14 @@ public CompletableFuture<ResolveFlagsResponse> resolveFlags(
return flagResolverClient.resolveFlags(flag, context);
}

void trackEvaluation(String resolveReason, @Nullable ErrorType errorType) {
if (telemetry != null) {
telemetry.appendEvaluation(
Telemetry.mapReason(resolveReason, errorType),
Telemetry.mapErrorCode(resolveReason, errorType));
}
}

@Override
public void close() throws IOException {
closeable.close();
Expand Down Expand Up @@ -417,7 +456,8 @@ public Confidence build() {
closer.register(flagResolverClient);
closer.register(eventSenderEngine);
return new RootInstance(
new ClientDelegate(closer, flagResolverClient, eventSenderEngine, clientSecret));
new ClientDelegate(
closer, flagResolverClient, eventSenderEngine, clientSecret, telemetry));
}

private void registerChannelForShutdown(ManagedChannel channel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public List<String> getCallHistory() {
// Mock implementation of ClientDelegate
private static class MockClientDelegate extends ClientDelegate {
private MockClientDelegate() {
super(null, null, null, "");
super(null, null, null, "", null);
}

@Override
Expand Down
4 changes: 3 additions & 1 deletion sdk-java/src/main/java/com/spotify/confidence/ErrorType.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ public enum ErrorType {
INVALID_VALUE_PATH,
INVALID_CONTEXT,
INTERNAL_ERROR,
NETWORK_ERROR
NETWORK_ERROR,
PARSE_ERROR,
PROVIDER_NOT_READY
}
Loading
Loading