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
58 changes: 58 additions & 0 deletions apm-agent-api/src/main/java/co/elastic/apm/api/ElasticApm.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,62 @@ public static void captureException(@Nullable Throwable e) {
public static void setServiceInfoForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName, @Nullable String serviceVersion) {
// co.elastic.apm.api.ElasticApmInstrumentation.SetServiceInfoForClassLoader.setServiceInfoForClassLoader
}

/**
* Returns the current value of a config option (regardless of however and whenever that option was (re)set)
* <p>
* This applies to dynamically updated values too, so for example "transaction_sample_rate"
* can be dynamically reset (eg by executing
* System.setProperty("elastic.apm.transaction_sample_rate", "0.6")
* or changing the "transaction_sample_rate" in the property file or from fleet UI)
* and calling getConfig("transaction_sample_rate") would provide the current value
* </p>
* <p>
* If an invalid key is passed as the first argument, or if the incorrect type for
* the key is passed as the second argument, the method will throw an IllegalArgumentException
* </p>
* <p>
* Note the value returned can be null if the "public-api" set of instrumentations
* has not been applied and also if the agent hasn't finished initializing
* </p>
*
* @param key the string option
* @param type the type of the option (String, Long, Integer, etc)
* @return The current value for the option, per the option type (String, Long, Integer, etc)
* @since 1.37.0
*/
public static <T> T getConfig(String key, Class<T> type) {
Object value = doGetConfig(key);
if (value == null) {
throw new IllegalArgumentException("There is no such option: " + key);
} else if (value instanceof IllegalStateException) {
return null;
} else if (type.isInstance(value)) {
return (T) value;
} else {
throw new IllegalArgumentException("The option: '"+key+"' is not of type "+type.getName());
}
}

private static Object doGetConfig(String key) {
// co.elastic.apm.api.ElasticApmInstrumentation.ConfigInstrumentation.doGetConfig
return null;
}

/**
* Convenience method that just calls System.setProperty("elastic.apm."+key, value);
* <p>
* Note there is a delay between executing the update and it taking effect. The corresponding
* {@link #getConfig(String, Class)} ()} will only report the new value after it has taken effect.
* Note also that only dynamic options can be updated, other updates will simply be ignored.
* </p>
*
* @param key the string option
* @param value the value for that option. No validation is applied here, so invalid
* values for an option will be ignored by the configuration system
* @since 1.37.0
*/
public static void setConfig(String key, String value) {
System.setProperty("elastic.apm."+key, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
package co.elastic.apm.agent.pluginapi;

import co.elastic.apm.agent.configuration.ServiceInfo;
import co.elastic.apm.agent.impl.GlobalTracer;
import co.elastic.apm.agent.impl.transaction.Transaction;
import co.elastic.apm.agent.util.PrivilegedActionUtils;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.stagemonitor.configuration.ConfigurationOption;

import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
Expand Down Expand Up @@ -162,4 +164,29 @@ public static void setServiceInfoForClassLoader(@Advice.Argument(0) @Nullable Cl
}
}
}

public static class ConfigInstrumentation extends ElasticApmApiInstrumentation {
public ConfigInstrumentation() {
super(named("doGetConfig"));
}

public static class AdviceClass {
@Nullable
@Advice.AssignReturned.ToReturned
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static Object doGetConfig(@Advice.Argument(0) @Nullable String key) {
try {
ConfigurationOption<?> configValue = GlobalTracer.getTracerImpl().getConfigurationRegistry().getConfigurationOptionByKey(key);
if (configValue == null) {
return null;
} else {
return configValue.getValue();
}
} catch (NullPointerException e) {
//this can only happen if the agent is messed up or not yet initialized
return new IllegalStateException("The agent is not initialized");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$CurrentTransactionIn
co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$CurrentSpanInstrumentation
co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$CaptureExceptionInstrumentation
co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$SetServiceInfoForClassLoaderInstrumentation
co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation$ConfigInstrumentation
co.elastic.apm.agent.pluginapi.TransactionInstrumentation$SetFrameworkNameInstrumentation
co.elastic.apm.agent.pluginapi.TransactionInstrumentation$SetUserInstrumentation
co.elastic.apm.agent.pluginapi.TransactionInstrumentation$EnsureParentIdInstrumentation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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 co.elastic.apm.agent.pluginapi;

import co.elastic.apm.agent.MockTracer;
import co.elastic.apm.agent.bci.ElasticApmAgent;
import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;
import co.elastic.apm.api.ElasticApm;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.stagemonitor.configuration.ConfigurationRegistry;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

class ConfigInstrumentationTest {

private static final Integer INVALID_TRANSACTION_MAX_SPANS = -37;

private ElasticApmTracer tracer;
private ConfigurationRegistry configurationRegistry;
private CoreConfiguration coreConfig;

@BeforeEach
void setup() {
tracer = MockTracer.createRealTracer();
configurationRegistry = tracer.getConfigurationRegistry();
coreConfig = configurationRegistry.getConfig(CoreConfiguration.class);
}

@AfterEach
void reset() {
ElasticApmAgent.reset();
}

Comment thread
SylvainJuge marked this conversation as resolved.
@Test
void testValidOptionConfigAccess() {
initAgent();
assertThat(configValue(Boolean.TRUE)).isEqualTo(coreConfig.getTransactionMaxSpans());
}

@Test
void testInvalidOptionConfigAccess() {
initAgent();
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> configValue(Boolean.FALSE))
.withMessageStartingWith("There is no such option");
}

@Test
void testInvalidTypeConfigAccess() {
initAgent();
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> configValue(null))
.withMessageContaining("is not of type");
}

private void initAgent() {
ArrayList<ElasticApmInstrumentation> instrumentations = new ArrayList<>();
instrumentations.add(new MaxTransInstrumentation());
instrumentations.add(new ElasticApmApiInstrumentation.ConfigInstrumentation());
ElasticApmAgent.initInstrumentation(tracer, ByteBuddyAgent.install(), instrumentations);
assertThat(coreConfig.getTransactionMaxSpans()).isNotEqualTo(INVALID_TRANSACTION_MAX_SPANS);
}

Integer configValue(Boolean valid) {
return INVALID_TRANSACTION_MAX_SPANS;
}

public static class MaxTransInstrumentation extends TracerAwareInstrumentation {
public static class AdviceClass {
@Advice.AssignReturned.ToReturned
@Advice.OnMethodExit(inline = false)
public static Integer onMethodExit(@Advice.Argument(0) @Nullable Boolean valid) {
if (valid == null) {
return ElasticApm.getConfig("transaction_max_spans", Long.class).intValue();
} else if (valid.booleanValue()) {
return ElasticApm.getConfig("transaction_max_spans", Integer.class);
} else { //if (valid.booleanValue()) - no other options
return ElasticApm.getConfig("xyz", Integer.class);
}
}
}

@Override
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
return named(ConfigInstrumentationTest.class.getName());
}

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
return ElementMatchers.nameEndsWithIgnoreCase("configValue");
}

@Override
public Collection<String> getInstrumentationGroupNames() {
return Collections.emptyList();
}

}

}