Skip to content

Commit c4a1f4d

Browse files
authored
connect opamp to dynamic config (#656)
* connect opamp to dynamic config * review feedback nicer code
1 parent c6fcd06 commit c4a1f4d

5 files changed

Lines changed: 314 additions & 19 deletions

File tree

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
import co.elastic.otel.dynamicconfig.BlockableLogRecordExporter;
2222
import co.elastic.otel.dynamicconfig.BlockableMetricExporter;
2323
import co.elastic.otel.dynamicconfig.BlockableSpanExporter;
24-
import co.elastic.otel.dynamicconfig.DynamicConfiguration;
25-
import co.elastic.otel.dynamicconfig.DynamicInstrumentation;
24+
import co.elastic.otel.dynamicconfig.CentralConfig;
2625
import com.google.auto.service.AutoService;
2726
import io.opentelemetry.api.common.AttributeKey;
2827
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
@@ -68,13 +67,13 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
6867

6968
autoConfiguration.addPropertiesCustomizer(
7069
ElasticAutoConfigurationCustomizerProvider::propertiesCustomizer);
70+
autoConfiguration.addResourceCustomizer(resourceProviders());
71+
// make sure this comes after anything that might set the service name
7172
autoConfiguration.addTracerProviderCustomizer(
7273
(providerBuilder, properties) -> {
73-
DynamicInstrumentation.setTracerConfigurator(
74-
providerBuilder, DynamicConfiguration.UpdatableConfigurator.INSTANCE);
74+
CentralConfig.init(providerBuilder, properties);
7575
return providerBuilder;
7676
});
77-
autoConfiguration.addResourceCustomizer(resourceProviders());
7877
}
7978

8079
private void configureExporterUserAgentHeaders(AutoConfigurationCustomizer autoConfiguration) {
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
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.dynamicconfig;
20+
21+
import co.elastic.opamp.client.CentralConfigurationManager;
22+
import co.elastic.opamp.client.CentralConfigurationProcessor;
23+
import co.elastic.otel.logging.AgentLog;
24+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
25+
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
26+
import java.text.MessageFormat;
27+
import java.time.Duration;
28+
import java.util.HashSet;
29+
import java.util.Map;
30+
import java.util.Set;
31+
import java.util.logging.Logger;
32+
import java.util.stream.Collectors;
33+
import java.util.stream.Stream;
34+
35+
public class CentralConfig {
36+
private static final Logger logger = Logger.getLogger(CentralConfig.class.getName());
37+
38+
static {
39+
DynamicConfigurationPropertyChecker.startCheckerThread();
40+
}
41+
42+
public static void init(SdkTracerProviderBuilder providerBuilder, ConfigProperties properties) {
43+
// TODO flip default when EDOT collector supports op amp
44+
boolean startOpAmp = properties.getBoolean("elastic.otel.opamp.start", false);
45+
if (!startOpAmp) {
46+
return;
47+
}
48+
String serviceName = getServiceName(properties);
49+
// TODO agree on polling interval property name
50+
int pollingInterval = properties.getInt("elastic.otel.opamp.polling.interval_in_seconds", 30);
51+
// TODO derive default endpoint from main endpoint when EDOT collector endpoint is stable
52+
String endpoint = properties.getString("elastic.otel.opamp.endpoint", "http://localhost:4320");
53+
if (!endpoint.endsWith("v1/opamp")) {
54+
if (endpoint.endsWith("/")) {
55+
endpoint += "v1/opamp";
56+
} else {
57+
endpoint += "/v1/opamp";
58+
}
59+
}
60+
logger.info("============= Starting OpAmp client for: " + serviceName);
61+
DynamicInstrumentation.setTracerConfigurator(
62+
providerBuilder, DynamicConfiguration.UpdatableConfigurator.INSTANCE);
63+
CentralConfigurationManager centralConfigurationManager =
64+
CentralConfigurationManager.builder()
65+
.setServiceName(serviceName)
66+
.setPollingInterval(Duration.ofSeconds(pollingInterval))
67+
.setConfigurationEndpoint(endpoint)
68+
.build();
69+
70+
centralConfigurationManager.start(
71+
configuration -> {
72+
logger.fine("Received configuration: " + configuration);
73+
Configs.applyConfigurations(configuration);
74+
return CentralConfigurationProcessor.Result.SUCCESS;
75+
});
76+
77+
Runtime.getRuntime()
78+
.addShutdownHook(
79+
new Thread(
80+
() -> {
81+
logger.info("=========== Shutting down OpAmp client for: " + serviceName);
82+
centralConfigurationManager.stop();
83+
}));
84+
}
85+
86+
private static String getServiceName(ConfigProperties properties) {
87+
String serviceName = properties.getString("otel.service.name");
88+
if (serviceName != null) {
89+
return serviceName;
90+
}
91+
Map<String, String> resourceMap = properties.getMap("otel.resource.attributes");
92+
if (resourceMap != null) {
93+
serviceName = resourceMap.get("service.name");
94+
if (serviceName != null) {
95+
return serviceName;
96+
}
97+
}
98+
return "unknown_service"; // Specified default
99+
}
100+
101+
public static class Configs {
102+
private static final Map<String, ConfigOption> configNameToConfig;
103+
private static final Set<String> currentNonDefaultConfigsApplied = new HashSet<>();
104+
105+
static {
106+
configNameToConfig =
107+
Stream.of(
108+
new SendLogs(),
109+
new SendMetrics(),
110+
new SendTraces(),
111+
new DeactivateAllInstrumentations(),
112+
new DeactivateInstrumentations(),
113+
new LoggingLevel())
114+
.collect(Collectors.toMap(ConfigOption::getConfigName, option -> option));
115+
}
116+
117+
public static synchronized void applyConfigurations(Map<String, String> configuration) {
118+
Set<String> copyOfCurrentNonDefaultConfigsApplied =
119+
new HashSet<>(currentNonDefaultConfigsApplied);
120+
configuration.forEach(
121+
(configurationName, configurationValue) -> {
122+
copyOfCurrentNonDefaultConfigsApplied.remove(configurationName);
123+
applyConfiguration(configurationName, configurationValue);
124+
currentNonDefaultConfigsApplied.add(configurationName);
125+
});
126+
if (!copyOfCurrentNonDefaultConfigsApplied.isEmpty()) {
127+
// We have configs that were applied previously but have now been set back to default and
128+
// have been removed from the configs being sent - so for all of these we need to set the
129+
// config back to default
130+
for (String configurationName : copyOfCurrentNonDefaultConfigsApplied) {
131+
applyDefaultConfiguration(configurationName);
132+
currentNonDefaultConfigsApplied.remove(configurationName);
133+
}
134+
}
135+
}
136+
137+
public static void applyDefaultConfiguration(String configurationName) {
138+
configNameToConfig.get(configurationName).updateToDefault();
139+
}
140+
141+
public static void applyConfiguration(String configurationName, String configurationValue) {
142+
if (configNameToConfig.containsKey(configurationName)) {
143+
configNameToConfig.get(configurationName).updateOrLog(configurationValue);
144+
} else {
145+
logger.warning(
146+
"Ignoring unknown confguration option: '"
147+
+ configurationName
148+
+ "' with value: "
149+
+ configurationValue);
150+
}
151+
}
152+
}
153+
154+
public abstract static class ConfigOption {
155+
protected final String configName;
156+
protected final String defaultConfigStringValue;
157+
158+
protected ConfigOption(String configName1, String defaultConfigStringValue1) {
159+
configName = configName1;
160+
defaultConfigStringValue = defaultConfigStringValue1;
161+
}
162+
163+
public String getConfigName() {
164+
return configName;
165+
}
166+
167+
protected boolean getBoolean(String configurationValue) {
168+
String error =
169+
"'"
170+
+ getConfigName()
171+
+ "' configuration option can only be 'true' or 'false' but is: {0}";
172+
return getBoolean(configurationValue, error);
173+
}
174+
175+
protected boolean getBoolean(String configurationValue, String error) {
176+
if ("true".equalsIgnoreCase(configurationValue)) {
177+
return true;
178+
} else if ("false".equalsIgnoreCase(configurationValue)) {
179+
return false;
180+
} else {
181+
throw new IllegalArgumentException(MessageFormat.format(error, configurationValue));
182+
}
183+
}
184+
185+
public void updateOrLog(String configurationValue) {
186+
try {
187+
update(configurationValue);
188+
} catch (IllegalArgumentException e) {
189+
logger.warning(e.getMessage());
190+
}
191+
}
192+
193+
abstract void update(String configurationValue) throws IllegalArgumentException;
194+
195+
public void updateToDefault() {
196+
update(defaultConfigStringValue);
197+
}
198+
199+
protected DynamicConfiguration config() {
200+
return DynamicConfiguration.getInstance();
201+
}
202+
}
203+
204+
public static final class SendLogs extends ConfigOption {
205+
SendLogs() {
206+
super("send_logs", "true");
207+
}
208+
209+
@Override
210+
void update(String configurationValue) throws IllegalArgumentException {
211+
config().setSendingLogs(getBoolean(configurationValue));
212+
}
213+
}
214+
215+
public static final class SendMetrics extends ConfigOption {
216+
SendMetrics() {
217+
super("send_metrics", "true");
218+
}
219+
220+
@Override
221+
void update(String configurationValue) throws IllegalArgumentException {
222+
config().setSendingMetrics(getBoolean(configurationValue));
223+
}
224+
}
225+
226+
public static final class SendTraces extends ConfigOption {
227+
SendTraces() {
228+
super("send_traces", "true");
229+
}
230+
231+
@Override
232+
void update(String configurationValue) throws IllegalArgumentException {
233+
config().setSendingSpans(getBoolean(configurationValue));
234+
}
235+
}
236+
237+
public static final class DeactivateAllInstrumentations extends ConfigOption {
238+
DeactivateAllInstrumentations() {
239+
super("deactivate_all_instrumentations", "false");
240+
}
241+
242+
@Override
243+
void update(String configurationValue) throws IllegalArgumentException {
244+
if (getBoolean(configurationValue)) {
245+
config().deactivateAllInstrumentations();
246+
} else {
247+
config().reactivateAllInstrumentations();
248+
}
249+
}
250+
}
251+
252+
public static final class DeactivateInstrumentations extends ConfigOption {
253+
DeactivateInstrumentations() {
254+
super("deactivate_instrumentations", "");
255+
}
256+
257+
@Override
258+
void update(String configurationValue) throws IllegalArgumentException {
259+
config().deactivateInstrumentations(configurationValue);
260+
}
261+
}
262+
263+
public static final class LoggingLevel extends ConfigOption {
264+
LoggingLevel() {
265+
super("logging_level", "");
266+
}
267+
268+
@Override
269+
void update(String configurationValue) throws IllegalArgumentException {
270+
AgentLog.setLevel(configurationValue);
271+
}
272+
}
273+
}

custom/src/main/java/co/elastic/otel/dynamicconfig/DynamicConfiguration.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,28 +136,28 @@ public void restartAllSending() {
136136
}
137137
}
138138

139-
public void reenableTracesFor(String instrumentationName) {
139+
private void reactivateInstrumentation(String instrumentationName) {
140140
UpdatableConfigurator.INSTANCE.put(
141141
InstrumentationScopeInfo.create(INSTRUMENTATION_NAME_PREPEND + instrumentationName),
142142
TracerConfig.enabled());
143143
setProviderTracerConfigurator(
144144
GlobalOpenTelemetry.getTracerProvider(), UpdatableConfigurator.INSTANCE);
145145
}
146146

147-
public void disableTracesFor(String instrumentationName) {
147+
private void deactivateInstrumentation(String instrumentationName) {
148148
UpdatableConfigurator.INSTANCE.put(
149149
InstrumentationScopeInfo.create(INSTRUMENTATION_NAME_PREPEND + instrumentationName),
150150
TracerConfig.disabled());
151151
setProviderTracerConfigurator(
152152
GlobalOpenTelemetry.getTracerProvider(), UpdatableConfigurator.INSTANCE);
153153
}
154154

155-
public void disableAllTraces() {
156-
disableTracesFor(ALL_INSTRUMENTATION);
155+
public void deactivateAllInstrumentations() {
156+
deactivateInstrumentation(ALL_INSTRUMENTATION);
157157
}
158158

159-
public void stopDisablingAllTraces() {
160-
reenableTracesFor(ALL_INSTRUMENTATION);
159+
public void reactivateAllInstrumentations() {
160+
reactivateInstrumentation(ALL_INSTRUMENTATION);
161161
}
162162

163163
// okay to synchronize as this should only be called after multi-second intervals and
@@ -180,7 +180,7 @@ public synchronized void deactivateInstrumentations(String deactivateList) {
180180
// Applying (1) - keySet.remove() is a valid concurrent mutation here within the loop
181181
Set<String> keySet = alreadyDeactivated.keySet();
182182
for (String instrumentation : keySet) {
183-
DynamicConfiguration.getInstance().reenableTracesFor(instrumentation);
183+
DynamicConfiguration.getInstance().reactivateInstrumentation(instrumentation);
184184
keySet.remove(instrumentation);
185185
}
186186
} else {
@@ -225,11 +225,11 @@ public Deactivations(Set<String> deactivateList, Set<String> alreadyDeactivated)
225225

226226
public void applyDeactivations(ConcurrentMap<String, Boolean> alreadyDeactivated) {
227227
for (String instrumentation : instrumentationsToReactivate) {
228-
DynamicConfiguration.getInstance().reenableTracesFor(instrumentation);
228+
DynamicConfiguration.getInstance().reactivateInstrumentation(instrumentation);
229229
alreadyDeactivated.remove(instrumentation);
230230
}
231231
for (String instrumentation : instrumentationsToDeactivate) {
232-
DynamicConfiguration.getInstance().disableTracesFor(instrumentation);
232+
DynamicConfiguration.getInstance().deactivateInstrumentation(instrumentation);
233233
alreadyDeactivated.put(instrumentation, Boolean.TRUE);
234234
}
235235
}

custom/src/main/java/co/elastic/otel/dynamicconfig/DynamicInstrumentation.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,4 @@ public static TracerConfig setProviderTracerConfigurator(
8282
"Expected SdkTracerProvider but got " + provider.getClass().getName());
8383
}
8484
}
85-
86-
static {
87-
// will refactor this when DynamicInstrumentation class becomes mostly empty
88-
DynamicConfigurationPropertyChecker.startCheckerThread();
89-
}
9085
}

0 commit comments

Comments
 (0)