Skip to content

Commit d0df278

Browse files
authored
add config logging on agent startup (#835)
* add config logging on agent startup * adjust to gradle passing in the path to the jar * remove unnecessary line
1 parent 18c785c commit d0df278

File tree

5 files changed

+231
-2
lines changed

5 files changed

+231
-2
lines changed

custom/build.gradle.kts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ tasks {
7575
}
7676
}
7777

78-
tasks.withType<Test> {
78+
tasks.withType<Test>().configureEach {
79+
val agentJarTask = project(":agent").tasks.named<Jar>("jar")
80+
dependsOn(agentJarTask)
81+
systemProperty("elastic.otel.agent.jar.path", agentJarTask.get().archiveFile.get().asFile.absolutePath)
82+
7983
val overrideConfig = project.properties["elastic.otel.overwrite.config.docs"]
8084
if (overrideConfig != null) {
8185
systemProperty("elastic.otel.overwrite.config.docs", overrideConfig)

custom/src/main/java/co/elastic/otel/ElasticAutoConfigurationCustomizerProvider.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package co.elastic.otel;
2020

2121
import co.elastic.otel.compositesampling.DynamicCompositeParentBasedTraceIdRatioBasedSampler;
22+
import co.elastic.otel.config.ConfigLoggingAgentListener;
2223
import co.elastic.otel.dynamicconfig.BlockableLogRecordExporter;
2324
import co.elastic.otel.dynamicconfig.BlockableMetricExporter;
2425
import co.elastic.otel.dynamicconfig.BlockableSpanExporter;
@@ -104,6 +105,8 @@ static Map<String, String> propertiesCustomizer(ConfigProperties configPropertie
104105
deltaMetricsTemporality(config, configProperties);
105106
resourceProviders(config, configProperties);
106107
spanStackTrace(config, configProperties);
108+
ConfigLoggingAgentListener.logTheConfig(
109+
configProperties.getBoolean(ConfigLoggingAgentListener.LOG_THE_CONFIG, true));
107110

108111
return config;
109112
}

custom/src/main/java/co/elastic/otel/compositesampling/DynamicCompositeParentBasedTraceIdRatioBasedSampler.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public enum DynamicCompositeParentBasedTraceIdRatioBasedSampler implements Sampl
3232

3333
static final double DEFAULT_TRACEIDRATIO_SAMPLE_RATIO = 1.0d;
3434

35-
private static Sampler delegate = newSampler(DEFAULT_TRACEIDRATIO_SAMPLE_RATIO);
35+
private static volatile double latestRatio;
36+
private static volatile Sampler delegate = newSampler(DEFAULT_TRACEIDRATIO_SAMPLE_RATIO);
3637

3738
public static void setRatio(double ratio) {
3839
delegate = newSampler(ratio);
@@ -55,6 +56,15 @@ public String getDescription() {
5556
}
5657

5758
private static Sampler newSampler(double ratio) {
59+
latestRatio = ratio;
5860
return ConsistentSampler.parentBased(ConsistentSampler.probabilityBased(ratio));
5961
}
62+
63+
public String toString() {
64+
return "DynamicCompositeParentBasedTraceIdRatioBasedSampler(ratio="
65+
+ latestRatio
66+
+ ", "
67+
+ delegate.toString()
68+
+ ")";
69+
}
6070
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.otel.config;
20+
21+
import com.google.auto.service.AutoService;
22+
import io.opentelemetry.javaagent.extension.AgentListener;
23+
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
24+
import java.util.logging.Logger;
25+
26+
@AutoService(AgentListener.class)
27+
public class ConfigLoggingAgentListener implements AgentListener {
28+
public static final String LOG_THE_CONFIG =
29+
"elastic.otel.java.experimental.configuration.logging.enabled";
30+
private static final Logger logger = Logger.getLogger(ConfigLoggingAgentListener.class.getName());
31+
32+
private static boolean logTheConfig = true;
33+
34+
public static synchronized void logTheConfig(boolean logTheConfig) {
35+
ConfigLoggingAgentListener.logTheConfig = logTheConfig;
36+
}
37+
38+
@Override
39+
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
40+
if (logTheConfig) {
41+
logger.info(autoConfiguredOpenTelemetrySdk.toString());
42+
}
43+
}
44+
45+
@Override
46+
public int order() {
47+
return 1;
48+
}
49+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.otel.config;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
import java.io.BufferedWriter;
24+
import java.io.File;
25+
import java.io.FileWriter;
26+
import java.io.IOException;
27+
import java.io.InputStream;
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
import java.util.concurrent.TimeUnit;
31+
import org.junit.jupiter.api.AfterAll;
32+
import org.junit.jupiter.api.BeforeAll;
33+
import org.junit.jupiter.api.Test;
34+
35+
public class ConfigLoggingAgentListenerTest {
36+
static final String TARGET_CLASS_NAME = "TempTest321";
37+
static String[] identifyingStrings = {
38+
"ConfigLoggingAgentListener - AutoConfiguredOpenTelemetrySdk{",
39+
"tracerProvider=SdkTracerProvider"
40+
};
41+
42+
private static String agentJarFile;
43+
private static File testTargetClass;
44+
45+
@BeforeAll
46+
public static void setup() throws IOException {
47+
agentJarFile = getAgentJarFile();
48+
testTargetClass = createTestTarget();
49+
}
50+
51+
@AfterAll
52+
public static void teardown() throws IOException {
53+
testTargetClass.delete();
54+
}
55+
56+
@Test
57+
public void checkLogConfigPresent() throws IOException {
58+
String output = executeCommand(createTestTargetCommand(true), 20);
59+
for (String identifyingString : identifyingStrings) {
60+
assertThat(output).contains(identifyingString);
61+
}
62+
}
63+
64+
@Test
65+
public void checkLogConfigAbsent() throws IOException {
66+
String output = executeCommand(createTestTargetCommand(false), 20);
67+
for (String identifyingString : identifyingStrings) {
68+
assertThat(output).doesNotContain(identifyingString);
69+
}
70+
}
71+
72+
static List<String> createTestTargetCommand(boolean logConfig) throws IOException {
73+
List<String> command = new ArrayList<>();
74+
command.add("java");
75+
command.add("-Xmx32m");
76+
command.add("-javaagent:" + agentJarFile);
77+
// Only on false, ie test the 'true' default with no option
78+
if (!logConfig) {
79+
command.add("-D" + ConfigLoggingAgentListener.LOG_THE_CONFIG + "=false");
80+
}
81+
command.add(testTargetClass.getAbsolutePath());
82+
return command;
83+
}
84+
85+
static File createTestTarget() throws IOException {
86+
String targetClass =
87+
"public class "
88+
+ TARGET_CLASS_NAME
89+
+ " {\n"
90+
+ " public static void main(String[] args) {\n"
91+
+ " System.out.println(\"Test started\");\n"
92+
// 15 seconds to give the agent plenty of time to start and be fully initialized
93+
+ " try {Thread.sleep(15_000L);} catch (InterruptedException e) {}\n"
94+
+ " System.out.println(\"Test ended\");\n"
95+
+ " }\n"
96+
+ "}\n";
97+
File targetClassFile =
98+
new File(System.getProperty("java.io.tmpdir"), TARGET_CLASS_NAME + ".java");
99+
targetClassFile.deleteOnExit();
100+
try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetClassFile))) {
101+
writer.write(targetClass);
102+
}
103+
return targetClassFile;
104+
}
105+
106+
static String getAgentJarFile() {
107+
String path = System.getProperty("elastic.otel.agent.jar.path");
108+
if (path == null) {
109+
throw new IllegalStateException("elastic.otel.agent.jar.path system property is not set");
110+
}
111+
return path;
112+
}
113+
114+
public static String executeCommand(List<String> command, int timeoutSeconds) throws IOException {
115+
ProcessBuilder pb = new ProcessBuilder(command);
116+
pb.redirectErrorStream(true);
117+
Process childProcess = pb.start();
118+
119+
StringBuilder commandOutput = new StringBuilder();
120+
121+
boolean isAlive = true;
122+
byte[] buffer = new byte[64 * 1000];
123+
InputStream in = childProcess.getInputStream();
124+
// stop trying if the time elapsed exceeds the timeout
125+
while (isAlive && (timeoutSeconds > 0)) {
126+
while (in.available() > 0) {
127+
int lengthRead = in.read(buffer, 0, buffer.length);
128+
commandOutput.append(new String(buffer, 0, lengthRead));
129+
}
130+
pauseSeconds(1);
131+
timeoutSeconds--;
132+
isAlive = childProcess.isAlive();
133+
}
134+
// it can die but still have output available buffered
135+
while (in.available() > 0) {
136+
int lengthRead = in.read(buffer, 0, buffer.length);
137+
commandOutput.append(new String(buffer, 0, lengthRead));
138+
}
139+
140+
// Cleanup as well as I can
141+
boolean exited = false;
142+
try {
143+
exited = childProcess.waitFor(3, TimeUnit.SECONDS);
144+
} catch (InterruptedException e) {
145+
}
146+
if (!exited) {
147+
childProcess.destroy();
148+
pauseSeconds(1);
149+
if (childProcess.isAlive()) {
150+
childProcess.destroyForcibly();
151+
}
152+
}
153+
154+
return commandOutput.toString();
155+
}
156+
157+
private static void pauseSeconds(int seconds) {
158+
try {
159+
Thread.sleep(seconds * 1_000L);
160+
} catch (InterruptedException e) {
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)