All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micrometer.tracing.test.SampleTestRunner Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micrometer.tracing.test;

import io.micrometer.common.util.StringUtils;
import io.micrometer.common.util.internal.logging.InternalLogger;
import io.micrometer.common.util.internal.logging.InternalLoggerFactory;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
import io.micrometer.observation.TestConfigAccessor;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.TracingObservationHandler;
import io.micrometer.tracing.test.reporter.BuildingBlocks;
import io.micrometer.tracing.test.reporter.inmemory.InMemoryBraveSetup;
import io.micrometer.tracing.test.reporter.inmemory.InMemoryOtelSetup;
import io.micrometer.tracing.test.reporter.wavefront.WavefrontBraveSetup;
import io.micrometer.tracing.test.reporter.wavefront.WavefrontOtelSetup;
import io.micrometer.tracing.test.reporter.zipkin.ZipkinBraveSetup;
import io.micrometer.tracing.test.reporter.zipkin.ZipkinOtelSetup;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import zipkin2.reporter.BytesMessageSender;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;

/**
 * Prepares the required tracing setup and reporters / exporters. The user needs to just
 * provide the code to test and that way all the combinations of tracers and exporters
 * will be automatically applied. It also sets up the {@link MeterRegistry} in such a way
 * that it consists all {@link TracingObservationHandler} injected into
 * {@link ObservationRegistry#observationConfig()}.
 * 

* When extending this class you can eagerly pass the {@link SampleRunnerConfig} by * calling this class' constructors. Different registry instances can be provided by * overriding {@link SampleTestRunner#createMeterRegistry()} and/or * {@link SampleTestRunner#createObservationRegistry()}. * * @author Marcin Grzejszczak * @since 1.0.0 */ public abstract class SampleTestRunner { private static final InternalLogger log = InternalLoggerFactory.getInstance(SampleTestRunner.class); private SampleRunnerConfig sampleRunnerConfig; private ObservationRegistry observationRegistry; private MeterRegistry meterRegistry; /** * Creates a new instance of the {@link SampleTestRunner}. * @param sampleRunnerConfig configuration for the SampleTestRunner */ public SampleTestRunner(SampleRunnerConfig sampleRunnerConfig) { this.sampleRunnerConfig = sampleRunnerConfig; } /** * Creates a new instance of the {@link SampleTestRunner} with a default * configuration. */ public SampleTestRunner() { this.sampleRunnerConfig = SampleRunnerConfig.builder().build(); } /** * Create a new instance of {@link MeterRegistry}. Each parameterized test run calls * this method to create a new instance. Override this method to use different * {@link MeterRegistry} implementation. * @return a new meter registry */ protected MeterRegistry createMeterRegistry() { return new SimpleMeterRegistry(); } /** * Returns the {@link MeterRegistry} instance used during each parameterized test run. * @return meter registry to be used in tests */ protected MeterRegistry getMeterRegistry() { Assertions.assertNotNull(this.meterRegistry, "MeterRegistry must be set either via constructor via overriding a method"); return this.meterRegistry; } /** * Create a new instance of {@link ObservationRegistry}. Override this method to use * different {@link ObservationRegistry} implementation. * @return a new observation registry */ protected ObservationRegistry createObservationRegistry() { ObservationRegistry registry = ObservationRegistry.create(); registry.observationConfig().observationHandler(new DefaultMeterObservationHandler(getMeterRegistry())); return registry; } /** * Returns the {@link ObservationRegistry} instance used during each parameterized * test run. * @return observation registry to be used in tests */ protected ObservationRegistry getObservationRegistry() { Assertions.assertNotNull(this.observationRegistry, "ObservationRegistry must be set either via constructor via overriding a method"); return this.observationRegistry; } /** * Override this to resolve the {@link SampleRunnerConfig} at runtime. If not * overridden * will return the passed {@link MeterRegistry} from the constructor. * @return sampler config to be used in tests */ protected SampleRunnerConfig getSampleRunnerConfig() { return this.sampleRunnerConfig; } @ParameterizedTest @EnumSource(TracingSetup.class) void run(TracingSetup tracingSetup) { tracingSetup.run(getSampleRunnerConfig(), getObservationRegistry(), getMeterRegistry(), this); } /** * Sets up registries before each test. */ @BeforeEach protected void setupRegistry() { this.meterRegistry = createMeterRegistry(); this.observationRegistry = createObservationRegistry(); } /** * Closes meter registry after each test. */ @AfterEach protected void closeMeterRegistry() { getMeterRegistry().close(); } private void printMetrics() { StringBuilder stringBuilder = new StringBuilder(); getMeterRegistry().forEachMeter(meter -> stringBuilder.append("\tMeter with name <") .append(meter.getId().getName()) .append(">") .append(" and type <") .append(meter.getId().getType()) .append(">") .append(" has the following measurements \n\t\t<") .append(meter.measure()) .append(">") .append(" \n\t\tand has the following tags <") .append(meter.getId().getTags()) .append(">\n")); log.info("Gathered the following metrics\n" + stringBuilder); } /** * Code that you want to measure and run. * @return your code with access to the current tracing and measuring infrastructure * @throws Exception any exception will be rethrown */ public abstract SampleTestRunnerConsumer yourCode() throws Exception; private SampleTestRunnerConsumer runWithMetricsPrinting() { return (bb, registry) -> { try { yourCode().accept(bb, registry); } catch (Exception e) { throw new RuntimeException(e); } finally { printMetrics(); } }; } /** * Functional interface to run user code. * * @since 1.0.0 */ @FunctionalInterface public interface SampleTestRunnerConsumer { /** * @param bb - building blocks * @param meterRegistry - meter registry * @throws Exception exception */ void accept(BuildingBlocks bb, MeterRegistry meterRegistry) throws Exception; } /** * Override this to customize the list of timer recording handlers. * @return timer recording handler customizing function */ @SuppressWarnings("rawtypes") public BiConsumer>> customizeObservationHandlers() { return (tracer, ObservationHandlers) -> { }; } /** * Override this to just run a subset of tracing setups to run. * @return array of tracing setups to run */ public TracingSetup[] getTracingSetup() { return TracingSetup.values(); } /** * Tracing setups. Contains various combinations of tracers and reporters. */ public enum TracingSetup { /** * In memory with OTel Tracer. */ IN_MEMORY_OTEL { @Override void run(SampleRunnerConfig sampleRunnerConfig, ObservationRegistry observationRegistry, MeterRegistry meterRegistry, SampleTestRunner sampleTestRunner) { checkTracingSetupAssumptions(IN_MEMORY_OTEL, sampleTestRunner.getTracingSetup()); scopeObservationHandlers(observationRegistry, () -> { InMemoryOtelSetup setup = InMemoryOtelSetup.builder() .observationHandlerCustomizer(sampleTestRunner.customizeObservationHandlers()) .register(observationRegistry); InMemoryOtelSetup.run(setup, __ -> runTraced(sampleRunnerConfig, IN_MEMORY_OTEL, setup.getBuildingBlocks(), observationRegistry, meterRegistry, sampleTestRunner.runWithMetricsPrinting())); }); } @Override void printTracingLink(SampleRunnerConfig sampleRunnerConfig, String traceId) { log.info("In memory tracing used. All spans should have trace id <{}>", traceId); } }, /** * In memory with Brave Tracer. */ IN_MEMORY_BRAVE { @Override void run(SampleRunnerConfig sampleRunnerConfig, ObservationRegistry observationRegistry, MeterRegistry meterRegistry, SampleTestRunner sampleTestRunner) { checkTracingSetupAssumptions(IN_MEMORY_BRAVE, sampleTestRunner.getTracingSetup()); scopeObservationHandlers(observationRegistry, () -> { InMemoryBraveSetup setup = InMemoryBraveSetup.builder() .observationHandlerCustomizer(sampleTestRunner.customizeObservationHandlers()) .register(observationRegistry); InMemoryBraveSetup.run(setup, __ -> runTraced(sampleRunnerConfig, IN_MEMORY_BRAVE, setup.getBuildingBlocks(), observationRegistry, meterRegistry, sampleTestRunner.runWithMetricsPrinting())); }); } @Override void printTracingLink(SampleRunnerConfig sampleRunnerConfig, String traceId) { log.info("In memory tracing used. All spans should have trace id <{}>", traceId); } }, /** * Zipkin Exporter with OTel Tracer. */ ZIPKIN_OTEL { @Override void run(SampleRunnerConfig sampleRunnerConfig, ObservationRegistry observationRegistry, MeterRegistry meterRegistry, SampleTestRunner sampleTestRunner) { checkTracingSetupAssumptions(ZIPKIN_OTEL, sampleTestRunner.getTracingSetup()); scopeObservationHandlers(observationRegistry, () -> { ZipkinOtelSetup setup = ZipkinOtelSetup.builder() .observationHandlerCustomizer(sampleTestRunner.customizeObservationHandlers()) .zipkinUrl(sampleRunnerConfig.zipkinUrl) .register(observationRegistry); checkZipkinAssumptions(setup.getBuildingBlocks().getSender()); ZipkinOtelSetup.run(setup, __ -> runTraced(sampleRunnerConfig, ZIPKIN_OTEL, setup.getBuildingBlocks(), observationRegistry, meterRegistry, sampleTestRunner.runWithMetricsPrinting())); }); } @Override void printTracingLink(SampleRunnerConfig sampleRunnerConfig, String traceId) { log.info("Below you can find the link to the trace in Zipkin with id <{}>", traceId); log.info("{}/zipkin/traces/{}", sampleRunnerConfig.zipkinUrl, traceId); } }, /** * Zipkin Reporter with Brave Tracer. */ ZIPKIN_BRAVE { @Override void run(SampleRunnerConfig sampleRunnerConfig, ObservationRegistry observationRegistry, MeterRegistry meterRegistry, SampleTestRunner sampleTestRunner) { checkTracingSetupAssumptions(ZIPKIN_BRAVE, sampleTestRunner.getTracingSetup()); scopeObservationHandlers(observationRegistry, () -> { ZipkinBraveSetup setup = ZipkinBraveSetup.builder() .observationHandlerCustomizer(sampleTestRunner.customizeObservationHandlers()) .zipkinUrl(sampleRunnerConfig.zipkinUrl) .register(observationRegistry); checkZipkinAssumptions(setup.getBuildingBlocks().getSender()); ZipkinBraveSetup.run(setup, __ -> runTraced(sampleRunnerConfig, ZIPKIN_BRAVE, setup.getBuildingBlocks(), observationRegistry, meterRegistry, sampleTestRunner.runWithMetricsPrinting())); }); } @Override void printTracingLink(SampleRunnerConfig sampleRunnerConfig, String traceId) { log.info("Below you can find the link to the trace in Zipkin with id <{}>", traceId); log.info("{}/zipkin/traces/{}", sampleRunnerConfig.zipkinUrl, traceId); } }, /** * Wavefront Exporter with OTel Tracer. */ WAVEFRONT_OTEL { @Override void run(SampleRunnerConfig sampleRunnerConfig, ObservationRegistry observationRegistry, MeterRegistry meterRegistry, SampleTestRunner sampleTestRunner) { checkTracingSetupAssumptions(WAVEFRONT_OTEL, sampleTestRunner.getTracingSetup()); checkWavefrontAssumptions(sampleRunnerConfig); scopeObservationHandlers(observationRegistry, () -> { WavefrontOtelSetup setup = WavefrontOtelSetup .builder(sampleRunnerConfig.wavefrontServerUrl, sampleRunnerConfig.wavefrontToken) .applicationName(sampleRunnerConfig.wavefrontApplicationName) .serviceName(sampleRunnerConfig.wavefrontServiceName) .source(sampleRunnerConfig.wavefrontSource) .observationHandlerCustomizer(sampleTestRunner.customizeObservationHandlers()) .register(observationRegistry, meterRegistry); WavefrontOtelSetup.run(setup, __ -> runTraced(sampleRunnerConfig, WAVEFRONT_OTEL, setup.getBuildingBlocks(), observationRegistry, meterRegistry, sampleTestRunner.runWithMetricsPrinting())); }); } @Override void printTracingLink(SampleRunnerConfig sampleRunnerConfig, String traceId) { log.info("Below you can find the link to the trace in Wavefront with id <{}>", traceId); String wavefrontUrl = sampleRunnerConfig.wavefrontServerUrl.endsWith("/") ? sampleRunnerConfig.wavefrontServerUrl.substring(0, sampleRunnerConfig.wavefrontServerUrl.length() - 1) : sampleRunnerConfig.wavefrontServerUrl; log.info("{}/tracing/search?sortBy=MOST_RECENT&traceID={}", wavefrontUrl, traceId); } }, /** * Wavefront Exporter with Brave Tracer. */ WAVEFRONT_BRAVE { @Override void run(SampleRunnerConfig sampleRunnerConfig, ObservationRegistry observationRegistry, MeterRegistry meterRegistry, SampleTestRunner sampleTestRunner) { checkTracingSetupAssumptions(WAVEFRONT_BRAVE, sampleTestRunner.getTracingSetup()); checkWavefrontAssumptions(sampleRunnerConfig); scopeObservationHandlers(observationRegistry, () -> { WavefrontBraveSetup setup = WavefrontBraveSetup .builder(sampleRunnerConfig.wavefrontServerUrl, sampleRunnerConfig.wavefrontToken) .applicationName(sampleRunnerConfig.wavefrontApplicationName) .serviceName(sampleRunnerConfig.wavefrontServiceName) .source(sampleRunnerConfig.wavefrontSource) .observationHandlerCustomizer(sampleTestRunner.customizeObservationHandlers()) .register(meterRegistry, observationRegistry); WavefrontBraveSetup.run(setup, __ -> runTraced(sampleRunnerConfig, WAVEFRONT_BRAVE, setup.getBuildingBlocks(), observationRegistry, meterRegistry, sampleTestRunner.runWithMetricsPrinting())); }); } @Override void printTracingLink(SampleRunnerConfig sampleRunnerConfig, String traceId) { log.info("Below you can find the link to the trace in Wavefront with id <{}>", traceId); String wavefrontUrl = sampleRunnerConfig.wavefrontServerUrl.endsWith("/") ? sampleRunnerConfig.wavefrontServerUrl.substring(0, sampleRunnerConfig.wavefrontServerUrl.length() - 1) : sampleRunnerConfig.wavefrontServerUrl; log.info("{}/tracing/search?sortBy=MOST_RECENT&traceID={}", wavefrontUrl, traceId); } }; private static void scopeObservationHandlers(ObservationRegistry observationRegistry, Runnable runnable) { ObservationConfig observationConfig = observationRegistry.observationConfig(); List> originalHandlers = new ArrayList<>( TestConfigAccessor.getHandlers(observationConfig)); try { runnable.run(); } finally { TestConfigAccessor.clearHandlers(observationConfig); originalHandlers.forEach(observationConfig::observationHandler); } } private static void runTraced(SampleRunnerConfig sampleRunnerConfig, TracingSetup tracingSetup, BuildingBlocks bb, ObservationRegistry observationRegistry, MeterRegistry meterRegistry, SampleTestRunnerConsumer runnable) { Observation observation = Observation.start(tracingSetup.name().toLowerCase(Locale.ROOT), observationRegistry); String traceId = ""; try (Observation.Scope ws = observation.openScope()) { Tracer tracer = bb.getTracer(); traceId = tracer.currentSpan().context().traceId(); runnable.accept(bb, meterRegistry); } catch (Exception ex) { throw new RuntimeException(ex); } finally { tracingSetup.printTracingLink(sampleRunnerConfig, traceId); observation.stop(); } } private static void checkTracingSetupAssumptions(TracingSetup tracingSetup, TracingSetup[] chosenSetups) { Assumptions.assumeTrue(Arrays.asList(chosenSetups).contains(tracingSetup), tracingSetup.name() + " not found in the list of tracing setups to run " + Arrays.toString(chosenSetups)); } private static void checkZipkinAssumptions(BytesMessageSender sender) { try { sender.send(Collections.emptyList()); } catch (Exception e) { Assumptions.abort("There was a problem with connecting to Zipkin. Will NOT run any tests"); } } private static void checkWavefrontAssumptions(SampleRunnerConfig sampleRunnerConfig) { Assumptions.assumeTrue(StringUtils.isNotBlank(sampleRunnerConfig.wavefrontServerUrl), "To run tests against Tanzu Observability by Wavefront you need to set the Wavefront server url"); Assumptions.assumeTrue(StringUtils.isNotBlank(sampleRunnerConfig.wavefrontToken), "To run tests against Tanzu Observability by Wavefront you need to set the Wavefront token"); } abstract void run(SampleRunnerConfig sampleRunnerConfig, ObservationRegistry observationRegistry, MeterRegistry meterRegistry, SampleTestRunner sampleTestRunner); abstract void printTracingLink(SampleRunnerConfig sampleRunnerConfig, String traceId); } /** * {@link SampleTestRunner} configuration. */ public static class SampleRunnerConfig { private String wavefrontToken; private String wavefrontServerUrl; String zipkinUrl; private String wavefrontApplicationName; private String wavefrontServiceName; private String wavefrontSource; SampleRunnerConfig(String wavefrontToken, String wavefrontServerUrl, String wavefrontApplicationName, String wavefrontServiceName, String wavefrontSource, String zipkinUrl) { this.wavefrontToken = wavefrontToken; this.wavefrontServerUrl = wavefrontServerUrl != null ? wavefrontServerUrl : "https://vmware.wavefront.com"; this.wavefrontApplicationName = wavefrontApplicationName != null ? wavefrontApplicationName : "test-application"; this.wavefrontServiceName = wavefrontServiceName != null ? wavefrontServiceName : "test-service"; this.wavefrontSource = wavefrontSource != null ? wavefrontSource : "test-source"; this.zipkinUrl = zipkinUrl != null ? zipkinUrl : "http://localhost:9411"; } /** * @return {@link SampleTestRunner} builder */ public static Builder builder() { return new Builder(); } /** * Builder for {@link SampleRunnerConfig}. */ public static class Builder { private String wavefrontToken; private String wavefrontUrl; private String wavefrontApplicationName; private String wavefrontServiceName; private String wavefrontSource; private String zipkinUrl; /** * Token required to connect to Tanzu Observability by Wavefront. * @param wavefrontToken wavefront token * @return this */ public Builder wavefrontToken(String wavefrontToken) { this.wavefrontToken = wavefrontToken; return this; } /** * URL of your Tanzu Observability by Wavefront installation. * @param wavefrontUrl wavefront URL. * @return this */ public Builder wavefrontUrl(String wavefrontUrl) { this.wavefrontUrl = wavefrontUrl; return this; } /** * URL of your Zipkin installation. * @param zipkinUrl zipkin URL * @return this */ public Builder zipkinUrl(String zipkinUrl) { this.zipkinUrl = zipkinUrl; return this; } /** * Name of the application grouping in Tanzu Observability by Wavefront. * @param wavefrontApplicationName wavefront application name * @return this */ public Builder wavefrontApplicationName(String wavefrontApplicationName) { this.wavefrontApplicationName = wavefrontApplicationName; return this; } /** * Name of this service in Tanzu Observability by Wavefront. * @param wavefrontServiceName wavefront service name * @return this */ public Builder wavefrontServiceName(String wavefrontServiceName) { this.wavefrontServiceName = wavefrontServiceName; return this; } /** * Name of the source to be presented in Tanzu Observability by Wavefront. * @param wavefrontSource wavefront source * @return this */ public Builder wavefrontSource(String wavefrontSource) { this.wavefrontSource = wavefrontSource; return this; } /** * Builds the configuration. * @return built configuration */ public SampleRunnerConfig build() { return new SampleRunnerConfig(this.wavefrontToken, this.wavefrontUrl, this.wavefrontApplicationName, this.wavefrontServiceName, this.wavefrontSource, this.zipkinUrl); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy