diff --git a/api/src/main/java/io/grpc/GlobalInterceptors.java b/api/src/main/java/io/grpc/GlobalInterceptors.java index 44d1592b61a4..e5fd86170f08 100644 --- a/api/src/main/java/io/grpc/GlobalInterceptors.java +++ b/api/src/main/java/io/grpc/GlobalInterceptors.java @@ -73,24 +73,19 @@ static synchronized void setInterceptorsTracers( isGlobalInterceptorsTracersSet = true; } - /** - * Returns the list of global {@link ClientInterceptor}. If not set, this returns am empty list. - */ + /** Returns the list of global {@link ClientInterceptor}. If not set, this returns null. */ static synchronized List getClientInterceptors() { isGlobalInterceptorsTracersGet = true; return clientInterceptors; } - /** Returns list of global {@link ServerInterceptor}. If not set, this returns an empty list. */ + /** Returns list of global {@link ServerInterceptor}. If not set, this returns null. */ static synchronized List getServerInterceptors() { isGlobalInterceptorsTracersGet = true; return serverInterceptors; } - /** - * Returns list of global {@link ServerStreamTracer.Factory}. If not set, this returns an empty - * list. - */ + /** Returns list of global {@link ServerStreamTracer.Factory}. If not set, this returns null. */ static synchronized List getServerStreamTracerFactories() { isGlobalInterceptorsTracersGet = true; return serverStreamTracerFactories; diff --git a/gcp-observability/build.gradle b/gcp-observability/build.gradle index 0da808f66ad2..c4de44ed44f4 100644 --- a/gcp-observability/build.gradle +++ b/gcp-observability/build.gradle @@ -30,6 +30,7 @@ dependencies { project(':grpc-alts'), project(':grpc-census'), ("com.google.cloud:google-cloud-logging:${cloudLoggingVersion}"), + libraries.opencensus.contrib.grpc.metrics, libraries.opencensus.exporter.stats.stackdriver, libraries.opencensus.exporter.trace.stackdriver, libraries.animalsniffer.annotations, // Prefer our version @@ -41,7 +42,8 @@ dependencies { runtimeOnly libraries.opencensus.impl - testImplementation project(':grpc-testing'), + testImplementation project(':grpc-context').sourceSets.test.output, + project(':grpc-testing'), project(':grpc-testing-proto'), project(':grpc-netty-shaded') testImplementation (libraries.guava.testlib) { diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java index e65f4d8ef017..4bb9ffcb0c10 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java @@ -19,8 +19,14 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; +import io.grpc.ClientInterceptor; import io.grpc.ExperimentalApi; +import io.grpc.InternalGlobalInterceptors; import io.grpc.ManagedChannelProvider.ProviderNotFoundException; +import io.grpc.ServerInterceptor; +import io.grpc.ServerStreamTracer; +import io.grpc.census.InternalCensusStatsAccessor; +import io.grpc.census.InternalCensusTracingAccessor; import io.grpc.gcp.observability.interceptors.ConfigFilterHelper; import io.grpc.gcp.observability.interceptors.InternalLoggingChannelInterceptor; import io.grpc.gcp.observability.interceptors.InternalLoggingServerInterceptor; @@ -28,13 +34,30 @@ import io.grpc.gcp.observability.logging.GcpLogSink; import io.grpc.gcp.observability.logging.Sink; import io.grpc.internal.TimeProvider; +import io.opencensus.contrib.grpc.metrics.RpcViews; +import io.opencensus.exporter.stats.stackdriver.StackdriverStatsConfiguration; +import io.opencensus.exporter.stats.stackdriver.StackdriverStatsExporter; +import io.opencensus.exporter.trace.stackdriver.StackdriverTraceConfiguration; +import io.opencensus.exporter.trace.stackdriver.StackdriverTraceExporter; +import io.opencensus.trace.Tracing; +import io.opencensus.trace.config.TraceConfig; import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; /** The main class for gRPC Google Cloud Platform Observability features. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8869") public final class GcpObservability implements AutoCloseable { + private static final Logger logger = Logger.getLogger(GcpObservability.class.getName()); private static GcpObservability instance = null; private final Sink sink; + private final ObservabilityConfig config; + private final ArrayList clientInterceptors = new ArrayList<>(); + private final ArrayList serverInterceptors = new ArrayList<>(); + private final ArrayList tracerFactories = new ArrayList<>(); + private boolean metricsEnabled; + private boolean tracesEnabled; /** * Initialize grpc-observability. @@ -48,20 +71,33 @@ public static synchronized GcpObservability grpcInit() throws IOException { Sink sink = new GcpLogSink(observabilityConfig.getDestinationProjectId(), globalLoggingTags.getLocationTags(), globalLoggingTags.getCustomTags(), observabilityConfig.getFlushMessageCount()); + // TODO(dnvindhya): Cleanup code for LoggingChannelProvider and LoggingServerProvider + // once ChannelBuilder and ServerBuilder are used LogHelper helper = new LogHelper(sink, TimeProvider.SYSTEM_TIME_PROVIDER); ConfigFilterHelper configFilterHelper = ConfigFilterHelper.factory(observabilityConfig); - instance = grpcInit(sink, + instance = grpcInit(sink, observabilityConfig, new InternalLoggingChannelInterceptor.FactoryImpl(helper, configFilterHelper), new InternalLoggingServerInterceptor.FactoryImpl(helper, configFilterHelper)); + instance.registerStackDriverExporter(observabilityConfig.getDestinationProjectId()); } return instance; } - @VisibleForTesting static GcpObservability grpcInit(Sink sink, + @VisibleForTesting + static GcpObservability grpcInit( + Sink sink, + ObservabilityConfig config, InternalLoggingChannelInterceptor.Factory channelInterceptorFactory, - InternalLoggingServerInterceptor.Factory serverInterceptorFactory) { + InternalLoggingServerInterceptor.Factory serverInterceptorFactory) + throws IOException { if (instance == null) { - instance = new GcpObservability(sink, channelInterceptorFactory, serverInterceptorFactory); + instance = + new GcpObservability(sink, config, channelInterceptorFactory, serverInterceptorFactory); + LogHelper logHelper = new LogHelper(sink, TimeProvider.SYSTEM_TIME_PROVIDER); + ConfigFilterHelper logFilterHelper = ConfigFilterHelper.factory(config); + instance.setProducer( + new InternalLoggingChannelInterceptor.FactoryImpl(logHelper, logFilterHelper), + new InternalLoggingServerInterceptor.FactoryImpl(logHelper, logFilterHelper)); } return instance; } @@ -73,6 +109,7 @@ public void close() { if (instance == null) { throw new IllegalStateException("GcpObservability already closed!"); } + unRegisterStackDriverExporter(); LoggingChannelProvider.shutdown(); LoggingServerProvider.shutdown(); sink.close(); @@ -80,10 +117,84 @@ public void close() { } } - private GcpObservability(Sink sink, + private void setProducer( + InternalLoggingChannelInterceptor.Factory channelInterceptorFactory, + InternalLoggingServerInterceptor.Factory serverInterceptorFactory) { + if (config.isEnableCloudLogging()) { + clientInterceptors.add(channelInterceptorFactory.create()); + serverInterceptors.add(serverInterceptorFactory.create()); + } + if (config.isEnableCloudMonitoring()) { + clientInterceptors.add( + InternalCensusStatsAccessor.getClientInterceptor(true, true, true, true)); + tracerFactories.add( + InternalCensusStatsAccessor.getServerStreamTracerFactory(true, true, true)); + } + if (config.isEnableCloudTracing()) { + clientInterceptors.add(InternalCensusTracingAccessor.getClientInterceptor()); + tracerFactories.add(InternalCensusTracingAccessor.getServerStreamTracerFactory()); + } + + InternalGlobalInterceptors.setInterceptorsTracers( + clientInterceptors, serverInterceptors, tracerFactories); + } + + private void registerStackDriverExporter(String projectId) throws IOException { + if (config.isEnableCloudMonitoring()) { + RpcViews.registerAllGrpcViews(); + StackdriverStatsConfiguration.Builder statsConfigurationBuilder = + StackdriverStatsConfiguration.builder(); + if (projectId != null) { + statsConfigurationBuilder.setProjectId(projectId); + } + StackdriverStatsExporter.createAndRegister(statsConfigurationBuilder.build()); + metricsEnabled = true; + } + + if (config.isEnableCloudTracing()) { + TraceConfig traceConfig = Tracing.getTraceConfig(); + traceConfig.updateActiveTraceParams( + traceConfig.getActiveTraceParams().toBuilder().setSampler(config.getSampler()).build()); + StackdriverTraceConfiguration.Builder traceConfigurationBuilder = + StackdriverTraceConfiguration.builder(); + if (projectId != null) { + traceConfigurationBuilder.setProjectId(projectId); + } + StackdriverTraceExporter.createAndRegister(traceConfigurationBuilder.build()); + tracesEnabled = true; + } + } + + private void unRegisterStackDriverExporter() { + if (metricsEnabled) { + try { + StackdriverStatsExporter.unregister(); + } catch (IllegalStateException e) { + logger.log( + Level.SEVERE, "Failed to unregister Stackdriver stats exporter, " + e.getMessage()); + } + metricsEnabled = false; + } + + if (tracesEnabled) { + try { + StackdriverTraceExporter.unregister(); + } catch (IllegalStateException e) { + logger.log( + Level.SEVERE, "Failed to unregister Stackdriver trace exporter, " + e.getMessage()); + } + tracesEnabled = false; + } + } + + private GcpObservability( + Sink sink, + ObservabilityConfig config, InternalLoggingChannelInterceptor.Factory channelInterceptorFactory, InternalLoggingServerInterceptor.Factory serverInterceptorFactory) { this.sink = checkNotNull(sink); + this.config = checkNotNull(config); + LoggingChannelProvider.init(checkNotNull(channelInterceptorFactory)); LoggingServerProvider.init(checkNotNull(serverInterceptorFactory)); } diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfig.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfig.java index 808a847695cc..a6abe589eed6 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfig.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfig.java @@ -18,6 +18,7 @@ import io.grpc.Internal; import io.grpc.observabilitylog.v1.GrpcLogRecord.EventType; +import io.opencensus.trace.Sampler; import java.util.List; @Internal @@ -43,6 +44,7 @@ public interface ObservabilityConfig { /** Get event types to log. */ List getEventTypes(); + /** Get sampler for TraceConfig - when Cloud Tracing is enabled. */ Sampler getSampler(); /** @@ -71,34 +73,4 @@ public LogFilter(String pattern, Integer headerBytes, Integer messageBytes) { this.messageBytes = messageBytes; } } - - /** Corresponds to a {@link io.opencensus.trace.Sampler} type. */ - enum SamplerType { - ALWAYS, - NEVER, - PROBABILISTIC; - } - - /** Represents a trace {@link io.opencensus.trace.Sampler} configuration. */ - class Sampler { - private SamplerType type; - private double probability; - - Sampler(double probability) { - this.probability = probability; - this.type = SamplerType.PROBABILISTIC; - } - - Sampler(SamplerType type) { - this.type = type; - } - - double getProbability() { - return probability; - } - - SamplerType getType() { - return type; - } - } } diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfigImpl.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfigImpl.java index a398a558162a..2f547ec5af7e 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfigImpl.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfigImpl.java @@ -23,6 +23,8 @@ import io.grpc.internal.JsonParser; import io.grpc.internal.JsonUtil; import io.grpc.observabilitylog.v1.GrpcLogRecord.EventType; +import io.opencensus.trace.Sampler; +import io.opencensus.trace.samplers.Samplers; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; @@ -35,6 +37,8 @@ final class ObservabilityConfigImpl implements ObservabilityConfig { private static final String CONFIG_ENV_VAR_NAME = "GRPC_CONFIG_OBSERVABILITY"; private static final String CONFIG_FILE_ENV_VAR_NAME = "GRPC_CONFIG_OBSERVABILITY_JSON"; + // Tolerance for floating-point comparisons. + private static final double EPSILON = 1e-6; private boolean enableCloudLogging = false; private boolean enableCloudMonitoring = false; @@ -100,19 +104,21 @@ private void parseConfig(Map config) { } this.eventTypes = eventTypesBuilder.build(); } - String sampler = JsonUtil.getString(config, "global_trace_sampler"); Double samplingRate = JsonUtil.getNumberAsDouble(config, "global_trace_sampling_rate"); - checkArgument( - sampler == null || samplingRate == null, - "only one of 'global_trace_sampler' or 'global_trace_sampling_rate' can be specified"); - if (sampler != null) { - this.sampler = new Sampler(SamplerType.valueOf(sampler.toUpperCase())); - } - if (samplingRate != null) { + if (samplingRate == null) { + this.sampler = Samplers.probabilitySampler(0.0); + } else { checkArgument( samplingRate >= 0.0 && samplingRate <= 1.0, - "'global_trace_sampling_rate' needs to be between 0.0 and 1.0"); - this.sampler = new Sampler(samplingRate); + "'global_trace_sampling_rate' needs to be between [0.0, 1.0]"); + // Using alwaysSample() instead of probabilitySampler() because according to + // {@link io.opencensus.trace.samplers.ProbabilitySampler#shouldSample} + // there is a (very) small chance of *not* sampling if probability = 1.00. + if (1 - samplingRate < EPSILON) { + this.sampler = Samplers.alwaysSample(); + } else { + this.sampler = Samplers.probabilitySampler(samplingRate); + } } } } diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java index 129a8271831b..d494b3c14f17 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java @@ -18,48 +18,185 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.InternalGlobalInterceptors; import io.grpc.ManagedChannelProvider; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; import io.grpc.ServerProvider; +import io.grpc.StaticTestingClassLoader; import io.grpc.gcp.observability.interceptors.InternalLoggingChannelInterceptor; import io.grpc.gcp.observability.interceptors.InternalLoggingServerInterceptor; import io.grpc.gcp.observability.logging.Sink; +import io.opencensus.trace.samplers.Samplers; +import java.io.IOException; +import java.util.regex.Pattern; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class GcpObservabilityTest { - + + private final StaticTestingClassLoader classLoader = + new StaticTestingClassLoader( + getClass().getClassLoader(), + Pattern.compile( + "io\\.grpc\\.InternalGlobalInterceptors|io\\.grpc\\.GlobalInterceptors|" + + "io\\.grpc\\.gcp\\.observability\\.[^.]+|" + + "io\\.grpc\\.gcp\\.observability\\.interceptors\\.[^.]+|" + + "io\\.grpc\\.gcp\\.observability\\.GcpObservabilityTest\\$.*")); + + @Test + public void initFinish() throws Exception { + Class runnable = + classLoader.loadClass(StaticTestingClassInitFinish.class.getName()); + ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); + } + + @Test + public void enableObservability() throws Exception { + Class runnable = + classLoader.loadClass(StaticTestingClassEnableObservability.class.getName()); + ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); + } + @Test - public void initFinish() { - ManagedChannelProvider prevChannelProvider = ManagedChannelProvider.provider(); - ServerProvider prevServerProvider = ServerProvider.provider(); - Sink sink = mock(Sink.class); - InternalLoggingChannelInterceptor.Factory channelInterceptorFactory = mock( - InternalLoggingChannelInterceptor.Factory.class); - InternalLoggingServerInterceptor.Factory serverInterceptorFactory = mock( - InternalLoggingServerInterceptor.Factory.class); - GcpObservability observability1; - try (GcpObservability observability = GcpObservability.grpcInit(sink, channelInterceptorFactory, - serverInterceptorFactory)) { - assertThat(ManagedChannelProvider.provider()).isInstanceOf(LoggingChannelProvider.class); - assertThat(ServerProvider.provider()).isInstanceOf(ServerProvider.class); - observability1 = GcpObservability.grpcInit(sink, channelInterceptorFactory, - serverInterceptorFactory); - assertThat(observability1).isSameInstanceAs(observability); + public void disableObservability() throws Exception { + Class runnable = + classLoader.loadClass(StaticTestingClassDisableObservability.class.getName()); + ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); + } + + // UsedReflectively + public static final class StaticTestingClassInitFinish implements Runnable { + + @Override + public void run() { + // TODO(dnvindhya) : Remove usage of Providers on cleaning up Logging*Provider + ManagedChannelProvider prevChannelProvider = ManagedChannelProvider.provider(); + ServerProvider prevServerProvider = ServerProvider.provider(); + Sink sink = mock(Sink.class); + ObservabilityConfig config = mock(ObservabilityConfig.class); + InternalLoggingChannelInterceptor.Factory channelInterceptorFactory = + mock(InternalLoggingChannelInterceptor.Factory.class); + InternalLoggingServerInterceptor.Factory serverInterceptorFactory = + mock(InternalLoggingServerInterceptor.Factory.class); + GcpObservability observability1; + try { + GcpObservability observability = + GcpObservability.grpcInit( + sink, config, channelInterceptorFactory, serverInterceptorFactory); + assertThat(ManagedChannelProvider.provider()).isInstanceOf(LoggingChannelProvider.class); + assertThat(ServerProvider.provider()).isInstanceOf(ServerProvider.class); + observability1 = + GcpObservability.grpcInit( + sink, config, channelInterceptorFactory, serverInterceptorFactory); + assertThat(observability1).isSameInstanceAs(observability); + observability.close(); + verify(sink).close(); + assertThat(ManagedChannelProvider.provider()).isSameInstanceAs(prevChannelProvider); + assertThat(ServerProvider.provider()).isSameInstanceAs(prevServerProvider); + try { + observability1.close(); + fail("should have failed for calling close() second time"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().contains("GcpObservability already closed!"); + } + } catch (IOException e) { + fail("Encountered exception: " + e); + } + } + } + + public static final class StaticTestingClassEnableObservability implements Runnable { + @Override + public void run() { + Sink sink = mock(Sink.class); + ObservabilityConfig config = mock(ObservabilityConfig.class); + when(config.isEnableCloudLogging()).thenReturn(true); + when(config.isEnableCloudMonitoring()).thenReturn(true); + when(config.isEnableCloudTracing()).thenReturn(true); + when(config.getSampler()).thenReturn(Samplers.neverSample()); + + ClientInterceptor clientInterceptor = + mock(ClientInterceptor.class, delegatesTo(new NoopClientInterceptor())); + InternalLoggingChannelInterceptor.Factory channelInterceptorFactory = + mock(InternalLoggingChannelInterceptor.Factory.class); + when(channelInterceptorFactory.create()).thenReturn(clientInterceptor); + + ServerInterceptor serverInterceptor = + mock(ServerInterceptor.class, delegatesTo(new NoopServerInterceptor())); + InternalLoggingServerInterceptor.Factory serverInterceptorFactory = + mock(InternalLoggingServerInterceptor.Factory.class); + when(serverInterceptorFactory.create()).thenReturn(serverInterceptor); + + try (GcpObservability unused = + GcpObservability.grpcInit( + sink, config, channelInterceptorFactory, serverInterceptorFactory)) { + assertThat(InternalGlobalInterceptors.getClientInterceptors()).hasSize(3); + assertThat(InternalGlobalInterceptors.getServerInterceptors()).hasSize(1); + assertThat(InternalGlobalInterceptors.getServerStreamTracerFactories()).hasSize(2); + } catch (Exception e) { + fail("Encountered exception: " + e); + } + } + } + + public static final class StaticTestingClassDisableObservability implements Runnable { + + @Override + public void run() { + Sink sink = mock(Sink.class); + ObservabilityConfig config = mock(ObservabilityConfig.class); + when(config.isEnableCloudLogging()).thenReturn(false); + when(config.isEnableCloudMonitoring()).thenReturn(false); + when(config.isEnableCloudTracing()).thenReturn(false); + when(config.getSampler()).thenReturn(Samplers.neverSample()); + + InternalLoggingChannelInterceptor.Factory channelInterceptorFactory = + mock(InternalLoggingChannelInterceptor.Factory.class); + InternalLoggingServerInterceptor.Factory serverInterceptorFactory = + mock(InternalLoggingServerInterceptor.Factory.class);; + + try (GcpObservability unused = + GcpObservability.grpcInit( + sink, config, channelInterceptorFactory, serverInterceptorFactory)) { + assertThat(InternalGlobalInterceptors.getClientInterceptors()).isEmpty(); + assertThat(InternalGlobalInterceptors.getServerInterceptors()).isEmpty(); + assertThat(InternalGlobalInterceptors.getServerStreamTracerFactories()).isEmpty(); + } catch (Exception e) { + fail("Encountered exception: " + e); + } + verify(sink).close(); } - verify(sink).close(); - assertThat(ManagedChannelProvider.provider()).isSameInstanceAs(prevChannelProvider); - assertThat(ServerProvider.provider()).isSameInstanceAs(prevServerProvider); - try { - observability1.close(); - fail("should have failed for calling close() second time"); - } catch (IllegalStateException e) { - assertThat(e).hasMessageThat().contains("GcpObservability already closed!"); + } + + private static class NoopClientInterceptor implements ClientInterceptor { + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return next.newCall(method, callOptions); + } + } + + private static class NoopServerInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + return next.startCall(call, headers); } } } diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/ObservabilityConfigImplTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/ObservabilityConfigImplTest.java index c7186786e375..f608dd5b25ac 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/ObservabilityConfigImplTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/ObservabilityConfigImplTest.java @@ -26,7 +26,8 @@ import com.google.common.collect.ImmutableList; import io.grpc.gcp.observability.ObservabilityConfig.LogFilter; import io.grpc.observabilitylog.v1.GrpcLogRecord.EventType; - +import io.opencensus.trace.Sampler; +import io.opencensus.trace.samplers.Samplers; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -77,35 +78,33 @@ public class ObservabilityConfigImplTest { + "}"; private static final String ENABLE_CLOUD_MONITORING_AND_TRACING = "{\n" - + " \"enable_cloud_monitoring\": true,\n" - + " \"enable_cloud_tracing\": true\n" - + "}"; + + " \"enable_cloud_monitoring\": true,\n" + + " \"enable_cloud_tracing\": true\n" + + "}"; private static final String GLOBAL_TRACING_ALWAYS_SAMPLER = "{\n" - + " \"enable_cloud_tracing\": true,\n" - + " \"global_trace_sampler\": \"always\"\n" - + "}"; + + " \"enable_cloud_tracing\": true,\n" + + " \"global_trace_sampling_rate\": 1.00\n" + + "}"; private static final String GLOBAL_TRACING_NEVER_SAMPLER = "{\n" - + " \"enable_cloud_tracing\": true,\n" - + " \"global_trace_sampler\": \"never\"\n" - + "}"; + + " \"enable_cloud_tracing\": true,\n" + + " \"global_trace_sampling_rate\": 0.00\n" + + "}"; private static final String GLOBAL_TRACING_PROBABILISTIC_SAMPLER = "{\n" - + " \"enable_cloud_tracing\": true,\n" - + " \"global_trace_sampling_rate\": 0.75\n" - + "}"; + + " \"enable_cloud_tracing\": true,\n" + + " \"global_trace_sampling_rate\": 0.75\n" + + "}"; - private static final String GLOBAL_TRACING_BOTH_SAMPLER_ERROR = "{\n" - + " \"enable_cloud_tracing\": true,\n" - + " \"global_trace_sampler\": \"never\",\n" - + " \"global_trace_sampling_rate\": 0.75\n" - + "}"; + private static final String GLOBAL_TRACING_DEFAULT_SAMPLER = "{\n" + + " \"enable_cloud_tracing\": true\n" + + "}"; private static final String GLOBAL_TRACING_BADPROBABILISTIC_SAMPLER = "{\n" - + " \"enable_cloud_tracing\": true,\n" - + " \"global_trace_sampling_rate\": -0.75\n" - + "}"; + + " \"enable_cloud_tracing\": true,\n" + + " \"global_trace_sampling_rate\": -0.75\n" + + "}"; ObservabilityConfigImpl observabilityConfig = new ObservabilityConfigImpl(); @@ -198,41 +197,36 @@ public void enableCloudMonitoringAndTracing() throws IOException { public void alwaysSampler() throws IOException { observabilityConfig.parse(GLOBAL_TRACING_ALWAYS_SAMPLER); assertTrue(observabilityConfig.isEnableCloudTracing()); - ObservabilityConfig.Sampler sampler = observabilityConfig.getSampler(); + Sampler sampler = observabilityConfig.getSampler(); assertThat(sampler).isNotNull(); - assertThat(sampler.getType()).isEqualTo(ObservabilityConfig.SamplerType.ALWAYS); + assertThat(sampler).isEqualTo(Samplers.alwaysSample()); } @Test public void neverSampler() throws IOException { observabilityConfig.parse(GLOBAL_TRACING_NEVER_SAMPLER); assertTrue(observabilityConfig.isEnableCloudTracing()); - ObservabilityConfig.Sampler sampler = observabilityConfig.getSampler(); + Sampler sampler = observabilityConfig.getSampler(); assertThat(sampler).isNotNull(); - assertThat(sampler.getType()).isEqualTo(ObservabilityConfig.SamplerType.NEVER); + assertThat(sampler).isEqualTo(Samplers.probabilitySampler(0.0)); } @Test public void probabilisticSampler() throws IOException { observabilityConfig.parse(GLOBAL_TRACING_PROBABILISTIC_SAMPLER); assertTrue(observabilityConfig.isEnableCloudTracing()); - ObservabilityConfig.Sampler sampler = observabilityConfig.getSampler(); + Sampler sampler = observabilityConfig.getSampler(); assertThat(sampler).isNotNull(); - assertThat(sampler.getType()).isEqualTo(ObservabilityConfig.SamplerType.PROBABILISTIC); - assertThat(sampler.getProbability()).isEqualTo(0.75); + assertThat(sampler).isEqualTo(Samplers.probabilitySampler(0.75)); } @Test - public void bothSamplerAndSamplingRate_error() throws IOException { - try { - observabilityConfig.parse(GLOBAL_TRACING_BOTH_SAMPLER_ERROR); - fail("exception expected!"); - } catch (IllegalArgumentException iae) { - assertThat(iae.getMessage()) - .isEqualTo( - "only one of 'global_trace_sampler' or 'global_trace_sampling_rate' can be" - + " specified"); - } + public void defaultSampler() throws IOException { + observabilityConfig.parse(GLOBAL_TRACING_DEFAULT_SAMPLER); + assertTrue(observabilityConfig.isEnableCloudTracing()); + Sampler sampler = observabilityConfig.getSampler(); + assertThat(sampler).isNotNull(); + assertThat(sampler).isEqualTo(Samplers.probabilitySampler(0.00)); } @Test @@ -242,7 +236,7 @@ public void badProbabilisticSampler_error() throws IOException { fail("exception expected!"); } catch (IllegalArgumentException iae) { assertThat(iae.getMessage()).isEqualTo( - "'global_trace_sampling_rate' needs to be between 0.0 and 1.0"); + "'global_trace_sampling_rate' needs to be between [0.0, 1.0]"); } } @@ -263,4 +257,4 @@ public void configFileLogFilters() throws Exception { assertThat(logFilters.get(1).headerBytes).isNull(); assertThat(logFilters.get(1).messageBytes).isNull(); } -} +} \ No newline at end of file