Skip to content

Commit 00a8a90

Browse files
authored
fix: change how we log resolve tester hints (#177)
* fix: change how we log resolve tester hints * fix: add test for ResolveHint logging
1 parent b1a6353 commit 00a8a90

File tree

4 files changed

+90
-17
lines changed

4 files changed

+90
-17
lines changed

sdk-java/pom.xml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@
1010
<artifactId>sdk-java</artifactId>
1111

1212
<dependencies>
13-
<dependency>
14-
<groupId>com.google.protobuf</groupId>
15-
<artifactId>protobuf-java-util</artifactId>
16-
<version>${protobuf.version}</version>
17-
<scope>test</scope>
18-
</dependency>
1913
<dependency>
2014
<groupId>io.grpc</groupId>
2115
<artifactId>grpc-protobuf</artifactId>
@@ -27,6 +21,11 @@
2721
</exclusion>
2822
</exclusions>
2923
</dependency>
24+
<dependency>
25+
<groupId>com.google.protobuf</groupId>
26+
<artifactId>protobuf-java-util</artifactId>
27+
<version>${protobuf.version}</version>
28+
</dependency>
3029
<dependency>
3130
<groupId>dev.failsafe</groupId>
3231
<artifactId>failsafe</artifactId>
@@ -37,6 +36,12 @@
3736
<artifactId>proto-google-common-protos</artifactId>
3837
<version>${common.protos.version}</version>
3938
</dependency>
39+
<dependency>
40+
<groupId>ch.qos.logback</groupId>
41+
<artifactId>logback-classic</artifactId>
42+
<version>1.4.14</version>
43+
<scope>test</scope>
44+
</dependency>
4045
</dependencies>
4146

4247
<build>

sdk-java/src/main/java/com/spotify/confidence/Confidence.java

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@
99
import com.google.common.collect.ImmutableMap;
1010
import com.google.common.collect.Maps;
1111
import com.google.common.io.Closer;
12+
import com.google.protobuf.InvalidProtocolBufferException;
13+
import com.google.protobuf.util.JsonFormat;
1214
import com.spotify.confidence.ConfidenceUtils.FlagPath;
1315
import com.spotify.confidence.Exceptions.IllegalValuePath;
1416
import com.spotify.confidence.Exceptions.IllegalValueType;
1517
import com.spotify.confidence.Exceptions.IncompatibleValueType;
1618
import com.spotify.confidence.Exceptions.ValueNotFound;
1719
import com.spotify.confidence.shaded.flags.resolver.v1.ResolveFlagsResponse;
1820
import com.spotify.confidence.shaded.flags.resolver.v1.ResolvedFlag;
21+
import com.spotify.internal.v1.ResolveTesterLogging;
1922
import io.grpc.ManagedChannel;
2023
import io.grpc.ManagedChannelBuilder;
2124
import io.grpc.StatusRuntimeException;
2225
import java.io.Closeable;
2326
import java.io.IOException;
24-
import java.net.URLEncoder;
25-
import java.nio.charset.StandardCharsets;
2627
import java.time.Duration;
2728
import java.time.Instant;
29+
import java.util.Base64;
2830
import java.util.Map;
2931
import java.util.Optional;
3032
import java.util.Set;
@@ -41,6 +43,7 @@ public abstract class Confidence implements FlagEvaluator, EventSender, Closeabl
4143

4244
protected Map<String, ConfidenceValue> context = Maps.newHashMap();
4345
private static final Logger log = org.slf4j.LoggerFactory.getLogger(Confidence.class);
46+
private static final JsonFormat.Printer jsonPrinter = JsonFormat.printer();
4447

4548
protected Confidence() {
4649
// Protected constructor to allow subclassing
@@ -137,15 +140,7 @@ public <T> FlagEvaluation<T> getEvaluation(String key, T defaultValue) {
137140
}
138141

139142
final ResolvedFlag resolvedFlag = response.getResolvedFlags(0);
140-
final String clientKey = client().clientSecret;
141-
final String flag = resolvedFlag.getFlag();
142-
final String context = URLEncoder.encode(getContext().toString(), StandardCharsets.UTF_8);
143-
final String logMessage =
144-
String.format(
145-
"See resolves for '%s' in Confidence: "
146-
+ "https://app.confidence.spotify.com/flags/resolver-test?client-key=%s&flag=flags/%s&context=%s",
147-
flag, clientKey, flag, context);
148-
log.debug(logMessage);
143+
logResolveTesterHint(resolvedFlag);
149144
if (!requestFlagName.equals(resolvedFlag.getFlag())) {
150145
final String errorMessage =
151146
String.format(
@@ -201,6 +196,30 @@ public <T> FlagEvaluation<T> getEvaluation(String key, T defaultValue) {
201196
}
202197
}
203198

199+
@VisibleForTesting
200+
public void logResolveTesterHint(ResolvedFlag resolvedFlag) {
201+
final String clientKey = client().clientSecret;
202+
final String flag = resolvedFlag.getFlag();
203+
try {
204+
final ResolveTesterLogging resolveTesterLogging =
205+
ResolveTesterLogging.newBuilder()
206+
.setClientKey(clientKey)
207+
.setFlag(flag)
208+
.setContext(getContext().toProto())
209+
.build();
210+
final String base64 =
211+
Base64.getEncoder().encodeToString(jsonPrinter.print(resolveTesterLogging).getBytes());
212+
final String logMessage =
213+
String.format(
214+
"Check your flag evaluation for '%s' by copy pasting the payload to the Resolve tester '%s'",
215+
flag, base64);
216+
log.debug(logMessage);
217+
} catch (InvalidProtocolBufferException e) {
218+
log.warn("Failed to produce correct resolve tester content", e);
219+
// warn and ignore is enough
220+
}
221+
}
222+
204223
CompletableFuture<ResolveFlagsResponse> resolveFlags(String flagName) {
205224
return client().resolveFlags(flagName, getContext());
206225
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
syntax = "proto3";
2+
3+
import "google/protobuf/struct.proto";
4+
5+
package confidence.internal.v1;
6+
option java_package = "com.spotify.internal.v1";
7+
option java_multiple_files = true;
8+
option java_outer_classname = "SdkLogging";
9+
10+
message ResolveTesterLogging {
11+
string client_key = 1;
12+
string flag = 2;
13+
google.protobuf.Value context = 3;
14+
}

sdk-java/src/test/java/com/spotify/confidence/ConfidenceTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
import static org.junit.jupiter.api.Assertions.*;
44

5+
import ch.qos.logback.classic.Level;
6+
import ch.qos.logback.classic.Logger;
7+
import ch.qos.logback.classic.spi.ILoggingEvent;
8+
import ch.qos.logback.core.read.ListAppender;
59
import com.google.protobuf.Value;
610
import com.spotify.confidence.ConfidenceValue.Struct;
711
import com.spotify.confidence.shaded.flags.resolver.v1.ResolveFlagsResponse;
@@ -15,18 +19,33 @@
1519
import java.util.List;
1620
import java.util.Map;
1721
import java.util.concurrent.CompletableFuture;
22+
import org.junit.jupiter.api.AfterEach;
1823
import org.junit.jupiter.api.BeforeEach;
1924
import org.junit.jupiter.api.Test;
25+
import org.slf4j.LoggerFactory;
2026

2127
final class ConfidenceTest {
2228
private final FakeEventSenderEngine fakeEngine = new FakeEventSenderEngine(new FakeClock());
2329
private final ResolverClientTestUtils.FakeFlagResolverClient fakeFlagResolverClient =
2430
new ResolverClientTestUtils.FakeFlagResolverClient();
2531
private static Confidence confidence;
32+
private ListAppender<ILoggingEvent> listAppender;
33+
private Logger confidenceLogger;
2634

2735
@BeforeEach
2836
void beforeEach() {
2937
confidence = Confidence.create(fakeEngine, fakeFlagResolverClient, "clientKey");
38+
confidenceLogger = (Logger) LoggerFactory.getLogger(Confidence.class);
39+
40+
listAppender = new ListAppender<>();
41+
listAppender.start();
42+
// Add the appender to the logger
43+
confidenceLogger.addAppender(listAppender);
44+
}
45+
46+
@AfterEach
47+
void afterEach() {
48+
confidenceLogger.detachAppender(listAppender);
3049
}
3150

3251
@Test
@@ -259,6 +278,22 @@ void internalError() {
259278
evaluation.getErrorMessage().get().startsWith("Crashing while performing network call"));
260279
}
261280

281+
@Test
282+
void shouldLogResolverHint() {
283+
confidence
284+
.withContext(Map.of("my_context_value", ConfidenceValue.of(42)))
285+
.logResolveTesterHint(ResolvedFlag.newBuilder().setFlag("FlagName").build());
286+
final List<ILoggingEvent> loggingEvents = listAppender.list;
287+
assertTrue(loggingEvents.size() > 0, "No log message was captured.");
288+
final ILoggingEvent lastLogEvent = loggingEvents.get(loggingEvents.size() - 1);
289+
assertEquals(Level.DEBUG, lastLogEvent.getLevel()); // Or whatever level you expect
290+
assertEquals(
291+
"Check your flag evaluation for 'FlagName' by copy pasting the payload to the Resolve tester "
292+
+ "'ewogICJjbGllbnRLZXkiOiAiY2xpZW50S2V5IiwKICAiZmxhZyI6ICJGbGFnTmFtZSIsCiAgImN"
293+
+ "vbnRleHQiOiB7CiAgICAibXlfY29udGV4dF92YWx1ZSI6IDQyLjAKICB9Cn0='",
294+
lastLogEvent.getFormattedMessage());
295+
}
296+
262297
public static class FailingFlagResolverClient implements FlagResolverClient {
263298

264299
@Override

0 commit comments

Comments
 (0)