From efa4f80fd9de558aabf0430fb4ee02cbdd69ad75 Mon Sep 17 00:00:00 2001 From: cleverchuk Date: Tue, 27 Oct 2026 12:39:08 -0400 Subject: [PATCH 1/2] Internal - Move sampling configs to sampler component config --- .../extensions/SolarwindsAgentListener.java | 14 +- .../extensions/config/HttpSettingsReader.java | 39 ++- .../config/HttpSettingsReaderDelegate.java | 13 +- .../HttpSamplerComponentProvider.java | 142 +++++++++ .../HttpSettingsReaderDelegateTest.java | 27 ++ .../config/HttpSettingsReaderTest.java | 29 +- .../HttpSamplerComponentProviderTest.java | 192 +++++++++++ custom/src/test/resources/sdk-config.yaml | 5 +- libs/lambda/build.gradle.kts | 3 + .../JsonSamplerComponentProvider.java | 50 +++ .../extensions/LambdaAgentListener.java | 11 +- .../JsonSamplerComponentProviderTest.java | 80 +++++ .../joboe/sampling/SettingsManager.java | 36 ++- .../joboe/sampling/SettingsManagerTest.java | 46 +++ .../opentelemetry/extensions/SharedNames.java | 4 + .../config/parser/yaml/TracingModeParser.java | 54 ---- .../yaml/TransactionSettingsConfigParser.java | 178 ----------- .../parser/yaml/TriggerTraceParser.java | 35 -- .../yaml/UrlSampleRateConfigParser.java | 128 -------- .../AbstractSamplerComponentProvider.java | 300 ++++++++++++++++++ .../provider/SamplerComponentProvider.java | 45 --- .../SharedConfigCustomizerProvider.java | 14 - .../parser/yaml/TracingModeParserTest.java | 60 ---- .../TransactionSettingsConfigParserTest.java | 89 ------ .../parser/yaml/TriggerTraceParserTest.java | 65 ---- .../yaml/UrlSampleRateConfigParserTest.java | 89 ------ .../AbstractSamplerComponentProviderTest.java | 260 +++++++++++++++ .../SamplerComponentProviderTest.java | 30 -- .../SharedConfigCustomizerProviderTest.java | 11 +- ...sdk-config-bad-transaction-settings-0.yaml | 43 --- ...sdk-config-bad-transaction-settings-1.yaml | 47 --- .../shared/src/test/resources/sdk-config.yaml | 27 +- smoke-tests/sdk-config.yaml | 11 +- 33 files changed, 1252 insertions(+), 925 deletions(-) create mode 100644 custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/HttpSamplerComponentProvider.java create mode 100644 custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/HttpSamplerComponentProviderTest.java create mode 100644 libs/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/JsonSamplerComponentProvider.java create mode 100644 libs/lambda/src/test/java/com/solarwinds/opentelemetry/extensions/JsonSamplerComponentProviderTest.java delete mode 100644 libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TracingModeParser.java delete mode 100644 libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TransactionSettingsConfigParser.java delete mode 100644 libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TriggerTraceParser.java delete mode 100644 libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/UrlSampleRateConfigParser.java create mode 100644 libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProvider.java delete mode 100644 libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SamplerComponentProvider.java delete mode 100644 libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TracingModeParserTest.java delete mode 100644 libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TransactionSettingsConfigParserTest.java delete mode 100644 libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TriggerTraceParserTest.java delete mode 100644 libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/UrlSampleRateConfigParserTest.java create mode 100644 libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProviderTest.java delete mode 100644 libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SamplerComponentProviderTest.java delete mode 100644 libs/shared/src/test/resources/sdk-config-bad-transaction-settings-0.yaml delete mode 100644 libs/shared/src/test/resources/sdk-config-bad-transaction-settings-1.yaml diff --git a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsAgentListener.java b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsAgentListener.java index 343e6987..27055c10 100644 --- a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsAgentListener.java +++ b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsAgentListener.java @@ -61,6 +61,7 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetr "Successfully submitted SolarwindsAPM OpenTelemetry extensions initialization tasks"); } else { autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk().shutdown(); + SettingsManager.reset(); } } @@ -90,11 +91,14 @@ private void executeStartupTasks() { + " ip addresses: " + ipAddresses); - CountDownLatch settingsLatch = - SettingsManager.initialize( - new HttpSettingsFetcher( - new HttpSettingsReader(new HttpSettingsReaderDelegate()), 60), - SamplingConfigProvider.getSamplingConfiguration()); + if (!SettingsManager.isInitialized()) { + SettingsManager.initialize( + new HttpSettingsFetcher( + HttpSettingsReader.forSystemConfig(new HttpSettingsReaderDelegate()), 60), + SamplingConfigProvider.getSamplingConfiguration()); + } + + CountDownLatch settingsLatch = SettingsManager.getSettingsAvailableLatch(); logger.debug("Initialized HostUtils"); ProfilerSetting profilerSetting = diff --git a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReader.java b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReader.java index 884e19bb..5a084a49 100644 --- a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReader.java +++ b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReader.java @@ -23,6 +23,7 @@ import com.solarwinds.joboe.logging.Logger; import com.solarwinds.joboe.logging.LoggerFactory; import com.solarwinds.joboe.sampling.Settings; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,20 +33,44 @@ public class HttpSettingsReader implements SettingsReader { private static final Logger logger = LoggerFactory.getLogger(); + private static final String DEFAULT_COLLECTOR = "apm.collector.na-01.cloud.solarwinds.com"; + + private static final String DEFAULT_SERVICE_KEY = ":unknown-java"; + private final HttpSettingsReaderDelegate delegate; - public HttpSettingsReader(HttpSettingsReaderDelegate delegate) { + private final Supplier collector; + + private final Supplier serviceKey; + + public static HttpSettingsReader forSystemConfig(HttpSettingsReaderDelegate delegate) { + return new HttpSettingsReader( + delegate, + () -> ConfigManager.getConfigOptional(ConfigProperty.AGENT_COLLECTOR, DEFAULT_COLLECTOR), + () -> + ConfigManager.getConfigOptional(ConfigProperty.AGENT_SERVICE_KEY, DEFAULT_SERVICE_KEY)); + } + + public static HttpSettingsReader forFileConfig( + HttpSettingsReaderDelegate delegate, String collector, String serviceKey) { + return new HttpSettingsReader( + delegate, () -> collector != null ? collector : DEFAULT_COLLECTOR, () -> serviceKey); + } + + private HttpSettingsReader( + HttpSettingsReaderDelegate delegate, + Supplier collector, + Supplier serviceKey) { + this.delegate = delegate; + this.collector = collector; + this.serviceKey = serviceKey; } @Override public Settings getSettings() { - String collectorUrl = - constructSettingsEndpoint( - ConfigManager.getConfigOptional( - ConfigProperty.AGENT_COLLECTOR, "apm.collector.na-01.cloud.solarwinds.com")); - String serviceKey = - ConfigManager.getConfigOptional(ConfigProperty.AGENT_SERVICE_KEY, ":unknown-java"); + String collectorUrl = constructSettingsEndpoint(collector.get()); + String serviceKey = this.serviceKey.get(); String serviceName = ServiceKeyUtils.getServiceName(serviceKey); String apiToken = ServiceKeyUtils.getApiKey(serviceKey); diff --git a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderDelegate.java b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderDelegate.java index c87f79d2..d06d13ad 100644 --- a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderDelegate.java +++ b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderDelegate.java @@ -34,6 +34,7 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; public class HttpSettingsReaderDelegate { @@ -41,6 +42,16 @@ public class HttpSettingsReaderDelegate { private static final Gson gson = new GsonBuilder().create(); + private final Supplier proxyConfig; + + public HttpSettingsReaderDelegate() { + this.proxyConfig = () -> (ProxyConfig) ConfigManager.getConfig(ConfigProperty.AGENT_PROXY); + } + + public HttpSettingsReaderDelegate(ProxyConfig proxyConfig) { + this.proxyConfig = () -> proxyConfig; + } + public Settings fetchSettings(String url, String authorizationHeader) { AtomicReference settings = new AtomicReference<>(); InstrumentationUtil.suppressInstrumentation( @@ -97,7 +108,7 @@ private String getErrorMessage(HttpURLConnection connection) { HttpURLConnection getHttpUrlConnection(URL url, String authorizationHeader) throws IOException { HttpURLConnection connection; - ProxyConfig proxyConfig = (ProxyConfig) ConfigManager.getConfig(ConfigProperty.AGENT_PROXY); + ProxyConfig proxyConfig = this.proxyConfig.get(); if (proxyConfig != null) { Proxy proxy = new Proxy( diff --git a/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/HttpSamplerComponentProvider.java b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/HttpSamplerComponentProvider.java new file mode 100644 index 00000000..5fa2f012 --- /dev/null +++ b/custom/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/HttpSamplerComponentProvider.java @@ -0,0 +1,142 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 + * + * http://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 com.solarwinds.opentelemetry.extensions.config.provider; + +import com.google.auto.service.AutoService; +import com.solarwinds.joboe.config.InvalidConfigException; +import com.solarwinds.joboe.config.ProxyConfig; +import com.solarwinds.joboe.config.ServiceKeyUtils; +import com.solarwinds.joboe.sampling.SamplingConfiguration; +import com.solarwinds.joboe.sampling.SettingsFetcher; +import com.solarwinds.opentelemetry.extensions.SharedNames; +import com.solarwinds.opentelemetry.extensions.config.HttpSettingsFetcher; +import com.solarwinds.opentelemetry.extensions.config.HttpSettingsReader; +import com.solarwinds.opentelemetry.extensions.config.HttpSettingsReaderDelegate; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import java.util.function.Function; + +@AutoService(ComponentProvider.class) +public class HttpSamplerComponentProvider extends AbstractSamplerComponentProvider { + + private static final String COLLECTOR = "collector"; + + private static final String SERVICE_KEY = "service_key"; + + private static final String CONTEXT_TTL = "context_ttl"; + + private static final String CONTEXT_MAX_EVENTS = "context_max_events"; + + private static final String CONTEXT_MAX_BACKTRACES = "context_max_backtraces"; + + private static final String PROXY = "proxy"; + + private static final String PROXY_HOST = "host"; + + private static final String PROXY_PORT = "port"; + + private static final long POLLING_INTERVAL_SECONDS = 60; + + private final Function delegateFactory; + + public HttpSamplerComponentProvider() { + this(HttpSettingsReaderDelegate::new); + } + + HttpSamplerComponentProvider(Function delegateFactory) { + this.delegateFactory = delegateFactory; + } + + @Override + public String getName() { + return SharedNames.HTTP_SAMPLER_NAME; + } + + @Override + protected void parseConfig( + DeclarativeConfigProperties properties, + SamplingConfiguration.SamplingConfigurationBuilder builder) + throws InvalidConfigException { + + Integer ttl = parsePositiveInt(properties, CONTEXT_TTL); + if (ttl != null) { + builder.ttl(ttl); + } + + Integer maxEvents = parsePositiveInt(properties, CONTEXT_MAX_EVENTS); + if (maxEvents != null) { + builder.maxEvents(maxEvents); + } + + Integer maxBacktraces = parsePositiveInt(properties, CONTEXT_MAX_BACKTRACES); + if (maxBacktraces != null) { + builder.maxBacktraces(maxBacktraces); + } + } + + @Override + protected SettingsFetcher createSettingsFetcher(DeclarativeConfigProperties properties) + throws InvalidConfigException { + + String collector = parseNonEmpty(properties, COLLECTOR); + String serviceKey = parseNonEmpty(properties, SERVICE_KEY); + if (serviceKey == null) { + throw new InvalidConfigException("Missing required sampler " + SERVICE_KEY + " config"); + } + + serviceKey = ServiceKeyUtils.transformServiceKey(serviceKey); + return new HttpSettingsFetcher( + HttpSettingsReader.forFileConfig( + delegateFactory.apply(parseProxy(properties)), collector, serviceKey), + POLLING_INTERVAL_SECONDS); + } + + private static Integer parsePositiveInt(DeclarativeConfigProperties properties, String key) + throws InvalidConfigException { + Integer value = properties.getInt(key); + if (value != null && value < 1) { + throw new InvalidConfigException( + "Invalid sampler " + key + " config, value must be a positive integer"); + } + return value; + } + + private static String parseNonEmpty(DeclarativeConfigProperties properties, String key) + throws InvalidConfigException { + String value = properties.getString(key); + if (value != null && value.trim().isEmpty()) { + throw new InvalidConfigException("Invalid sampler " + key + " config, value cannot be empty"); + } + return value; + } + + private ProxyConfig parseProxy(DeclarativeConfigProperties properties) + throws InvalidConfigException { + DeclarativeConfigProperties proxy = properties.getStructured(PROXY); + if (proxy == null) { + return null; + } + + String host = proxy.getString(PROXY_HOST); + Integer port = proxy.getInt(PROXY_PORT); + if (host == null || host.isEmpty() || port == null || port < 1 || port > 65535) { + throw new InvalidConfigException( + "Invalid sampler proxy config, both proxy.host and a valid proxy.port are required"); + } + return new ProxyConfig(host, port, null, null); + } +} diff --git a/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderDelegateTest.java b/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderDelegateTest.java index 0d50de16..4730d2eb 100644 --- a/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderDelegateTest.java +++ b/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderDelegateTest.java @@ -37,11 +37,14 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.URL; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; @@ -224,4 +227,28 @@ void testGetHttpUrlConnectionConfigurationProxy() throws IOException, InvalidCon verify(mockConnection).setConnectTimeout(10000); verify(mockConnection).setReadTimeout(10000); } + + @Test + void testGetHttpUrlConnectionUsesConfigureProxy() throws IOException { + HttpSettingsReaderDelegate samplerProxied = + new HttpSettingsReaderDelegate(new ProxyConfig("sampler.example.com", 2000, null, null)); + when(mockUrl.openConnection(any())).thenReturn(mockConnection); + + samplerProxied.getHttpUrlConnection(mockUrl, TEST_AUTH_HEADER); + ArgumentCaptor proxyCaptor = ArgumentCaptor.forClass(Proxy.class); + verify(mockUrl).openConnection(proxyCaptor.capture()); + + InetSocketAddress address = (InetSocketAddress) proxyCaptor.getValue().address(); + assertEquals("sampler.example.com", address.getHostString()); + assertEquals(2000, address.getPort()); + } + + @Test + void testGetHttpUrlConnectionNoProxy() throws IOException { + HttpSettingsReaderDelegate samplerScoped = new HttpSettingsReaderDelegate(null); + when(mockUrl.openConnection()).thenReturn(mockConnection); + + samplerScoped.getHttpUrlConnection(mockUrl, TEST_AUTH_HEADER); + verify(mockUrl).openConnection(); + } } diff --git a/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderTest.java b/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderTest.java index 126f98df..a4bf0fb0 100644 --- a/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderTest.java +++ b/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/HttpSettingsReaderTest.java @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -29,7 +30,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -40,11 +40,12 @@ class HttpSettingsReaderTest { @Mock private Settings mockSettings; - @InjectMocks private HttpSettingsReader tested; + private HttpSettingsReader tested; @BeforeEach void setUp() { ConfigManager.reset(); + tested = HttpSettingsReader.forSystemConfig(delegate); } @Test @@ -171,4 +172,28 @@ void testGetSettings_UrlWithPortAndPath() throws InvalidConfigException { assertEquals(mockSettings, result); verify(delegate).fetchSettings(expectedUrl, expectedTokenHeader); } + + @Test + void testGetSettingsSamplerScopedReaderUsesOnlyItsValues() { + HttpSettingsReader samplerScoped = + HttpSettingsReader.forFileConfig( + delegate, "sampler.collector.com", "sampler-key:sampler-service"); + String expectedUrl = "https://sampler.collector.com/v1/settings/sampler-service"; + String expectedTokenHeader = "Bearer sampler-key"; + + when(delegate.fetchSettings(eq(expectedUrl), eq(expectedTokenHeader))).thenReturn(mockSettings); + assertEquals(mockSettings, samplerScoped.getSettings()); + } + + @Test + void testGetSettingsSamplerScopedReaderUsesDefaultConfig() { + HttpSettingsReader samplerScoped = + HttpSettingsReader.forFileConfig(delegate, null, "sampler-key:sampler-service"); + String expectedUrl = + "https://apm.collector.na-01.cloud.solarwinds.com/v1/settings/sampler-service"; + + when(delegate.fetchSettings(eq(expectedUrl), eq("Bearer sampler-key"))) + .thenReturn(mockSettings); + assertEquals(mockSettings, samplerScoped.getSettings()); + } } diff --git a/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/HttpSamplerComponentProviderTest.java b/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/HttpSamplerComponentProviderTest.java new file mode 100644 index 00000000..2be06c02 --- /dev/null +++ b/custom/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/HttpSamplerComponentProviderTest.java @@ -0,0 +1,192 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 + * + * http://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 com.solarwinds.opentelemetry.extensions.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.solarwinds.joboe.config.ConfigManager; +import com.solarwinds.joboe.config.ConfigProperty; +import com.solarwinds.joboe.config.InvalidConfigException; +import com.solarwinds.joboe.config.ProxyConfig; +import com.solarwinds.joboe.sampling.SamplingConfiguration; +import com.solarwinds.joboe.sampling.SettingsManager; +import com.solarwinds.opentelemetry.extensions.SolarwindsSampler; +import com.solarwinds.opentelemetry.extensions.config.HttpSettingsReaderDelegate; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class HttpSamplerComponentProviderTest { + private static final String SERVICE_KEY_ONLY = + "swo_http:\n service_key: sampler-key:sampler-service\n"; + + private final HttpSettingsReaderDelegate delegate = mock(HttpSettingsReaderDelegate.class); + + private final AtomicReference proxyConfig = new AtomicReference<>(); + + private final HttpSamplerComponentProvider tested = + new HttpSamplerComponentProvider( + proxy -> { + proxyConfig.set(proxy); + return delegate; + }); + + @BeforeEach + void setUp() { + ConfigManager.reset(); + SettingsManager.reset(); + } + + @AfterEach + void tearDown() { + ConfigManager.reset(); + SettingsManager.reset(); + } + + @Test + void testName() { + assertEquals("swo_http", tested.getName()); + } + + @Test + void testCreateFetchesSettingsWithSamplerConfig() throws InterruptedException { + assertInstanceOf( + SolarwindsSampler.class, + tested.create( + fromYaml( + "swo_http:\n" + + " collector: apm.collector.na-01.st-ssp.solarwinds.com\n" + + " service_key: sampler-key:sampler-service\n" + + " proxy:\n" + + " host: proxy.example.com\n" + + " port: 8080\n"))); + + awaitFirstPoll(); + verify(delegate) + .fetchSettings( + "https://apm.collector.na-01.st-ssp.solarwinds.com/v1/settings/sampler-service", + "Bearer sampler-key"); + + assertEquals("proxy.example.com", proxyConfig.get().getHost()); + assertEquals(8080, proxyConfig.get().getPort()); + assertNull(ConfigManager.getConfig(ConfigProperty.AGENT_SERVICE_KEY)); + + assertNull(ConfigManager.getConfig(ConfigProperty.AGENT_COLLECTOR)); + assertNull(ConfigManager.getConfig(ConfigProperty.AGENT_PROXY)); + } + + @Test + void testCreateIgnoresSolarwindsConfig() throws InterruptedException, InvalidConfigException { + ConfigManager.setConfig(ConfigProperty.AGENT_COLLECTOR, "global.collector.com"); + ConfigManager.setConfig(ConfigProperty.AGENT_SERVICE_KEY, "global-key:global-service"); + ConfigManager.setConfig(ConfigProperty.AGENT_CONTEXT_TTL, 1000); + + tested.create(fromYaml(SERVICE_KEY_ONLY)); + awaitFirstPoll(); + + verify(delegate) + .fetchSettings( + "https://apm.collector.na-01.cloud.solarwinds.com/v1/settings/sampler-service", + "Bearer sampler-key"); + assertNull(proxyConfig.get()); + assertEquals(1_200_000, SettingsManager.getSamplingConfiguration().getTtl()); + } + + @Test + void testCreateTwiceClosesPreviousFetcher() { + tested.create(fromYaml(SERVICE_KEY_ONLY)); + CountDownLatch first = SettingsManager.getSettingsAvailableLatch(); + + tested.create(fromYaml(SERVICE_KEY_ONLY)); + assertNotSame(first, SettingsManager.getSettingsAvailableLatch()); + } + + @Test + void testCreateAppliesContextConfig() { + tested.create( + fromYaml( + SERVICE_KEY_ONLY + + " context_ttl: 600000\n" + + " context_max_events: 5000\n" + + " context_max_backtraces: 50\n")); + + SamplingConfiguration samplingConfiguration = SettingsManager.getSamplingConfiguration(); + assertEquals(600000, samplingConfiguration.getTtl()); + assertEquals(5000, samplingConfiguration.getMaxEvents()); + + assertEquals(50, samplingConfiguration.getMaxBacktraces()); + assertNull(ConfigManager.getConfig(ConfigProperty.AGENT_CONTEXT_TTL)); + } + + @ParameterizedTest + @MethodSource("invalidConfigs") + void testCreateThrowsForInvalidConfig(String yaml) { + RuntimeException exception = + assertThrows(RuntimeException.class, () -> tested.create(fromYaml(yaml))); + assertInstanceOf(InvalidConfigException.class, exception.getCause()); + } + + static Stream invalidConfigs() { + return Stream.of( + "swo_http:\n collector: apm.collector.na-01.cloud.solarwinds.com\n", + "swo_http:\n service_key: ''\n", + SERVICE_KEY_ONLY + " collector: ' '\n", + SERVICE_KEY_ONLY + " context_ttl: 0\n", + SERVICE_KEY_ONLY + " context_max_events: -1\n", + SERVICE_KEY_ONLY + " context_max_backtraces: 0\n", + SERVICE_KEY_ONLY + " proxy:\n host: proxy.example.com\n", + SERVICE_KEY_ONLY + " proxy:\n port: 8080\n", + SERVICE_KEY_ONLY + " proxy:\n host: proxy.example.com\n port: 70000\n"); + } + + @Test + void testCreateThrowsWhenServiceKeyMissing() { + RuntimeException exception = + assertThrows( + RuntimeException.class, () -> tested.create(DeclarativeConfigProperties.empty())); + assertInstanceOf(InvalidConfigException.class, exception.getCause()); + } + + private void awaitFirstPoll() throws InterruptedException { + assertTrue(SettingsManager.getSettingsAvailableLatch().await(5, TimeUnit.SECONDS)); + verify(delegate).fetchSettings(anyString(), anyString()); + } + + private DeclarativeConfigProperties fromYaml(String yaml) { + return DeclarativeConfiguration.toConfigProperties( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))) + .getStructured("swo_http"); + } +} diff --git a/custom/src/test/resources/sdk-config.yaml b/custom/src/test/resources/sdk-config.yaml index a25e4cdc..43bfcdef 100644 --- a/custom/src/test/resources/sdk-config.yaml +++ b/custom/src/test/resources/sdk-config.yaml @@ -67,7 +67,9 @@ tracer_provider: sampler: # Configure sampler. - swo/sampler: + swo_http: + service_key: token:service + tracing_mode: enabled # Configure meter provider. meter_provider: @@ -156,4 +158,3 @@ instrumentation/development: solarwinds: agent.logging: level: trace - agent.tracingMode: enabled diff --git a/libs/lambda/build.gradle.kts b/libs/lambda/build.gradle.kts index ac2fdbb2..ca98b4d9 100644 --- a/libs/lambda/build.gradle.kts +++ b/libs/lambda/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-api") compileOnly("io.opentelemetry.semconv:opentelemetry-semconv") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling") compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api") @@ -41,6 +42,8 @@ dependencies { testImplementation(project(":libs:config")) testImplementation(project(":libs:sampling")) testImplementation(project(":libs:shared")) + testImplementation("io.opentelemetry:opentelemetry-api-incubator") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config") } tasks.named("compileJava") { diff --git a/libs/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/JsonSamplerComponentProvider.java b/libs/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/JsonSamplerComponentProvider.java new file mode 100644 index 00000000..029a2fbb --- /dev/null +++ b/libs/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/JsonSamplerComponentProvider.java @@ -0,0 +1,50 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 + * + * http://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 com.solarwinds.opentelemetry.extensions; + +import com.google.auto.service.AutoService; +import com.solarwinds.joboe.config.InvalidConfigException; +import com.solarwinds.joboe.sampling.SettingsFetcher; +import com.solarwinds.opentelemetry.extensions.config.provider.AbstractSamplerComponentProvider; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; + +@AutoService(ComponentProvider.class) +public class JsonSamplerComponentProvider extends AbstractSamplerComponentProvider { + + public static final String DEFAULT_SETTINGS_FILE = "/tmp/solarwinds-apm-settings.json"; + + private static final String FILE = "file"; + + @Override + public String getName() { + return SharedNames.JSON_SAMPLER_NAME; + } + + @Override + protected SettingsFetcher createSettingsFetcher(DeclarativeConfigProperties properties) + throws InvalidConfigException { + + String file = properties.getString(FILE, DEFAULT_SETTINGS_FILE); + if (file.trim().isEmpty()) { + throw new InvalidConfigException( + "Invalid sampler " + FILE + " config, value cannot be empty"); + } + + return new AwsLambdaSettingsFetcher(new FileSettingsReader(file)); + } +} diff --git a/libs/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaAgentListener.java b/libs/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaAgentListener.java index f7be2998..d12bbd5c 100644 --- a/libs/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaAgentListener.java +++ b/libs/lambda/src/main/java/com/solarwinds/opentelemetry/extensions/LambdaAgentListener.java @@ -36,12 +36,17 @@ public class LambdaAgentListener implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { if (isAgentEnabled()) { - SettingsManager.initialize( - new AwsLambdaSettingsFetcher(new FileSettingsReader("/tmp/solarwinds-apm-settings.json")), - SamplingConfigProvider.getSamplingConfiguration()); + if (!SettingsManager.isInitialized()) { + SettingsManager.initialize( + new AwsLambdaSettingsFetcher( + new FileSettingsReader(JsonSamplerComponentProvider.DEFAULT_SETTINGS_FILE)), + SamplingConfigProvider.getSamplingConfiguration()); + } + logger.info("Successfully submitted SolarwindsAPM OpenTelemetry extensions settings"); } else { autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk().shutdown(); + SettingsManager.reset(); logger.info("SolarwindsAPM OpenTelemetry extensions is disabled"); } } diff --git a/libs/lambda/src/test/java/com/solarwinds/opentelemetry/extensions/JsonSamplerComponentProviderTest.java b/libs/lambda/src/test/java/com/solarwinds/opentelemetry/extensions/JsonSamplerComponentProviderTest.java new file mode 100644 index 00000000..95ed5a24 --- /dev/null +++ b/libs/lambda/src/test/java/com/solarwinds/opentelemetry/extensions/JsonSamplerComponentProviderTest.java @@ -0,0 +1,80 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 + * + * http://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 com.solarwinds.opentelemetry.extensions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.solarwinds.joboe.config.ConfigManager; +import com.solarwinds.joboe.config.InvalidConfigException; +import com.solarwinds.joboe.sampling.SettingsManager; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class JsonSamplerComponentProviderTest { + private final JsonSamplerComponentProvider tested = new JsonSamplerComponentProvider(); + + @AfterEach + void tearDown() { + ConfigManager.reset(); + SettingsManager.reset(); + } + + @Test + void testName() { + assertEquals("swo_json", tested.getName()); + } + + @Test + void testCreateReadsSettingsFromConfiguredFile() { + String file = + JsonSamplerComponentProviderTest.class + .getResource("/solarwinds-apm-settings.json") + .getPath(); + + assertInstanceOf( + SolarwindsSampler.class, tested.create(fromYaml("swo_json:\n file: " + file + "\n"))); + assertNotNull(SettingsManager.getSettings()); + } + + @Test + void testCreateDoesNotFallBackToDefaultFile() { + tested.create(fromYaml("swo_json:\n file: /does/not/exist.json\n")); + assertNull(SettingsManager.getSettings()); + } + + @Test + void testCreateThrowsForEmptyFile() { + RuntimeException exception = + assertThrows( + RuntimeException.class, () -> tested.create(fromYaml("swo_json:\n file: ' '\n"))); + assertInstanceOf(InvalidConfigException.class, exception.getCause()); + } + + private DeclarativeConfigProperties fromYaml(String yaml) { + return DeclarativeConfiguration.toConfigProperties( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))) + .getStructured("swo_json"); + } +} diff --git a/libs/sampling/src/main/java/com/solarwinds/joboe/sampling/SettingsManager.java b/libs/sampling/src/main/java/com/solarwinds/joboe/sampling/SettingsManager.java index d0ba6efa..d2fb9017 100644 --- a/libs/sampling/src/main/java/com/solarwinds/joboe/sampling/SettingsManager.java +++ b/libs/sampling/src/main/java/com/solarwinds/joboe/sampling/SettingsManager.java @@ -43,7 +43,7 @@ */ @Getter public final class SettingsManager { - private static SettingsFetcher fetcher; + private static volatile SettingsFetcher fetcher; private static final Map, Set>> listeners = new ConcurrentHashMap, Set>>(); private static final Logger logger = LoggerFactory.getLogger(); @@ -54,19 +54,44 @@ public final class SettingsManager { public static CountDownLatch initialize( SettingsFetcher fetcher, SamplingConfiguration samplingConfiguration) { + SettingsManager.samplingConfiguration = samplingConfiguration; initializeFetcher(fetcher); Metadata.setup(samplingConfiguration); - SettingsManager.samplingConfiguration = samplingConfiguration; return fetcher.isSettingsAvailableLatch(); } + public static boolean isInitialized() { + return fetcher != null; + } + + /** + * @return the latch of the current fetcher, or null if this manager is not initialized + */ + public static CountDownLatch getSettingsAvailableLatch() { + SettingsFetcher current = fetcher; + return current != null ? current.isSettingsAvailableLatch() : null; + } + + public static void reset() { + SettingsFetcher previous = fetcher; + fetcher = null; + if (previous != null) { + previous.close(); + } + } + /** * Initializes this manager with a provided {@link SettingsFetcher}. Direct call to this is only * for internal tests */ public static void initializeFetcher(SettingsFetcher fetcher) { + SettingsFetcher previous = SettingsManager.fetcher; SettingsManager.fetcher = fetcher; + if (previous != null && previous != fetcher) { + previous.close(); + } + fetcher.registerListener( newSettings -> { // figure out the difference and notify listeners @@ -134,10 +159,11 @@ public static Settings getSettings() { * @return the current {@link Settings}, or null if not available within the timeout */ public static Settings getSettings(long timeout, TimeUnit unit) { - if (fetcher != null) { + SettingsFetcher current = fetcher; + if (current != null) { if (timeout > 0) { try { - if (!fetcher.isSettingsAvailableLatch().await(timeout, unit)) { + if (!current.isSettingsAvailableLatch().await(timeout, unit)) { logger.warn("Settings are not avaialable after waiting for " + timeout + " " + unit); return null; } @@ -146,7 +172,7 @@ public static Settings getSettings(long timeout, TimeUnit unit) { return null; } } - return fetcher.getSettings(); + return current.getSettings(); } else { logger.debug("Settings are not yet available as initialization has not been completed yet"); return null; diff --git a/libs/sampling/src/test/java/com/solarwinds/joboe/sampling/SettingsManagerTest.java b/libs/sampling/src/test/java/com/solarwinds/joboe/sampling/SettingsManagerTest.java index dc6764ca..c2a95bb8 100644 --- a/libs/sampling/src/test/java/com/solarwinds/joboe/sampling/SettingsManagerTest.java +++ b/libs/sampling/src/test/java/com/solarwinds/joboe/sampling/SettingsManagerTest.java @@ -17,7 +17,11 @@ package com.solarwinds.joboe.sampling; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -40,6 +44,48 @@ public class SettingsManagerTest { @Mock private SettingsFetcher settingsFetcherMock; + @Test + public void testInitializedFetcherLatchIsExposed() { + CountDownLatch latch = new CountDownLatch(1); + when(settingsFetcherMock.isSettingsAvailableLatch()).thenReturn(latch); + + SettingsManager.initializeFetcher(settingsFetcherMock); + + assertTrue(SettingsManager.isInitialized()); + assertSame(latch, SettingsManager.getSettingsAvailableLatch()); + } + + @Test + public void testReinitializeClosesPreviousFetcher() { + SettingsFetcher previous = mock(SettingsFetcher.class); + SettingsManager.initializeFetcher(previous); + SettingsManager.initializeFetcher(settingsFetcherMock); + + verify(previous).close(); + SettingsManager.reset(); + verify(settingsFetcherMock).close(); + assertFalse(SettingsManager.isInitialized()); + } + + @Test + public void testGetSettingsReturnsAwaitedFetcherSettingsWhenResetWhileWaiting() { + Settings settings = mock(Settings.class); + CountDownLatch latch = + new CountDownLatch(0) { + @Override + public boolean await(long timeout, TimeUnit unit) { + SettingsManager.reset(); + return true; + } + }; + + when(settingsFetcherMock.isSettingsAvailableLatch()).thenReturn(latch); + when(settingsFetcherMock.getSettings()).thenReturn(settings); + SettingsManager.initializeFetcher(settingsFetcherMock); + + assertSame(settings, SettingsManager.getSettings(1, TimeUnit.SECONDS)); + } + @Test public void testArgChangeListener() { TestArgChangeListener bucketCapacityListener = diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SharedNames.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SharedNames.java index 3c566b3c..acb40c5d 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SharedNames.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SharedNames.java @@ -25,6 +25,10 @@ private SharedNames() {} public static final String COMPONENT_NAME = "solarwinds"; + public static final String HTTP_SAMPLER_NAME = "swo_http"; + + public static final String JSON_SAMPLER_NAME = "swo_json"; + public static final String SPAN_STACKTRACE_FILTER_CLASS = "com.solarwinds.opentelemetry.extensions.SpanStacktraceFilter"; diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TracingModeParser.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TracingModeParser.java deleted file mode 100644 index f2b5d14c..00000000 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TracingModeParser.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import com.google.auto.service.AutoService; -import com.solarwinds.joboe.config.ConfigProperty; -import com.solarwinds.joboe.config.InvalidConfigException; -import com.solarwinds.joboe.sampling.TracingMode; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; - -@AutoService(DeclarativeConfigPropertyParser.class) -public class TracingModeParser implements DeclarativeConfigPropertyParser { - private static final String CONFIG_KEY = "agent.tracingMode"; - - @Override - public TracingMode convert(DeclarativeConfigProperties declarativeConfigProperties) - throws InvalidConfigException { - String mode = declarativeConfigProperties.getString(CONFIG_KEY); - if (mode == null) { - return null; - } - - TracingMode tracingMode = TracingMode.fromString(mode); - if (tracingMode != null) { - return tracingMode; - } else { - throw new InvalidConfigException( - "Invalid " - + ConfigProperty.AGENT_TRACING_MODE.getConfigFileKey() - + " : " - + mode - + ", must be \"disabled\" or \"enabled\""); - } - } - - @Override - public String configKey() { - return CONFIG_KEY; - } -} diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TransactionSettingsConfigParser.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TransactionSettingsConfigParser.java deleted file mode 100644 index 8bbd82b7..00000000 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TransactionSettingsConfigParser.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import com.google.auto.service.AutoService; -import com.solarwinds.joboe.config.ConfigProperty; -import com.solarwinds.joboe.config.InvalidConfigException; -import com.solarwinds.joboe.sampling.ResourceMatcher; -import com.solarwinds.joboe.sampling.SampleRateSource; -import com.solarwinds.joboe.sampling.TraceConfig; -import com.solarwinds.joboe.sampling.TraceConfigs; -import com.solarwinds.joboe.sampling.TracingMode; -import com.solarwinds.opentelemetry.extensions.config.parser.json.ResourceExtensionsMatcher; -import com.solarwinds.opentelemetry.extensions.config.parser.json.StringPatternMatcher; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import org.json.JSONException; - -@AutoService(DeclarativeConfigPropertyParser.class) -public class TransactionSettingsConfigParser implements DeclarativeConfigPropertyParser { - private static final String TRACING_KEY = "tracing"; - private static final String REGEX_KEY = "regex"; - private static final String EXTENSIONS_KEY = "com/solarwinds/opentelemetry/extensions"; - - private static final List KEYS = Arrays.asList(TRACING_KEY, EXTENSIONS_KEY, REGEX_KEY); - private static final String CONFIG_KEY = "agent.transactionSettings"; - - @Override - public TraceConfigs convert(DeclarativeConfigProperties declarativeConfigProperties) - throws InvalidConfigException { - try { - List transactionSettings = - declarativeConfigProperties.getStructuredList(CONFIG_KEY, Collections.emptyList()); - - Map result = new LinkedHashMap(); - for (DeclarativeConfigProperties transactionSetting : transactionSettings) { - ResourceMatcher matcher = parseMatcher(transactionSetting); - TraceConfig traceConfig = parseTraceConfig(transactionSetting); - - result.put(matcher, traceConfig); - } - - return new TraceConfigs(result); - } catch (JSONException e) { - throw new InvalidConfigException( - "Failed to parse \"" - + ConfigProperty.AGENT_TRANSACTION_SETTINGS.getConfigFileKey() - + "\". Error message is [" - + e.getMessage() - + "]", - e); - } - } - - @Override - public String configKey() { - return CONFIG_KEY; - } - - private ResourceMatcher parseMatcher(DeclarativeConfigProperties declarativeConfigProperties) - throws InvalidConfigException, JSONException { - checkKeys(declarativeConfigProperties.getPropertyKeys()); - String regex = declarativeConfigProperties.getString(REGEX_KEY); - List extensions = - declarativeConfigProperties.getScalarList(EXTENSIONS_KEY, String.class); - - if (regex != null && extensions != null) { - throw new InvalidConfigException( - "Multiple matchers found for \"" - + ConfigProperty.AGENT_TRANSACTION_SETTINGS.getConfigFileKey() - + "\" entry " - + declarativeConfigProperties); - } else if (regex != null) { - try { - Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); - return new StringPatternMatcher(pattern); - } catch (PatternSyntaxException e) { - throw new InvalidConfigException( - "Failed to compile pattern " - + regex - + " defined in \"" - + configKey() - + "." - + REGEX_KEY - + "\", error message [" - + e.getMessage() - + "].", - e); - } - - } else if (extensions != null) { - Set resourceExtensions = new HashSet(extensions); - return new ResourceExtensionsMatcher(resourceExtensions); - } else { - throw new InvalidConfigException( - "Cannot find proper matcher for \"" - + configKey() - + "\" entry " - + declarativeConfigProperties - + ". Neither " - + REGEX_KEY - + " nor " - + EXTENSIONS_KEY - + " was defined"); - } - } - - private void checkKeys(Set jsonKeys) throws InvalidConfigException { - Set keys = new HashSet(jsonKeys); - KEYS.forEach(keys::remove); - - if (!keys.isEmpty()) { - throw new InvalidConfigException( - "Failed to parse \"" - + ConfigProperty.AGENT_TRANSACTION_SETTINGS.getConfigFileKey() - + "\". Unknown key(s) " - + keys - + " found"); - } - } - - private TraceConfig parseTraceConfig(DeclarativeConfigProperties declarativeConfigProperties) - throws InvalidConfigException, JSONException { - String tracingModeString = declarativeConfigProperties.getString(TRACING_KEY); - - if (tracingModeString != null) { - TracingMode tracingMode = TracingMode.fromString(tracingModeString); - if (tracingMode == null) { - throw new InvalidConfigException( - "Invalid \"" - + TRACING_KEY - + "\" value [" - + tracingModeString - + "], must either be " - + TracingMode.ENABLED.getStringValue() - + " or " - + TracingMode.DISABLED.getStringValue()); - } - if (tracingMode == TracingMode.ALWAYS || tracingMode == TracingMode.ENABLED) { - return new TraceConfig( - null, - SampleRateSource.FILE, - tracingMode.toFlags()); // undefined sample rate if trace mode is enabled - } else { - return new TraceConfig(0, SampleRateSource.FILE, tracingMode.toFlags()); - } - } else { - throw new InvalidConfigException( - "Need to define \"" - + TRACING_KEY - + "\" for each entry in \"" - + ConfigProperty.AGENT_TRANSACTION_SETTINGS.getConfigFileKey() - + "\""); - } - } -} diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TriggerTraceParser.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TriggerTraceParser.java deleted file mode 100644 index 5afcc0aa..00000000 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TriggerTraceParser.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import com.google.auto.service.AutoService; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; - -@AutoService(DeclarativeConfigPropertyParser.class) -public class TriggerTraceParser implements DeclarativeConfigPropertyParser { - private static final String CONFIG_KEY = "agent.triggerTrace"; - - @Override - public Boolean convert(DeclarativeConfigProperties declarativeConfigProperties) { - return declarativeConfigProperties.getBoolean(CONFIG_KEY, true); - } - - @Override - public String configKey() { - return CONFIG_KEY; - } -} diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/UrlSampleRateConfigParser.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/UrlSampleRateConfigParser.java deleted file mode 100644 index 7dee61f9..00000000 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/UrlSampleRateConfigParser.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import com.google.auto.service.AutoService; -import com.solarwinds.joboe.config.InvalidConfigException; -import com.solarwinds.joboe.sampling.ResourceMatcher; -import com.solarwinds.joboe.sampling.SampleRateSource; -import com.solarwinds.joboe.sampling.TraceConfig; -import com.solarwinds.joboe.sampling.TraceConfigs; -import com.solarwinds.joboe.sampling.TracingMode; -import com.solarwinds.opentelemetry.extensions.config.parser.json.StringPatternMatcher; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -@AutoService(DeclarativeConfigPropertyParser.class) -public class UrlSampleRateConfigParser implements DeclarativeConfigPropertyParser { - - private static final String SAMPLE_RATE = "sampleRate"; - private static final String TRACING_MODE = "tracingMode"; - private static final String CONFIG_KEY = "agent.urlSampleRates"; - - @Override - public TraceConfigs convert(DeclarativeConfigProperties declarativeConfigProperties) - throws InvalidConfigException { - List urlSampleRates = - declarativeConfigProperties.getStructuredList(CONFIG_KEY, Collections.emptyList()); - Map result = new LinkedHashMap<>(); - for (DeclarativeConfigProperties urlSampleRate : urlSampleRates) { - - Set propertyKeys = urlSampleRate.getPropertyKeys(); - if (propertyKeys.size() != 1) { - throw new InvalidConfigException("Invalid url sample rate config"); - } - - Pattern pattern; - String url = propertyKeys.iterator().next(); - try { - pattern = Pattern.compile(url, Pattern.CASE_INSENSITIVE); - - } catch (PatternSyntaxException e) { - throw new InvalidConfigException( - "Failed to compile the url sample rate of url pattern [" - + url - + "], error message [" - + e.getMessage() - + "].", - e); - } - - DeclarativeConfigProperties urlConfig = - urlSampleRate.getStructured(url, DeclarativeConfigProperties.empty()); - TraceConfig traceConfig = extractConfig(urlConfig, declarativeConfigProperties); - - result.put(new StringPatternMatcher(pattern), traceConfig); - } - - return new TraceConfigs(result); - } - - @Override - public String configKey() { - return CONFIG_KEY; - } - - private TraceConfig extractConfig( - DeclarativeConfigProperties urlConfigEntry, DeclarativeConfigProperties urlConfig) - throws InvalidConfigException { - - Integer sampleRate = urlConfigEntry.getInt(SAMPLE_RATE); - String tracingModeString = urlConfigEntry.getString(TRACING_MODE); - TracingMode tracingMode = null; - - if (tracingModeString != null) { - tracingMode = TracingMode.fromString(tracingModeString); - if (tracingMode == null) { - throw new InvalidConfigException( - "Invalid tracingMode value [" - + tracingModeString - + "], must be \"never\" if default is to be overridden; otherwise do not specify this property in [" - + urlConfig - + "]"); - } - } - - if (sampleRate == null && tracingMode == null) { - throw new InvalidConfigException( - "Need to define either tracingMode, sampleRate or metricsEnabled, but found none in [" - + urlConfig - + "]"); - } - - if (sampleRate == null) { - if (tracingMode == TracingMode.ALWAYS) { - throw new InvalidConfigException( - "Define sampleRate if tracingMode is \"always\" in [" + urlConfig + "]"); - } else { - sampleRate = 0; - } - } - - if (tracingMode == null) { - tracingMode = TracingMode.ALWAYS; // default to ALWAYS if not provided - } - - return new TraceConfig(sampleRate, SampleRateSource.FILE, tracingMode.toFlags()); - } -} diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProvider.java new file mode 100644 index 00000000..9a262351 --- /dev/null +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProvider.java @@ -0,0 +1,300 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 + * + * http://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 com.solarwinds.opentelemetry.extensions.config.provider; + +import com.solarwinds.joboe.config.InvalidConfigException; +import com.solarwinds.joboe.logging.Logger; +import com.solarwinds.joboe.logging.LoggerFactory; +import com.solarwinds.joboe.sampling.ResourceMatcher; +import com.solarwinds.joboe.sampling.SampleRateSource; +import com.solarwinds.joboe.sampling.SamplingConfiguration; +import com.solarwinds.joboe.sampling.SettingsFetcher; +import com.solarwinds.joboe.sampling.SettingsManager; +import com.solarwinds.joboe.sampling.TraceConfig; +import com.solarwinds.joboe.sampling.TraceConfigs; +import com.solarwinds.joboe.sampling.TracingMode; +import com.solarwinds.opentelemetry.extensions.SolarwindsSampler; +import com.solarwinds.opentelemetry.extensions.config.parser.json.ResourceExtensionsMatcher; +import com.solarwinds.opentelemetry.extensions.config.parser.json.StringPatternMatcher; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public abstract class AbstractSamplerComponentProvider implements ComponentProvider { + private static final Logger logger = LoggerFactory.getLogger(); + + private static final String TRACING_MODE = "tracing_mode"; + + private static final String TRIGGER_TRACE = "trigger_trace"; + + private static final String TRANSACTION_SETTINGS = "transaction_settings"; + + private static final String URL_SAMPLE_RATES = "url_sample_rates"; + + private static final String TRACING = "tracing"; + + private static final String REGEX = "regex"; + + private static final String EXTENSIONS = "extensions"; + + private static final String SAMPLE_RATE = "sample_rate"; + + private static final List TRANSACTION_SETTING_KEYS = + Arrays.asList(TRACING, REGEX, EXTENSIONS); + + @Override + public Class getType() { + return Sampler.class; + } + + @Override + public Sampler create(DeclarativeConfigProperties declarativeConfigProperties) { + try { + SamplingConfiguration.SamplingConfigurationBuilder builder = + SamplingConfiguration.builder() + .tracingMode(parseTracingMode(declarativeConfigProperties)) + .triggerTraceEnabled(declarativeConfigProperties.getBoolean(TRIGGER_TRACE, true)) + .internalTransactionSettings(parseTraceConfigs(declarativeConfigProperties)); + + parseConfig(declarativeConfigProperties, builder); + SettingsManager.initialize( + createSettingsFetcher(declarativeConfigProperties), builder.build()); + + } catch (InvalidConfigException e) { + throw new RuntimeException(e); + } + + return new SolarwindsSampler(); + } + + protected void parseConfig( + DeclarativeConfigProperties properties, + SamplingConfiguration.SamplingConfigurationBuilder builder) + throws InvalidConfigException {} + + protected abstract SettingsFetcher createSettingsFetcher(DeclarativeConfigProperties properties) + throws InvalidConfigException; + + private TracingMode parseTracingMode(DeclarativeConfigProperties properties) + throws InvalidConfigException { + String mode = properties.getString(TRACING_MODE); + if (mode == null) { + return null; + } + + TracingMode tracingMode = TracingMode.fromString(mode); + if (tracingMode == null) { + throw new InvalidConfigException( + "Invalid " + TRACING_MODE + " : " + mode + ", must be \"disabled\" or \"enabled\""); + } + return tracingMode; + } + + private TraceConfigs parseTraceConfigs(DeclarativeConfigProperties properties) + throws InvalidConfigException { + List transactionSettings = + properties.getStructuredList(TRANSACTION_SETTINGS); + List urlSampleRates = + properties.getStructuredList(URL_SAMPLE_RATES); + + if (transactionSettings != null) { + if (urlSampleRates != null) { + logger.warn( + URL_SAMPLE_RATES + " is ignored as " + TRANSACTION_SETTINGS + " is also defined"); + } + return parseTransactionSettings(transactionSettings); + } + + if (urlSampleRates != null) { + return parseUrlSampleRates(urlSampleRates); + } + return null; + } + + private TraceConfigs parseTransactionSettings(List settings) + throws InvalidConfigException { + Map result = new LinkedHashMap<>(); + for (DeclarativeConfigProperties setting : settings) { + result.put(parseMatcher(setting), parseTransactionTraceConfig(setting)); + } + return new TraceConfigs(result); + } + + private ResourceMatcher parseMatcher(DeclarativeConfigProperties setting) + throws InvalidConfigException { + Set unknownKeys = new HashSet<>(setting.getPropertyKeys()); + TRANSACTION_SETTING_KEYS.forEach(unknownKeys::remove); + if (!unknownKeys.isEmpty()) { + throw new InvalidConfigException( + "Failed to parse \"" + + TRANSACTION_SETTINGS + + "\". Unknown key(s) " + + unknownKeys + + " found"); + } + + String regex = setting.getString(REGEX); + List extensions = setting.getScalarList(EXTENSIONS, String.class); + + if (regex != null && extensions != null) { + throw new InvalidConfigException( + "Multiple matchers found for \"" + TRANSACTION_SETTINGS + "\" entry " + setting); + } else if (regex != null) { + try { + return new StringPatternMatcher(Pattern.compile(regex, Pattern.CASE_INSENSITIVE)); + } catch (PatternSyntaxException e) { + throw new InvalidConfigException( + "Failed to compile pattern " + + regex + + " defined in \"" + + TRANSACTION_SETTINGS + + "." + + REGEX + + "\", error message [" + + e.getMessage() + + "].", + e); + } + } else if (extensions != null) { + return new ResourceExtensionsMatcher(new HashSet<>(extensions)); + } else { + throw new InvalidConfigException( + "Cannot find proper matcher for \"" + + TRANSACTION_SETTINGS + + "\" entry " + + setting + + ". Neither " + + REGEX + + " nor " + + EXTENSIONS + + " was defined"); + } + } + + private TraceConfig parseTransactionTraceConfig(DeclarativeConfigProperties setting) + throws InvalidConfigException { + String tracingModeString = setting.getString(TRACING); + if (tracingModeString == null) { + throw new InvalidConfigException( + "Need to define \"" + TRACING + "\" for each entry in \"" + TRANSACTION_SETTINGS + "\""); + } + + TracingMode tracingMode = TracingMode.fromString(tracingModeString); + if (tracingMode == null) { + throw new InvalidConfigException( + "Invalid \"" + + TRACING + + "\" value [" + + tracingModeString + + "], must either be " + + TracingMode.ENABLED.getStringValue() + + " or " + + TracingMode.DISABLED.getStringValue()); + } + + if (tracingMode == TracingMode.ALWAYS || tracingMode == TracingMode.ENABLED) { + // undefined sample rate if trace mode is enabled + return new TraceConfig(null, SampleRateSource.FILE, tracingMode.toFlags()); + } + return new TraceConfig(0, SampleRateSource.FILE, tracingMode.toFlags()); + } + + private TraceConfigs parseUrlSampleRates(List urlSampleRates) + throws InvalidConfigException { + Map result = new LinkedHashMap<>(); + for (DeclarativeConfigProperties urlSampleRate : urlSampleRates) { + Set propertyKeys = urlSampleRate.getPropertyKeys(); + if (propertyKeys.size() != 1) { + throw new InvalidConfigException( + "Invalid \"" + URL_SAMPLE_RATES + "\" entry " + urlSampleRate); + } + + String url = propertyKeys.iterator().next(); + Pattern pattern; + try { + pattern = Pattern.compile(url, Pattern.CASE_INSENSITIVE); + } catch (PatternSyntaxException e) { + throw new InvalidConfigException( + "Failed to compile the url sample rate of url pattern [" + + url + + "], error message [" + + e.getMessage() + + "].", + e); + } + + DeclarativeConfigProperties urlConfig = + urlSampleRate.getStructured(url, DeclarativeConfigProperties.empty()); + result.put(new StringPatternMatcher(pattern), parseUrlTraceConfig(urlConfig, url)); + } + return new TraceConfigs(result); + } + + private TraceConfig parseUrlTraceConfig(DeclarativeConfigProperties urlConfig, String url) + throws InvalidConfigException { + Integer sampleRate = urlConfig.getInt(SAMPLE_RATE); + String tracingModeString = urlConfig.getString(TRACING_MODE); + TracingMode tracingMode = null; + + if (tracingModeString != null) { + tracingMode = TracingMode.fromString(tracingModeString); + if (tracingMode == null) { + throw new InvalidConfigException( + "Invalid " + + TRACING_MODE + + " value [" + + tracingModeString + + "], must be \"never\" if default is to be overridden; otherwise do not specify" + + " this property in [" + + url + + "]"); + } + } + + if (sampleRate == null && tracingMode == null) { + throw new InvalidConfigException( + "Need to define either " + + TRACING_MODE + + " or " + + SAMPLE_RATE + + ", but found none in [" + + url + + "]"); + } + + if (sampleRate == null) { + if (tracingMode == TracingMode.ALWAYS) { + throw new InvalidConfigException( + "Define " + SAMPLE_RATE + " if " + TRACING_MODE + " is \"always\" in [" + url + "]"); + } + sampleRate = 0; + } + + if (tracingMode == null) { + tracingMode = TracingMode.ALWAYS; + } + return new TraceConfig(sampleRate, SampleRateSource.FILE, tracingMode.toFlags()); + } +} diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SamplerComponentProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SamplerComponentProvider.java deleted file mode 100644 index 80bdbed5..00000000 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SamplerComponentProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.provider; - -import com.google.auto.service.AutoService; -import com.solarwinds.opentelemetry.extensions.SolarwindsSampler; -import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; -import io.opentelemetry.sdk.trace.samplers.Sampler; - -@AutoService(ComponentProvider.class) -public class SamplerComponentProvider implements ComponentProvider { - - public static final String COMPONENT_NAME = "swo/sampler"; - - @Override - public Class getType() { - return Sampler.class; - } - - @Override - public String getName() { - return COMPONENT_NAME; - } - - @Override - public Sampler create( - io.opentelemetry.api.incubator.config.DeclarativeConfigProperties - declarativeConfigProperties) { - return new SolarwindsSampler(); - } -} diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java index cf082e01..96de72d8 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java @@ -39,13 +39,11 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.PeriodicMetricReaderModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.PushMetricExporterModel; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SpanExporterModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SpanProcessorModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.TracerProviderModel; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -84,8 +82,6 @@ public void customize(DeclarativeConfigurationCustomizer customizer) { addMetricExporter(configurationModel); if (tracerProvider != null) { - addSampler(tracerProvider); - addSpanExporter(configurationModel); } @@ -106,16 +102,6 @@ private static Map properties(Object... keyValues) { return properties; } - private void addSampler(TracerProviderModel model) { - SamplerModel sampler = model.getSampler(); - if (sampler == null) { - model.setSampler( - new SamplerModel() - .setExtensionProperty( - SamplerComponentProvider.COMPONENT_NAME, new HashMap())); - } - } - private void addSpanExporter(OpenTelemetryConfigurationModel model) { TracerProviderModel tracerProvider = Objects.requireNonNull(model.getTracerProvider()); List processors = tracerProvider.getProcessors(); diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TracingModeParserTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TracingModeParserTest.java deleted file mode 100644 index cfcbd1c6..00000000 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TracingModeParserTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.solarwinds.joboe.config.InvalidConfigException; -import com.solarwinds.joboe.sampling.TracingMode; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration; -import java.io.IOException; -import java.io.InputStream; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class TracingModeParserTest { - private static DeclarativeConfigProperties declarativeConfigProperties; - private final TracingModeParser tested = new TracingModeParser(); - - @BeforeAll - static void setup() { - try (InputStream resourceAsStream = - TracingModeParserTest.class.getResourceAsStream("/sdk-config.yaml")) { - DeclarativeConfigProperties configProperties = - DeclarativeConfiguration.toConfigProperties(resourceAsStream); - declarativeConfigProperties = - configProperties - .getStructured("instrumentation/development", DeclarativeConfigProperties.empty()) - .getStructured("java", DeclarativeConfigProperties.empty()) - .getStructured("solarwinds"); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Test - void testConvert() throws InvalidConfigException { - TracingMode tracingMode = tested.convert(declarativeConfigProperties); - assertEquals(TracingMode.ENABLED, tracingMode); - } - - @Test - void configKey() { - assertEquals("agent.tracingMode", tested.configKey()); - } -} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TransactionSettingsConfigParserTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TransactionSettingsConfigParserTest.java deleted file mode 100644 index 4e65f307..00000000 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TransactionSettingsConfigParserTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.solarwinds.joboe.config.InvalidConfigException; -import com.solarwinds.joboe.sampling.TraceConfigs; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration; -import java.io.IOException; -import java.io.InputStream; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class TransactionSettingsConfigParserTest { - private final TransactionSettingsConfigParser tested = new TransactionSettingsConfigParser(); - - @Test - void testConvert() throws InvalidConfigException { - try (InputStream resourceAsStream = - TransactionSettingsConfigParserTest.class.getResourceAsStream("/sdk-config.yaml")) { - DeclarativeConfigProperties configProperties = - DeclarativeConfiguration.toConfigProperties(resourceAsStream); - DeclarativeConfigProperties declarativeConfigProperties = - configProperties - .getStructured("instrumentation/development", DeclarativeConfigProperties.empty()) - .getStructured("java", DeclarativeConfigProperties.empty()) - .getStructured("solarwinds", DeclarativeConfigProperties.empty()); - - TraceConfigs traceConfigs = tested.convert(declarativeConfigProperties); - assertNotNull(traceConfigs); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @ParameterizedTest - @MethodSource("paths") - void testConvertWithException(String configPath) { - try (InputStream resourceAsStream = - TransactionSettingsConfigParserTest.class.getResourceAsStream(configPath)) { - DeclarativeConfigProperties configProperties = - DeclarativeConfiguration.toConfigProperties(resourceAsStream); - - DeclarativeConfigProperties declarativeConfigProperties = - configProperties - .getStructured("instrumentation/development", DeclarativeConfigProperties.empty()) - .getStructured("java", DeclarativeConfigProperties.empty()) - .getStructured("solarwinds", DeclarativeConfigProperties.empty()); - - assertThrows(InvalidConfigException.class, () -> tested.convert(declarativeConfigProperties)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Test - void configKey() { - assertEquals("agent.transactionSettings", tested.configKey()); - } - - private Stream paths() { - return Stream.of( - Arguments.of("/sdk-config-bad-transaction-settings-0.yaml"), - Arguments.of("/sdk-config-bad-transaction-settings-1.yaml")); - } -} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TriggerTraceParserTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TriggerTraceParserTest.java deleted file mode 100644 index 6bbbc214..00000000 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/TriggerTraceParserTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration; -import java.io.IOException; -import java.io.InputStream; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class TriggerTraceParserTest { - private static DeclarativeConfigProperties declarativeConfigProperties; - private final TriggerTraceParser tested = new TriggerTraceParser(); - - @BeforeAll - static void setup() { - try (InputStream resourceAsStream = - TriggerTraceParserTest.class.getResourceAsStream("/sdk-config.yaml")) { - DeclarativeConfigProperties configProperties = - DeclarativeConfiguration.toConfigProperties(resourceAsStream); - declarativeConfigProperties = - configProperties - .getStructured("instrumentation/development", DeclarativeConfigProperties.empty()) - .getStructured("java", DeclarativeConfigProperties.empty()) - .getStructured("solarwinds"); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Test - void testConvertTrue() { - Boolean result = tested.convert(declarativeConfigProperties); - assertTrue(result); - } - - @Test - void testConvertNull() { - Boolean result = tested.convert(DeclarativeConfigProperties.empty()); - assertTrue(result); - } - - @Test - void configKey() { - assertEquals("agent.triggerTrace", tested.configKey()); - } -} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/UrlSampleRateConfigParserTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/UrlSampleRateConfigParserTest.java deleted file mode 100644 index 0626e8f5..00000000 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/parser/yaml/UrlSampleRateConfigParserTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.parser.yaml; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.solarwinds.joboe.config.InvalidConfigException; -import com.solarwinds.joboe.sampling.TraceConfigs; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration; -import java.io.IOException; -import java.io.InputStream; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class UrlSampleRateConfigParserTest { - private final UrlSampleRateConfigParser tested = new UrlSampleRateConfigParser(); - - @Test - void testConvert() throws InvalidConfigException { - try (InputStream resourceAsStream = - UrlSampleRateConfigParserTest.class.getResourceAsStream("/sdk-config.yaml")) { - DeclarativeConfigProperties configProperties = - DeclarativeConfiguration.toConfigProperties(resourceAsStream); - DeclarativeConfigProperties declarativeConfigProperties = - configProperties - .getStructured("instrumentation/development", DeclarativeConfigProperties.empty()) - .getStructured("java", DeclarativeConfigProperties.empty()) - .getStructured("solarwinds", DeclarativeConfigProperties.empty()); - - TraceConfigs traceConfigs = tested.convert(declarativeConfigProperties); - assertNotNull(traceConfigs); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @ParameterizedTest - @MethodSource("paths") - void testConvertWithException(String configPath) { - try (InputStream resourceAsStream = - UrlSampleRateConfigParserTest.class.getResourceAsStream(configPath)) { - DeclarativeConfigProperties configProperties = - DeclarativeConfiguration.toConfigProperties(resourceAsStream); - - DeclarativeConfigProperties declarativeConfigProperties = - configProperties - .getStructured("instrumentation/development", DeclarativeConfigProperties.empty()) - .getStructured("java", DeclarativeConfigProperties.empty()) - .getStructured("solarwinds", DeclarativeConfigProperties.empty()); - - assertThrows(InvalidConfigException.class, () -> tested.convert(declarativeConfigProperties)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Test - void configKey() { - assertEquals("agent.urlSampleRates", tested.configKey()); - } - - private Stream paths() { - return Stream.of( - Arguments.of("/sdk-config-bad-transaction-settings-0.yaml"), - Arguments.of("/sdk-config-bad-transaction-settings-1.yaml")); - } -} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProviderTest.java new file mode 100644 index 00000000..0c39a083 --- /dev/null +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProviderTest.java @@ -0,0 +1,260 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 + * + * http://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 com.solarwinds.opentelemetry.extensions.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.solarwinds.joboe.config.ConfigManager; +import com.solarwinds.joboe.config.ConfigProperty; +import com.solarwinds.joboe.config.InvalidConfigException; +import com.solarwinds.joboe.sampling.SamplingConfiguration; +import com.solarwinds.joboe.sampling.Settings; +import com.solarwinds.joboe.sampling.SettingsFetcher; +import com.solarwinds.joboe.sampling.SettingsManager; +import com.solarwinds.joboe.sampling.TraceConfig; +import com.solarwinds.joboe.sampling.TraceConfigs; +import com.solarwinds.joboe.sampling.TracingMode; +import com.solarwinds.opentelemetry.extensions.SolarwindsSampler; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class AbstractSamplerComponentProviderTest { + private final SettingsFetcher settingsFetcher = mock(SettingsFetcher.class); + + private final AbstractSamplerComponentProvider tested = + new AbstractSamplerComponentProvider() { + @Override + public String getName() { + return "test"; + } + + @Override + protected SettingsFetcher createSettingsFetcher(DeclarativeConfigProperties properties) { + return settingsFetcher; + } + }; + + @AfterEach + void tearDown() { + ConfigManager.reset(); + SettingsManager.reset(); + } + + @Test + void testCreateDoesNotWriteToConfigManager() { + tested.create(loadConfig()); + + assertNull(ConfigManager.getConfig(ConfigProperty.AGENT_TRACING_MODE)); + assertNull(ConfigManager.getConfig(ConfigProperty.AGENT_TRIGGER_TRACE_ENABLED)); + assertNull(ConfigManager.getConfig(ConfigProperty.AGENT_INTERNAL_TRANSACTION_SETTINGS)); + } + + @Test + void testCreateInitializesSettingsManagerWithFetcher() { + Settings settings = mock(Settings.class); + when(settingsFetcher.getSettings()).thenReturn(settings); + + tested.create(DeclarativeConfigProperties.empty()); + assertSame(settings, SettingsManager.getSettings()); + } + + @Test + void testCreateAppliesSamplerConfig() { + Sampler sampler = tested.create(loadConfig()); + + assertInstanceOf(SolarwindsSampler.class, sampler); + assertEquals(TracingMode.ENABLED, samplingConfiguration().getTracingMode()); + assertTrue(samplingConfiguration().isTriggerTraceEnabled()); + } + + @Test + void testTransactionSettingsTakePrecedenceOverUrlSampleRates() { + tested.create(loadConfig()); + TraceConfigs traceConfigs = internalTransactionSettings(); + + TraceConfig specialties = + traceConfigs.getTraceConfig(Collections.singletonList("http://localhost/specialties")); + assertNotNull(specialties); + assertEquals(0, specialties.getSampleRate()); + assertFalse(specialties.hasSampleStartFlag()); + + assertNull(traceConfigs.getTraceConfig(Collections.singletonList("example.com/greet"))); + } + + @Test + void testCreateWithEmptyConfigUsesDefaults() { + tested.create(DeclarativeConfigProperties.empty()); + + assertNull(samplingConfiguration().getTracingMode()); + assertNull(samplingConfiguration().getInternalTransactionSettings()); + assertTrue(samplingConfiguration().isTriggerTraceEnabled()); + } + + @Test + void testCreateDisablesTriggerTrace() { + tested.create(fromYaml("swo_http:\n trigger_trace: false\n")); + assertFalse(samplingConfiguration().isTriggerTraceEnabled()); + } + + @Test + void testCreateAppliesUrlSampleRates() { + tested.create( + fromYaml( + "swo_http:\n" + + " url_sample_rates:\n" + + " - .*/greet:\n" + + " sample_rate: 500000\n" + + " - .*/health:\n" + + " tracing_mode: never\n")); + TraceConfigs traceConfigs = internalTransactionSettings(); + + TraceConfig greet = + traceConfigs.getTraceConfig(Collections.singletonList("http://example.com/greet")); + assertEquals(500000, greet.getSampleRate()); + assertTrue(greet.hasSampleStartFlag()); + + TraceConfig health = + traceConfigs.getTraceConfig(Collections.singletonList("http://example.com/health")); + assertEquals(0, health.getSampleRate()); + assertFalse(health.hasSampleStartFlag()); + } + + @Test + void testCreateAppliesExtensionsMatcher() { + tested.create( + fromYaml( + "swo_http:\n" + + " transaction_settings:\n" + + " - extensions: [png, jpg]\n" + + " tracing: disabled\n")); + TraceConfigs traceConfigs = internalTransactionSettings(); + + assertNotNull( + traceConfigs.getTraceConfig(Collections.singletonList("http://example.com/logo.png"))); + assertNull(traceConfigs.getTraceConfig(Collections.singletonList("http://example.com/api"))); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("invalidConfigs") + void testCreateThrowsForInvalidConfig(String description, String yaml, String expectedMessage) { + tested.create(fromYaml("swo_http:\n tracing_mode: disabled\n trigger_trace: false\n")); + SamplingConfiguration previous = samplingConfiguration(); + + RuntimeException exception = + assertThrows(RuntimeException.class, () -> tested.create(fromYaml(yaml))); + + InvalidConfigException cause = + assertInstanceOf(InvalidConfigException.class, exception.getCause()); + assertTrue( + cause.getMessage().contains(expectedMessage), + () -> + "Expected message containing [" + expectedMessage + "] but was: " + cause.getMessage()); + assertSame(previous, samplingConfiguration()); + } + + static Stream invalidConfigs() { + return Stream.of( + Arguments.of( + "unknown tracing_mode", + "swo_http:\n tracing_mode: sometimes\n", + "Invalid tracing_mode : sometimes"), + Arguments.of( + "unknown transaction tracing value", + "swo_http:\n transaction_settings:\n - regex: .*\n tracing: maybe\n", + "Invalid \"tracing\" value [maybe]"), + Arguments.of( + "transaction setting without tracing", + "swo_http:\n transaction_settings:\n - regex: .*\n", + "Need to define \"tracing\""), + Arguments.of( + "transaction setting without matcher", + "swo_http:\n transaction_settings:\n - tracing: disabled\n", + "Cannot find proper matcher"), + Arguments.of( + "transaction setting with multiple matchers", + "swo_http:\n transaction_settings:\n" + + " - regex: .*\n extensions: [png]\n tracing: disabled\n", + "Multiple matchers found"), + Arguments.of( + "transaction setting with unknown key", + "swo_http:\n transaction_settings:\n" + + " - regex: .*\n tracing: disabled\n extra: extra\n", + "Unknown key(s) [extra]"), + Arguments.of( + "transaction setting with invalid regex", + "swo_http:\n transaction_settings:\n - regex: '[A-Za-z{'\n tracing: disabled\n", + "Failed to compile pattern [A-Za-z{"), + Arguments.of( + "url sample rate with invalid regex", + "swo_http:\n url_sample_rates:\n - '[A-Za-z{':\n sample_rate: 1\n", + "Failed to compile the url sample rate of url pattern [[A-Za-z{]"), + Arguments.of( + "url sample rate without sample_rate or tracing_mode", + "swo_http:\n url_sample_rates:\n - .*/greet:\n other: 1\n", + "Need to define either tracing_mode or sample_rate"), + Arguments.of( + "url sample rate with always and no sample_rate", + "swo_http:\n url_sample_rates:\n - .*/greet:\n tracing_mode: always\n", + "Define sample_rate if tracing_mode is \"always\"")); + } + + private static SamplingConfiguration samplingConfiguration() { + return SettingsManager.getSamplingConfiguration(); + } + + private static TraceConfigs internalTransactionSettings() { + return samplingConfiguration().getInternalTransactionSettings(); + } + + private DeclarativeConfigProperties fromYaml(String yaml) { + return DeclarativeConfiguration.toConfigProperties( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))) + .getStructured("swo_http"); + } + + private DeclarativeConfigProperties loadConfig() { + try (InputStream in = getClass().getResourceAsStream("/sdk-config.yaml")) { + return DeclarativeConfiguration.toConfigProperties(in) + .getStructured("tracer_provider", DeclarativeConfigProperties.empty()) + .getStructured("sampler", DeclarativeConfigProperties.empty()) + .getStructured("swo_http"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SamplerComponentProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SamplerComponentProviderTest.java deleted file mode 100644 index 621448e3..00000000 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SamplerComponentProviderTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * © SolarWinds Worldwide, LLC. All rights reserved. - * - * 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 - * - * http://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 com.solarwinds.opentelemetry.extensions.config.provider; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -class SamplerComponentProviderTest { - private final SamplerComponentProvider tested = new SamplerComponentProvider(); - - @Test - void testName() { - assertEquals("swo/sampler", tested.getName()); - } -} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java index 3a100dbf..6c53c7c3 100644 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java @@ -25,6 +25,7 @@ import com.solarwinds.joboe.config.ConfigManager; import com.solarwinds.joboe.config.ConfigProperty; +import com.solarwinds.opentelemetry.extensions.SharedNames; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.AttributeLimitsModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.BatchLogRecordProcessorModel; @@ -105,8 +106,7 @@ void testCustomize() { TracerProviderModel tracerProvider = openTelemetryConfigurationModel.getTracerProvider(); assertNotNull(tracerProvider); - SamplerModel sampler = tracerProvider.getSampler(); - assertNotNull(sampler.getExtensionProperties().get(SamplerComponentProvider.COMPONENT_NAME)); + assertNull(tracerProvider.getSampler()); assertEquals(1, tracerProvider.getProcessors().size()); PeriodicMetricReaderModel periodic = @@ -168,8 +168,7 @@ void testCustomize1() { TracerProviderModel tracerProvider = openTelemetryConfigurationModel.getTracerProvider(); assertNotNull(tracerProvider); - SamplerModel sampler = tracerProvider.getSampler(); - assertNotNull(sampler.getExtensionProperties().get(SamplerComponentProvider.COMPONENT_NAME)); + assertNull(tracerProvider.getSampler()); assertEquals(1, tracerProvider.getProcessors().size()); PeriodicMetricReaderModel periodic = @@ -600,7 +599,7 @@ void customizeShouldNotOverrideSamplerWhenOneIsAlreadyConfigured() { TracerProviderModel tracerProvider = openTelemetryConfigurationModel.getTracerProvider(); SamplerModel sampler = tracerProvider.getSampler(); - assertNull(sampler.getExtensionProperties().get(SamplerComponentProvider.COMPONENT_NAME)); + assertNull(sampler.getExtensionProperties().get(SharedNames.HTTP_SAMPLER_NAME)); assertTrue(sampler.getExtensionProperties().containsKey("cel")); } @@ -633,7 +632,7 @@ void logsNotConfiguredWhenLoggerProviderAbsent() { assertNull(openTelemetryConfigurationModel.getLoggerProvider()); assertNotNull(openTelemetryConfigurationModel.getTracerProvider()); - assertNotNull(openTelemetryConfigurationModel.getTracerProvider().getSampler()); + assertNull(openTelemetryConfigurationModel.getTracerProvider().getSampler()); assertNotNull(openTelemetryConfigurationModel.getMeterProvider()); } diff --git a/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-0.yaml b/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-0.yaml deleted file mode 100644 index 9b4c9361..00000000 --- a/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-0.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# sdk-config.yaml is a typical starting point for configuring the SDK, including exporting to -# localhost via OTLP. - -# NOTE: With the exception of env var substitution syntax (i.e. ${MY_ENV}), SDKs ignore -# environment variables when interpreting config files. This including ignoring all env -# vars defined in https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/. - -# The file format version. -file_format: "0.4" - -# Configure if the SDK is disabled or not. This is not required to be provided to ensure the SDK isn't disabled, the default value when this is not provided is for the SDK to be enabled. -disabled: false - -# Configure resource for all signals. -resource: - # Configure resource attributes. Entries have higher priority than entries from .resource.attributes_list. - # Entries must contain .name nand .value, and may optionally include .type, which defaults ot "string" if not set. The value must match the type. Values for .type include: string, bool, int, double, string_array, bool_array, int_array, double_array. - attributes: - - name: service.name - value: java-apm-smoke-test - -instrumentation/development: - java: - common: - default-enabled: true - swo_jdbc: - sql_tag: true - sql_tag_prepared: true - sql_tag_databases: - - postgresql - solarwinds: - agent.logging: - level: trace - agent.tracingMode: enabled - agent.transactionSettings: - - - tracing: "disabled" - agent.urlSampleRates: - - - extra: extra - example.com/greet: - sampleRate: 100000000 - tracing: "enabled" \ No newline at end of file diff --git a/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-1.yaml b/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-1.yaml deleted file mode 100644 index 4ff7b446..00000000 --- a/libs/shared/src/test/resources/sdk-config-bad-transaction-settings-1.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# sdk-config.yaml is a typical starting point for configuring the SDK, including exporting to -# localhost via OTLP. - -# NOTE: With the exception of env var substitution syntax (i.e. ${MY_ENV}), SDKs ignore -# environment variables when interpreting config files. This including ignoring all env -# vars defined in https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/. - -# The file format version. -file_format: "0.4" - -# Configure if the SDK is disabled or not. This is not required to be provided to ensure the SDK isn't disabled, the default value when this is not provided is for the SDK to be enabled. -disabled: false - -# Configure resource for all signals. -resource: - # Configure resource attributes. Entries have higher priority than entries from .resource.attributes_list. - # Entries must contain .name nand .value, and may optionally include .type, which defaults ot "string" if not set. The value must match the type. Values for .type include: string, bool, int, double, string_array, bool_array, int_array, double_array. - attributes: - - name: service.name - value: java-apm-smoke-test - -instrumentation/development: - java: - common: - default-enabled: true - swo_jdbc: - sql_tag: true - sql_tag_prepared: true - sql_tag_databases: - - postgresql - solarwinds: - agent.logging: - level: trace - agent.tracingMode: enabled - agent.transactionSettings: - - - regex: ".*/specialties" - tracing: "disabled" - extension: - - png - - jpeg - - exe - agent.urlSampleRates: - - - \[A-Za-z{: - sampleRate: 100000000 - tracing: "enabled" \ No newline at end of file diff --git a/libs/shared/src/test/resources/sdk-config.yaml b/libs/shared/src/test/resources/sdk-config.yaml index 47e56c13..68e762a6 100644 --- a/libs/shared/src/test/resources/sdk-config.yaml +++ b/libs/shared/src/test/resources/sdk-config.yaml @@ -69,7 +69,19 @@ tracer_provider: sampler: # Configure sampler. - swo/sampler: + swo_http: + tracing_mode: enabled + trigger_trace: true + transaction_settings: + - regex: ".*/specialties" + tracing: "disabled" + url_sample_rates: + - example.com/greet: + sample_rate: 100000000 + tracing_mode: "enabled" + proxy: + host: proxy.example.com + port: 8080 # Configure meter provider. meter_provider: @@ -146,16 +158,7 @@ instrumentation/development: solarwinds: agent.logging: level: trace - agent.tracingMode: enabled - agent.transactionSettings: - - - regex: ".*/specialties" - tracing: "disabled" - agent.urlSampleRates: - - - example.com/greet: - sampleRate: 100000000 - tracing: "enabled" - agent.triggerTrace: true + agent.spanStacktraceFilters: + - http.request.method agent.serviceKey: ${SW_APM_SERVICE_KEY} agent.collector: apm.collector.na-01.st-ssp.solarwinds.com \ No newline at end of file diff --git a/smoke-tests/sdk-config.yaml b/smoke-tests/sdk-config.yaml index 85ff14e3..cac43f20 100644 --- a/smoke-tests/sdk-config.yaml +++ b/smoke-tests/sdk-config.yaml @@ -96,7 +96,12 @@ tracer_provider: root: cel_based: fallback_sampler: - swo/sampler: + swo_http: + service_key: ${SW_APM_SERVICE_KEY} + collector: apm.collector.na-01.st-ssp.solarwinds.com + transaction_settings: + - regex: ".*/specialties" + tracing: "disabled" expressions: - action: DROP expression: spanKind == 'SERVER' && attribute['http.request.method'] == 'HEAD' @@ -176,9 +181,5 @@ instrumentation/development: sql_tag_databases: - postgresql solarwinds: - agent.transactionSettings: - - - regex: ".*/specialties" - tracing: "disabled" agent.serviceKey: ${SW_APM_SERVICE_KEY} agent.collector: apm.collector.na-01.st-ssp.solarwinds.com From 53881d5854db3b1cf7c9f38ab6ea4541b889112b Mon Sep 17 00:00:00 2001 From: cleverchuk Date: Thu, 29 Oct 2026 14:31:43 -0400 Subject: [PATCH 2/2] use spec name and value --- custom/src/test/resources/sdk-config.yaml | 2 +- .../AbstractSamplerComponentProvider.java | 53 ++++++------------- .../AbstractSamplerComponentProviderTest.java | 27 +++++----- .../shared/src/test/resources/sdk-config.yaml | 6 +-- 4 files changed, 34 insertions(+), 54 deletions(-) diff --git a/custom/src/test/resources/sdk-config.yaml b/custom/src/test/resources/sdk-config.yaml index 43bfcdef..501af9f9 100644 --- a/custom/src/test/resources/sdk-config.yaml +++ b/custom/src/test/resources/sdk-config.yaml @@ -69,7 +69,7 @@ tracer_provider: # Configure sampler. swo_http: service_key: token:service - tracing_mode: enabled + tracing_enabled: true # Configure meter provider. meter_provider: diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProvider.java index 9a262351..5e0d2259 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProvider.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProvider.java @@ -45,9 +45,9 @@ public abstract class AbstractSamplerComponentProvider implements ComponentProvider { private static final Logger logger = LoggerFactory.getLogger(); - private static final String TRACING_MODE = "tracing_mode"; + private static final String TRACING_ENABLED = "tracing_enabled"; - private static final String TRIGGER_TRACE = "trigger_trace"; + private static final String TRIGGER_TRACE_ENABLED = "trigger_trace_enabled"; private static final String TRANSACTION_SETTINGS = "transaction_settings"; @@ -75,7 +75,8 @@ public Sampler create(DeclarativeConfigProperties declarativeConfigProperties) { SamplingConfiguration.SamplingConfigurationBuilder builder = SamplingConfiguration.builder() .tracingMode(parseTracingMode(declarativeConfigProperties)) - .triggerTraceEnabled(declarativeConfigProperties.getBoolean(TRIGGER_TRACE, true)) + .triggerTraceEnabled( + declarativeConfigProperties.getBoolean(TRIGGER_TRACE_ENABLED, true)) .internalTransactionSettings(parseTraceConfigs(declarativeConfigProperties)); parseConfig(declarativeConfigProperties, builder); @@ -97,19 +98,12 @@ protected void parseConfig( protected abstract SettingsFetcher createSettingsFetcher(DeclarativeConfigProperties properties) throws InvalidConfigException; - private TracingMode parseTracingMode(DeclarativeConfigProperties properties) - throws InvalidConfigException { - String mode = properties.getString(TRACING_MODE); - if (mode == null) { + private TracingMode parseTracingMode(DeclarativeConfigProperties properties) { + Boolean tracingEnabled = properties.getBoolean(TRACING_ENABLED); + if (tracingEnabled == null) { return null; } - - TracingMode tracingMode = TracingMode.fromString(mode); - if (tracingMode == null) { - throw new InvalidConfigException( - "Invalid " + TRACING_MODE + " : " + mode + ", must be \"disabled\" or \"enabled\""); - } - return tracingMode; + return tracingEnabled ? TracingMode.ENABLED : TracingMode.DISABLED; } private TraceConfigs parseTraceConfigs(DeclarativeConfigProperties properties) @@ -255,28 +249,12 @@ private TraceConfigs parseUrlSampleRates(List urlSa private TraceConfig parseUrlTraceConfig(DeclarativeConfigProperties urlConfig, String url) throws InvalidConfigException { Integer sampleRate = urlConfig.getInt(SAMPLE_RATE); - String tracingModeString = urlConfig.getString(TRACING_MODE); - TracingMode tracingMode = null; - - if (tracingModeString != null) { - tracingMode = TracingMode.fromString(tracingModeString); - if (tracingMode == null) { - throw new InvalidConfigException( - "Invalid " - + TRACING_MODE - + " value [" - + tracingModeString - + "], must be \"never\" if default is to be overridden; otherwise do not specify" - + " this property in [" - + url - + "]"); - } - } + Boolean tracingEnabled = urlConfig.getBoolean(TRACING_ENABLED); - if (sampleRate == null && tracingMode == null) { + if (sampleRate == null && tracingEnabled == null) { throw new InvalidConfigException( "Need to define either " - + TRACING_MODE + + TRACING_ENABLED + " or " + SAMPLE_RATE + ", but found none in [" @@ -285,16 +263,15 @@ private TraceConfig parseUrlTraceConfig(DeclarativeConfigProperties urlConfig, S } if (sampleRate == null) { - if (tracingMode == TracingMode.ALWAYS) { + if (tracingEnabled) { throw new InvalidConfigException( - "Define " + SAMPLE_RATE + " if " + TRACING_MODE + " is \"always\" in [" + url + "]"); + "Define " + SAMPLE_RATE + " if " + TRACING_ENABLED + " is true in [" + url + "]"); } sampleRate = 0; } - if (tracingMode == null) { - tracingMode = TracingMode.ALWAYS; - } + TracingMode tracingMode = + tracingEnabled == null || tracingEnabled ? TracingMode.ENABLED : TracingMode.DISABLED; return new TraceConfig(sampleRate, SampleRateSource.FILE, tracingMode.toFlags()); } } diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProviderTest.java index 0c39a083..bd0e75c1 100644 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProviderTest.java +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/AbstractSamplerComponentProviderTest.java @@ -127,10 +127,16 @@ void testCreateWithEmptyConfigUsesDefaults() { @Test void testCreateDisablesTriggerTrace() { - tested.create(fromYaml("swo_http:\n trigger_trace: false\n")); + tested.create(fromYaml("swo_http:\n trigger_trace_enabled: false\n")); assertFalse(samplingConfiguration().isTriggerTraceEnabled()); } + @Test + void testCreateDisablesTracing() { + tested.create(fromYaml("swo_http:\n tracing_enabled: false\n")); + assertEquals(TracingMode.DISABLED, samplingConfiguration().getTracingMode()); + } + @Test void testCreateAppliesUrlSampleRates() { tested.create( @@ -140,7 +146,7 @@ void testCreateAppliesUrlSampleRates() { + " - .*/greet:\n" + " sample_rate: 500000\n" + " - .*/health:\n" - + " tracing_mode: never\n")); + + " tracing_enabled: false\n")); TraceConfigs traceConfigs = internalTransactionSettings(); TraceConfig greet = @@ -172,7 +178,8 @@ void testCreateAppliesExtensionsMatcher() { @ParameterizedTest(name = "{0}") @MethodSource("invalidConfigs") void testCreateThrowsForInvalidConfig(String description, String yaml, String expectedMessage) { - tested.create(fromYaml("swo_http:\n tracing_mode: disabled\n trigger_trace: false\n")); + tested.create( + fromYaml("swo_http:\n tracing_enabled: false\n trigger_trace_enabled: false\n")); SamplingConfiguration previous = samplingConfiguration(); RuntimeException exception = @@ -189,10 +196,6 @@ void testCreateThrowsForInvalidConfig(String description, String yaml, String ex static Stream invalidConfigs() { return Stream.of( - Arguments.of( - "unknown tracing_mode", - "swo_http:\n tracing_mode: sometimes\n", - "Invalid tracing_mode : sometimes"), Arguments.of( "unknown transaction tracing value", "swo_http:\n transaction_settings:\n - regex: .*\n tracing: maybe\n", @@ -224,13 +227,13 @@ static Stream invalidConfigs() { "swo_http:\n url_sample_rates:\n - '[A-Za-z{':\n sample_rate: 1\n", "Failed to compile the url sample rate of url pattern [[A-Za-z{]"), Arguments.of( - "url sample rate without sample_rate or tracing_mode", + "url sample rate without sample_rate or tracing_enabled", "swo_http:\n url_sample_rates:\n - .*/greet:\n other: 1\n", - "Need to define either tracing_mode or sample_rate"), + "Need to define either tracing_enabled or sample_rate"), Arguments.of( - "url sample rate with always and no sample_rate", - "swo_http:\n url_sample_rates:\n - .*/greet:\n tracing_mode: always\n", - "Define sample_rate if tracing_mode is \"always\"")); + "url sample rate with tracing enabled and no sample_rate", + "swo_http:\n url_sample_rates:\n - .*/greet:\n tracing_enabled: true\n", + "Define sample_rate if tracing_enabled is true")); } private static SamplingConfiguration samplingConfiguration() { diff --git a/libs/shared/src/test/resources/sdk-config.yaml b/libs/shared/src/test/resources/sdk-config.yaml index 68e762a6..0b8addc4 100644 --- a/libs/shared/src/test/resources/sdk-config.yaml +++ b/libs/shared/src/test/resources/sdk-config.yaml @@ -70,15 +70,15 @@ tracer_provider: sampler: # Configure sampler. swo_http: - tracing_mode: enabled - trigger_trace: true + tracing_enabled: true + trigger_trace_enabled: true transaction_settings: - regex: ".*/specialties" tracing: "disabled" url_sample_rates: - example.com/greet: sample_rate: 100000000 - tracing_mode: "enabled" + tracing_enabled: true proxy: host: proxy.example.com port: 8080