Skip to content

Commit a0e206a

Browse files
[File based config] Support for nocode rules embedded in declarative config YAML (#2623)
* Support for nocode rules embedded in declarative config YAML * Updated no codew instrumentation config root node name to match the one used by .NET * Embedded rules and rules file mutually exclusive for declarative config * Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/instrumentation/nocode/NocodeInitializer.java Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --------- Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com>
1 parent 35f8cc1 commit a0e206a

File tree

7 files changed

+333
-43
lines changed

7 files changed

+333
-43
lines changed

bootstrap/src/main/java/com/splunk/opentelemetry/javaagent/bootstrap/nocode/NocodeRules.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ public static Iterable<Rule> getGlobalRules() {
5353
public static Rule find(int id) {
5454
return ruleMap.get(id);
5555
}
56+
57+
public static void clearGlobalRules() {
58+
ruleMap.clear();
59+
}
5660
}

custom/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfigureUtil.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import io.opentelemetry.api.OpenTelemetry;
2222
import io.opentelemetry.api.incubator.ExtendedOpenTelemetry;
23+
import io.opentelemetry.api.incubator.config.ConfigProvider;
2324
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
2425
import io.opentelemetry.common.ComponentLoader;
2526
import io.opentelemetry.instrumentation.config.bridge.ConfigPropertiesBackedConfigProvider;
@@ -54,12 +55,17 @@ public static ConfigProperties getConfig(AutoConfiguredOpenTelemetrySdk sdk) {
5455
}
5556

5657
public static boolean isDeclarativeConfig(AutoConfiguredOpenTelemetrySdk sdk) {
58+
ConfigProvider configProvider = getConfigProvider(sdk);
59+
return configProvider != null
60+
&& !(configProvider instanceof ConfigPropertiesBackedConfigProvider);
61+
}
62+
63+
public static ConfigProvider getConfigProvider(AutoConfiguredOpenTelemetrySdk sdk) {
5764
OpenTelemetry openTelemetry = sdk.getOpenTelemetrySdk();
5865
if (openTelemetry instanceof ExtendedOpenTelemetry) {
59-
return !(((ExtendedOpenTelemetry) openTelemetry).getConfigProvider()
60-
instanceof ConfigPropertiesBackedConfigProvider);
66+
return ((ExtendedOpenTelemetry) openTelemetry).getConfigProvider();
6167
}
62-
return false;
68+
return null;
6369
}
6470

6571
public static DeclarativeConfigProperties getDistributionConfig(

instrumentation/nocode-testing/build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1+
plugins {
2+
id("splunk.instrumentation-conventions")
3+
}
4+
15
dependencies {
26
testImplementation(project(":bootstrap"))
7+
testImplementation(project(":custom"))
38
testImplementation(project(":instrumentation:nocode"))
9+
testCompileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling")
10+
testRuntimeOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling") {
11+
exclude("io.opentelemetry.javaagent", "opentelemetry-javaagent-bootstrap")
12+
}
413
testImplementation("org.snakeyaml:snakeyaml-engine")
514
}
615

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunk.opentelemetry.instrumentation.nocode;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
21+
import static org.mockito.Mockito.mock;
22+
import static org.mockito.Mockito.when;
23+
24+
import com.splunk.opentelemetry.javaagent.bootstrap.nocode.NocodeRules;
25+
import com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil;
26+
import io.opentelemetry.api.incubator.config.ConfigProvider;
27+
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
28+
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
29+
import java.io.IOException;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
import java.util.ArrayList;
33+
import java.util.Collections;
34+
import java.util.List;
35+
import org.junit.jupiter.api.AfterEach;
36+
import org.junit.jupiter.api.BeforeEach;
37+
import org.junit.jupiter.api.Nested;
38+
import org.junit.jupiter.api.Test;
39+
import org.junit.jupiter.api.extension.RegisterExtension;
40+
import org.junit.jupiter.api.io.TempDir;
41+
42+
class NocodeInitializerTest {
43+
@RegisterExtension final AutoCleanupExtension autoCleanup = AutoCleanupExtension.create();
44+
private List<NocodeRules.Rule> previousRules;
45+
46+
@BeforeEach
47+
void captureRules() {
48+
previousRules = new ArrayList<>();
49+
NocodeRules.getGlobalRules().forEach(previousRules::add);
50+
}
51+
52+
@AfterEach
53+
void restoreRules() {
54+
NocodeRules.setGlobalRules(previousRules);
55+
}
56+
57+
@Test
58+
void shouldReadNoRulesIfNoFilePathProvided() {
59+
assertThat(NocodeInitializer.readRulesFromFile(null)).isEmpty();
60+
assertThat(NocodeInitializer.readRulesFromFile(" ")).isEmpty();
61+
}
62+
63+
@Test
64+
void shouldSurviveMissingRulesFile() {
65+
assertThat(NocodeInitializer.readRulesFromFile("nonexistingFile.yaml")).isEmpty();
66+
}
67+
68+
@Test
69+
void processRulesFromFile(@TempDir Path tempDir) throws IOException {
70+
// given
71+
String rulesYaml = "- class: foo.Foo\n method: bar\n";
72+
Path rulesFilePath = writeRulesFile(tempDir, rulesYaml);
73+
74+
AutoConfiguredOpenTelemetrySdk sdk =
75+
AutoConfiguredOpenTelemetrySdk.builder()
76+
.addPropertiesSupplier(
77+
() ->
78+
Collections.singletonMap(
79+
"splunk.otel.instrumentation.nocode.yml.file", rulesFilePath.toString()))
80+
.build();
81+
autoCleanup.deferCleanup(sdk.getOpenTelemetrySdk());
82+
83+
// when
84+
new NocodeInitializer().beforeAgent(sdk);
85+
86+
// then
87+
assertThat(NocodeRules.getGlobalRules().iterator().hasNext()).isTrue();
88+
}
89+
90+
@Nested
91+
class DeclarativeConfigRules {
92+
@Test
93+
void shouldSurviveMissingInstrumentationConfigNode() {
94+
// given
95+
ConfigProvider provider = mock(ConfigProvider.class);
96+
when(provider.getInstrumentationConfig("splunk")).thenReturn(null);
97+
98+
// when
99+
List<NocodeRules.Rule> rules = NocodeInitializer.readDeclarativeConfigRules(provider);
100+
101+
// then
102+
assertThat(rules).isEmpty();
103+
}
104+
105+
@Test
106+
void throwExceptionWhenRulesFileAndEmbeddedRulesAreDefined(@TempDir Path tempDir)
107+
throws IOException {
108+
// given
109+
String yaml =
110+
"""
111+
file_format: "1.0-rc.3"
112+
instrumentation/development:
113+
java:
114+
splunk:
115+
no_code_file: "/tmp/does-not-exist.yml"
116+
no_code:
117+
- class: foo.Foo
118+
method: bar
119+
""";
120+
121+
AutoConfiguredOpenTelemetrySdk sdk =
122+
DeclarativeConfigTestUtil.createAutoConfiguredSdk(yaml, tempDir, autoCleanup);
123+
124+
// then
125+
assertThatThrownBy(() -> new NocodeInitializer().beforeAgent(sdk))
126+
.isInstanceOf(IllegalStateException.class);
127+
}
128+
129+
@Test
130+
void processRulesFromRulesFile(@TempDir Path tempDir) throws IOException {
131+
// given
132+
Path rulesFile = writeRulesFile(tempDir, "- class: foo.Foo\n method: bar\n");
133+
134+
String yaml =
135+
"""
136+
file_format: "1.0-rc.3"
137+
instrumentation/development:
138+
java:
139+
splunk:
140+
no_code_file: "%s"
141+
"""
142+
.formatted(rulesFile.toString());
143+
144+
AutoConfiguredOpenTelemetrySdk sdk =
145+
DeclarativeConfigTestUtil.createAutoConfiguredSdk(yaml, tempDir, autoCleanup);
146+
147+
// when
148+
new NocodeInitializer().beforeAgent(sdk);
149+
150+
// then
151+
assertThat(NocodeRules.getGlobalRules().iterator().hasNext()).isTrue();
152+
}
153+
154+
@Test
155+
void processRulesFromDeclarativeConfiguration(@TempDir Path tempDir) throws IOException {
156+
// given
157+
String yaml =
158+
"""
159+
file_format: "1.0-rc.3"
160+
instrumentation/development:
161+
java:
162+
splunk:
163+
no_code:
164+
- class: foo.Foo
165+
method: bar
166+
""";
167+
168+
AutoConfiguredOpenTelemetrySdk sdk =
169+
DeclarativeConfigTestUtil.createAutoConfiguredSdk(yaml, tempDir, autoCleanup);
170+
171+
// when
172+
new NocodeInitializer().beforeAgent(sdk);
173+
174+
// then
175+
assertThat(NocodeRules.getGlobalRules().iterator().hasNext()).isTrue();
176+
}
177+
}
178+
179+
private static Path writeRulesFile(Path tempDir, String yaml) throws IOException {
180+
Path file = tempDir.resolve("nocode.yml");
181+
Files.writeString(file, yaml);
182+
return file;
183+
}
184+
}

instrumentation/nocode-testing/src/test/java/com/splunk/opentelemetry/instrumentation/nocode/YamlParserTest.java

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,17 @@
1616

1717
package com.splunk.opentelemetry.instrumentation.nocode;
1818

19+
import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
1922
import static org.junit.jupiter.api.Assertions.assertEquals;
20-
import static org.junit.jupiter.api.Assertions.fail;
2123

24+
import com.splunk.opentelemetry.javaagent.bootstrap.nocode.NocodeRules;
25+
import com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil;
26+
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
27+
import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil;
28+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
29+
import java.util.List;
2230
import org.junit.jupiter.api.Test;
2331
import org.junit.jupiter.params.ParameterizedTest;
2432
import org.junit.jupiter.params.provider.ValueSource;
@@ -93,11 +101,33 @@ void testBasicRuleParsesOK() throws Exception {
93101
})
94102
// spotless:on
95103
void invalidYamlIsInvalid(String yaml) {
96-
try {
97-
YamlParser.parseFromString(yaml);
98-
fail("Expected an exception parsing broken yaml");
99-
} catch (Exception expected) {
100-
// ok
101-
}
104+
assertThatThrownBy(() -> YamlParser.parseFromString(yaml)).isInstanceOf(RuntimeException.class);
105+
}
106+
107+
@Test
108+
void shouldParseFromDeclarativeConfigYaml() {
109+
// given
110+
var yaml =
111+
"""
112+
file_format: "1.0-rc.3"
113+
instrumentation/development:
114+
java:
115+
splunk:
116+
no_code:
117+
- class: foo.Foo
118+
method: foo
119+
- class: foo.Foo
120+
method: throwSomething
121+
""";
122+
OpenTelemetryConfigurationModel model = DeclarativeConfigTestUtil.parse(yaml);
123+
DeclarativeConfigProperties splunkRoot =
124+
AutoConfigureUtil.getInstrumentationConfig(model).getStructured("splunk", empty());
125+
List<DeclarativeConfigProperties> ruleNodes = splunkRoot.getStructuredList("no_code");
126+
127+
// when
128+
List<NocodeRules.Rule> rules = YamlParser.parseFromDeclarativeConfig(ruleNodes);
129+
130+
// then
131+
assertThat(rules).hasSize(2);
102132
}
103133
}

instrumentation/nocode/src/main/java/com/splunk/opentelemetry/instrumentation/nocode/NocodeInitializer.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
package com.splunk.opentelemetry.instrumentation.nocode;
1818

1919
import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getConfig;
20+
import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getConfigProvider;
21+
import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.isDeclarativeConfig;
2022

2123
import com.google.auto.service.AutoService;
2224
import com.splunk.opentelemetry.javaagent.bootstrap.nocode.NocodeRules;
25+
import io.opentelemetry.api.incubator.config.ConfigProvider;
26+
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
2327
import io.opentelemetry.javaagent.tooling.BeforeAgentListener;
2428
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
25-
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
2629
import java.util.Collections;
2730
import java.util.List;
31+
import java.util.Objects;
2832
import java.util.logging.Level;
2933
import java.util.logging.Logger;
3034

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

3640
@Override
37-
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
38-
ConfigProperties config = getConfig(autoConfiguredOpenTelemetrySdk);
39-
String yamlFileName = config.getString(NOCODE_YMLFILE);
40-
if (yamlFileName == null || yamlFileName.trim().isEmpty()) {
41-
return;
41+
public void beforeAgent(AutoConfiguredOpenTelemetrySdk sdk) {
42+
List<NocodeRules.Rule> rules =
43+
isDeclarativeConfig(sdk)
44+
? readDeclarativeConfigRules(getConfigProvider(sdk))
45+
: readRulesFromFile(getConfig(sdk).getString(NOCODE_YMLFILE));
46+
47+
NocodeRules.setGlobalRules(rules);
48+
}
49+
50+
static List<NocodeRules.Rule> readDeclarativeConfigRules(ConfigProvider configProvider) {
51+
Objects.requireNonNull(configProvider);
52+
DeclarativeConfigProperties config = configProvider.getInstrumentationConfig("splunk");
53+
54+
if (config != null) {
55+
String rulesFile = config.getString("no_code_file");
56+
List<DeclarativeConfigProperties> noCodeRules = config.getStructuredList("no_code");
57+
58+
if (noCodeRules != null) {
59+
if (rulesFile != null) {
60+
throw new IllegalStateException(
61+
"Ambiguous NoCode rules definition (choose declarative config or rules file, not both)");
62+
}
63+
return YamlParser.parseFromDeclarativeConfig(noCodeRules);
64+
}
65+
66+
return readRulesFromFile(rulesFile);
4267
}
68+
69+
return Collections.emptyList();
70+
}
71+
72+
static List<NocodeRules.Rule> readRulesFromFile(String yamlFileName) {
4373
List<NocodeRules.Rule> instrumentationRules = Collections.emptyList();
74+
if (yamlFileName == null || yamlFileName.trim().isEmpty()) {
75+
return instrumentationRules;
76+
}
4477
try {
4578
instrumentationRules = YamlParser.parseFromFile(yamlFileName);
4679
// can throw IllegalArgument and various other RuntimeExceptions too, not just IOException
4780
} catch (Exception e) {
4881
logger.log(Level.SEVERE, "Can't load configured nocode yaml.", e);
4982
}
50-
NocodeRules.setGlobalRules(instrumentationRules);
83+
return instrumentationRules;
5184
}
5285
}

0 commit comments

Comments
 (0)