Skip to content

Commit 17da1e4

Browse files
committed
connect opamp to dynamic config
1 parent 0d5df72 commit 17da1e4

5 files changed

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

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)