Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,8 @@ public static Iterable<Rule> getGlobalRules() {
public static Rule find(int id) {
return ruleMap.get(id);
}

public static void clearGlobalRules() {
ruleMap.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.incubator.ExtendedOpenTelemetry;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.common.ComponentLoader;
import io.opentelemetry.instrumentation.config.bridge.ConfigPropertiesBackedConfigProvider;
Expand Down Expand Up @@ -54,12 +55,17 @@ public static ConfigProperties getConfig(AutoConfiguredOpenTelemetrySdk sdk) {
}

public static boolean isDeclarativeConfig(AutoConfiguredOpenTelemetrySdk sdk) {
ConfigProvider configProvider = getConfigProvider(sdk);
return configProvider != null
&& !(configProvider instanceof ConfigPropertiesBackedConfigProvider);
}

public static ConfigProvider getConfigProvider(AutoConfiguredOpenTelemetrySdk sdk) {
OpenTelemetry openTelemetry = sdk.getOpenTelemetrySdk();
if (openTelemetry instanceof ExtendedOpenTelemetry) {
return !(((ExtendedOpenTelemetry) openTelemetry).getConfigProvider()
instanceof ConfigPropertiesBackedConfigProvider);
return ((ExtendedOpenTelemetry) openTelemetry).getConfigProvider();
}
return false;
return null;
}

public static DeclarativeConfigProperties getDistributionConfig(
Expand Down
9 changes: 9 additions & 0 deletions instrumentation/nocode-testing/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
plugins {
id("splunk.instrumentation-conventions")
}

dependencies {
testImplementation(project(":bootstrap"))
testImplementation(project(":custom"))
testImplementation(project(":instrumentation:nocode"))
testCompileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling")
testRuntimeOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling") {
exclude("io.opentelemetry.javaagent", "opentelemetry-javaagent-bootstrap")
}
testImplementation("org.snakeyaml:snakeyaml-engine")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright Splunk Inc.
*
* 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.splunk.opentelemetry.instrumentation.nocode;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.splunk.opentelemetry.javaagent.bootstrap.nocode.NocodeRules;
import com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;

class NocodeInitializerTest {
@RegisterExtension final AutoCleanupExtension autoCleanup = AutoCleanupExtension.create();
private List<NocodeRules.Rule> previousRules;

@BeforeEach
void captureRules() {
previousRules = new ArrayList<>();
NocodeRules.getGlobalRules().forEach(previousRules::add);
}

@AfterEach
void restoreRules() {
NocodeRules.setGlobalRules(previousRules);
}

@Test
void shouldReadNoRulesIfNoFilePathProvided() {
assertThat(NocodeInitializer.readRulesFromFile(null)).isEmpty();
assertThat(NocodeInitializer.readRulesFromFile(" ")).isEmpty();
}

@Test
void shouldSurviveMissingRulesFile() {
assertThat(NocodeInitializer.readRulesFromFile("nonexistingFile.yaml")).isEmpty();
}

@Test
void processRulesFromFile(@TempDir Path tempDir) throws IOException {
// given
String rulesYaml = "- class: foo.Foo\n method: bar\n";
Path rulesFilePath = writeRulesFile(tempDir, rulesYaml);

AutoConfiguredOpenTelemetrySdk sdk =
AutoConfiguredOpenTelemetrySdk.builder()
.addPropertiesSupplier(
() ->
Collections.singletonMap(
"splunk.otel.instrumentation.nocode.yml.file", rulesFilePath.toString()))
.build();
autoCleanup.deferCleanup(sdk.getOpenTelemetrySdk());

// when
new NocodeInitializer().beforeAgent(sdk);

// then
assertThat(NocodeRules.getGlobalRules().iterator().hasNext()).isTrue();
}

@Nested
class DeclarativeConfigRules {
@Test
void shouldSurviveMissingInstrumentationConfigNode() {
// given
ConfigProvider provider = mock(ConfigProvider.class);
when(provider.getInstrumentationConfig("splunk")).thenReturn(null);

// when
List<NocodeRules.Rule> rules = NocodeInitializer.readDeclarativeConfigRules(provider);

// then
assertThat(rules).isEmpty();
}

@Test
void throwExceptionWhenRulesFileAndEmbeddedRulesAreDefined(@TempDir Path tempDir)
throws IOException {
// given
String yaml =
"""
file_format: "1.0-rc.3"
instrumentation/development:
java:
splunk:
no_code_file: "/tmp/does-not-exist.yml"
no_code:
- class: foo.Foo
method: bar
""";

AutoConfiguredOpenTelemetrySdk sdk =
DeclarativeConfigTestUtil.createAutoConfiguredSdk(yaml, tempDir, autoCleanup);

// then
assertThatThrownBy(() -> new NocodeInitializer().beforeAgent(sdk))
.isInstanceOf(IllegalStateException.class);
}

@Test
void processRulesFromRulesFile(@TempDir Path tempDir) throws IOException {
// given
Path rulesFile = writeRulesFile(tempDir, "- class: foo.Foo\n method: bar\n");

String yaml =
"""
file_format: "1.0-rc.3"
instrumentation/development:
java:
splunk:
no_code_file: "%s"
"""
.formatted(rulesFile.toString());

AutoConfiguredOpenTelemetrySdk sdk =
DeclarativeConfigTestUtil.createAutoConfiguredSdk(yaml, tempDir, autoCleanup);

// when
new NocodeInitializer().beforeAgent(sdk);

// then
assertThat(NocodeRules.getGlobalRules().iterator().hasNext()).isTrue();
}

@Test
void processRulesFromDeclarativeConfiguration(@TempDir Path tempDir) throws IOException {
// given
String yaml =
"""
file_format: "1.0-rc.3"
instrumentation/development:
java:
splunk:
no_code:
- class: foo.Foo
method: bar
""";

AutoConfiguredOpenTelemetrySdk sdk =
DeclarativeConfigTestUtil.createAutoConfiguredSdk(yaml, tempDir, autoCleanup);

// when
new NocodeInitializer().beforeAgent(sdk);

// then
assertThat(NocodeRules.getGlobalRules().iterator().hasNext()).isTrue();
}
}

private static Path writeRulesFile(Path tempDir, String yaml) throws IOException {
Path file = tempDir.resolve("nocode.yml");
Files.writeString(file, yaml);
return file;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@

package com.splunk.opentelemetry.instrumentation.nocode;

import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

import com.splunk.opentelemetry.javaagent.bootstrap.nocode.NocodeRules;
import com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
Expand Down Expand Up @@ -93,11 +101,33 @@ void testBasicRuleParsesOK() throws Exception {
})
// spotless:on
void invalidYamlIsInvalid(String yaml) {
try {
YamlParser.parseFromString(yaml);
fail("Expected an exception parsing broken yaml");
} catch (Exception expected) {
// ok
}
assertThatThrownBy(() -> YamlParser.parseFromString(yaml)).isInstanceOf(RuntimeException.class);
}

@Test
void shouldParseFromDeclarativeConfigYaml() {
// given
var yaml =
"""
file_format: "1.0-rc.3"
instrumentation/development:
java:
splunk:
no_code:
- class: foo.Foo
method: foo
- class: foo.Foo
method: throwSomething
""";
OpenTelemetryConfigurationModel model = DeclarativeConfigTestUtil.parse(yaml);
DeclarativeConfigProperties splunkRoot =
AutoConfigureUtil.getInstrumentationConfig(model).getStructured("splunk", empty());
List<DeclarativeConfigProperties> ruleNodes = splunkRoot.getStructuredList("no_code");

// when
List<NocodeRules.Rule> rules = YamlParser.parseFromDeclarativeConfig(ruleNodes);

// then
assertThat(rules).hasSize(2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@
package com.splunk.opentelemetry.instrumentation.nocode;

import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getConfig;
import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getConfigProvider;
import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.isDeclarativeConfig;

import com.google.auto.service.AutoService;
import com.splunk.opentelemetry.javaagent.bootstrap.nocode.NocodeRules;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.javaagent.tooling.BeforeAgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -34,19 +38,48 @@ public class NocodeInitializer implements BeforeAgentListener {
private static final Logger logger = Logger.getLogger(NocodeInitializer.class.getName());

@Override
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ConfigProperties config = getConfig(autoConfiguredOpenTelemetrySdk);
String yamlFileName = config.getString(NOCODE_YMLFILE);
if (yamlFileName == null || yamlFileName.trim().isEmpty()) {
return;
public void beforeAgent(AutoConfiguredOpenTelemetrySdk sdk) {
List<NocodeRules.Rule> rules =
isDeclarativeConfig(sdk)
? readDeclarativeConfigRules(getConfigProvider(sdk))
: readRulesFromFile(getConfig(sdk).getString(NOCODE_YMLFILE));

NocodeRules.setGlobalRules(rules);
}

static List<NocodeRules.Rule> readDeclarativeConfigRules(ConfigProvider configProvider) {
Objects.requireNonNull(configProvider);
DeclarativeConfigProperties config = configProvider.getInstrumentationConfig("splunk");

if (config != null) {
String rulesFile = config.getString("no_code_file");
List<DeclarativeConfigProperties> noCodeRules = config.getStructuredList("no_code");

if (noCodeRules != null) {
if (rulesFile != null) {
throw new IllegalStateException(
"Ambiguous NoCode rules definition (choose declarative config or rules file, not both)");
}
return YamlParser.parseFromDeclarativeConfig(noCodeRules);
}

return readRulesFromFile(rulesFile);
}

return Collections.emptyList();
}

static List<NocodeRules.Rule> readRulesFromFile(String yamlFileName) {
List<NocodeRules.Rule> instrumentationRules = Collections.emptyList();
if (yamlFileName == null || yamlFileName.trim().isEmpty()) {
return instrumentationRules;
}
try {
instrumentationRules = YamlParser.parseFromFile(yamlFileName);
// can throw IllegalArgument and various other RuntimeExceptions too, not just IOException
} catch (Exception e) {
logger.log(Level.SEVERE, "Can't load configured nocode yaml.", e);
}
NocodeRules.setGlobalRules(instrumentationRules);
return instrumentationRules;
}
}
Loading