Skip to content

Commit a5a9cf2

Browse files
authored
Add support for providing config on attach (#544)
1 parent a3731b2 commit a5a9cf2

File tree

5 files changed

+84
-26
lines changed

5 files changed

+84
-26
lines changed

apm-agent-attach/src/main/java/co/elastic/apm/attach/ElasticApmAttacher.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.io.IOException;
2727
import java.io.InputStream;
2828
import java.io.OutputStream;
29+
import java.util.Iterator;
30+
import java.util.Map;
2931

3032
/**
3133
* Attaches the Elastic Apm agent to the current or a remote JVM
@@ -44,6 +46,41 @@ public static void attach() {
4446
ByteBuddyAgent.attach(AgentJarFileHolder.INSTANCE.agentJarFile, ByteBuddyAgent.ProcessProvider.ForCurrentVm.INSTANCE);
4547
}
4648

49+
/**
50+
* Attaches the Elastic Apm agent to the current JVM.
51+
* <p>
52+
* This method may only be invoked once.
53+
* </p>
54+
*
55+
* @param configuration the agent configuration
56+
* @throws IllegalStateException if there was a problem while attaching the agent to this VM
57+
*/
58+
public static void attach(Map<String, String> configuration) {
59+
ByteBuddyAgent.attach(AgentJarFileHolder.INSTANCE.agentJarFile, ByteBuddyAgent.ProcessProvider.ForCurrentVm.INSTANCE, toAgentArgs(configuration));
60+
}
61+
62+
static String toAgentArgs(Map<String, String> configuration) {
63+
StringBuilder args = new StringBuilder();
64+
for (Iterator<Map.Entry<String, String>> iterator = configuration.entrySet().iterator(); iterator.hasNext(); ) {
65+
Map.Entry<String, String> entry = iterator.next();
66+
args.append(entry.getKey()).append('=').append(entry.getValue());
67+
if (iterator.hasNext()) {
68+
args.append(';');
69+
}
70+
}
71+
return args.toString();
72+
}
73+
74+
/**
75+
* Attaches the agent to a remote JVM
76+
*
77+
* @param pid the PID of the JVM the agent should be attached on
78+
* @param configuration the agent configuration
79+
*/
80+
public static void attach(String pid, Map<String, String> configuration) {
81+
ByteBuddyAgent.attach(AgentJarFileHolder.INSTANCE.agentJarFile, pid, toAgentArgs(configuration));
82+
}
83+
4784
/**
4885
* Attaches the agent to a remote JVM
4986
*

apm-agent-attach/src/main/java/co/elastic/apm/attach/RemoteAttacher.java

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
import java.text.SimpleDateFormat;
2828
import java.util.ArrayList;
2929
import java.util.Date;
30+
import java.util.HashMap;
3031
import java.util.HashSet;
3132
import java.util.List;
33+
import java.util.Map;
3234
import java.util.Objects;
3335
import java.util.Scanner;
3436
import java.util.Set;
@@ -61,7 +63,7 @@ public static void main(String[] args) throws IOException, InterruptedException
6163
System.out.println(getJpsOutput());
6264
} else if (arguments.getPid() != null) {
6365
log("INFO", "Attaching the Elastic APM agent to %s", arguments.getPid());
64-
ElasticApmAttacher.attach(arguments.getPid(), arguments.getArgs());
66+
ElasticApmAttacher.attach(arguments.getPid(), arguments.getConfig());
6567
log("INFO", "Done");
6668
} else {
6769
do {
@@ -136,7 +138,7 @@ private void onJvmStart(JvmInfo jvmInfo) {
136138
}
137139

138140
private String getAgentArgs(JvmInfo jvmInfo) throws IOException, InterruptedException {
139-
return arguments.getArgsProvider() != null ? getArgsProviderOutput(jvmInfo) : arguments.getArgs();
141+
return arguments.getArgsProvider() != null ? getArgsProviderOutput(jvmInfo) : ElasticApmAttacher.toAgentArgs(arguments.getConfig());
140142
}
141143

142144
private String getArgsProviderOutput(JvmInfo jvmInfo) throws IOException, InterruptedException {
@@ -211,34 +213,34 @@ static class Arguments {
211213
private final String pid;
212214
private final List<String> includes;
213215
private final List<String> excludes;
214-
private final String args;
216+
private final Map<String, String> config;
215217
private final String argsProvider;
216218
private final boolean help;
217219
private final boolean list;
218220
private final boolean continuous;
219221

220-
private Arguments(String pid, List<String> includes, List<String> excludes, String args, String argsProvider, boolean help, boolean list, boolean continuous) {
222+
private Arguments(String pid, List<String> includes, List<String> excludes, Map<String, String> config, String argsProvider, boolean help, boolean list, boolean continuous) {
221223
this.help = help;
222224
this.list = list;
223225
this.continuous = continuous;
224-
if (args != null && argsProvider != null) {
225-
throw new IllegalArgumentException("Providing both --args and --args-provider is illegal");
226+
if (!config.isEmpty() && argsProvider != null) {
227+
throw new IllegalArgumentException("Providing both --config and --args-provider is illegal");
226228
}
227229
if (pid != null && (!includes.isEmpty() || !excludes.isEmpty() || continuous)) {
228230
throw new IllegalArgumentException("Providing --pid and either of --include, --exclude or --continuous is illegal");
229231
}
230232
this.pid = pid;
231233
this.includes = includes;
232234
this.excludes = excludes;
233-
this.args = args;
235+
this.config = config;
234236
this.argsProvider = argsProvider;
235237
}
236238

237239
static Arguments parse(String... args) {
238240
String pid = null;
239241
List<String> includes = new ArrayList<>();
240242
List<String> excludes = new ArrayList<>();
241-
String agentArgs = null;
243+
Map<String, String> config = new HashMap<>();
242244
String argsProvider = null;
243245
boolean help = args.length == 0;
244246
boolean list = false;
@@ -264,6 +266,8 @@ static Arguments parse(String... args) {
264266
case "--pid":
265267
case "-a":
266268
case "--args":
269+
case "-C":
270+
case "--config":
267271
case "-A":
268272
case "--args-provider":
269273
case "-e":
@@ -290,7 +294,14 @@ static Arguments parse(String... args) {
290294
break;
291295
case "-a":
292296
case "--args":
293-
agentArgs = arg;
297+
System.err.println("--args is deprecated in favor of --config");
298+
for (String conf : arg.split(";")) {
299+
config.put(conf.substring(0, conf.indexOf('=')), conf.substring(conf.indexOf('=') + 1));
300+
}
301+
break;
302+
case "-C":
303+
case "--config":
304+
config.put(arg.substring(0, arg.indexOf('=')), arg.substring(arg.indexOf('=') + 1));
294305
break;
295306
case "-A":
296307
case "--args-provider":
@@ -301,7 +312,7 @@ static Arguments parse(String... args) {
301312
}
302313
}
303314
}
304-
return new Arguments(pid, includes, excludes, agentArgs, argsProvider, help, list, continuous);
315+
return new Arguments(pid, includes, excludes, config, argsProvider, help, list, continuous);
305316
}
306317

307318
// -ab -> -a -b
@@ -323,7 +334,7 @@ void printHelp(PrintStream out) {
323334
out.println("SYNOPSIS");
324335
out.println(" java -jar apm-agent-attach.jar -p <pid> [--args <agent_arguments>]");
325336
out.println(" java -jar apm-agent-attach.jar [-i <include_pattern>...] [-e <exclude_pattern>...] [--continuous]");
326-
out.println(" [--args <agent_arguments> | --args-provider <args_provider_script>]");
337+
out.println(" [--config <key=value>... | --args-provider <args_provider_script>]");
327338
out.println(" java -jar apm-agent-attach.jar (--list | --help)");
328339
out.println();
329340
out.println("DESCRIPTION");
@@ -348,9 +359,14 @@ void printHelp(PrintStream out) {
348359
out.println(" (Matches the output of 'jps -l')");
349360
out.println();
350361
out.println(" -a, --args <agent_arguments>");
362+
out.println(" Deprecated in favor of --config.");
351363
out.println(" If set, the arguments are used to configure the agent on the attached JVM (agentArguments of agentmain).");
352364
out.println(" The syntax of the arguments is 'key1=value1;key2=value1,value2'.");
353365
out.println();
366+
out.println(" -C --config <key=value>...");
367+
out.println(" This repeatable option sets one agent configuration option.");
368+
out.println(" Example: --config server_urls=http://localhost:8200,http://localhost:8201.");
369+
out.println();
354370
out.println(" -A, --args-provider <args_provider_script>");
355371
out.println(" The name of a program which is called when a new JVM starts up.");
356372
out.println(" The program gets the pid and the main class name or path to the JAR file as an argument");
@@ -372,8 +388,8 @@ List<String> getExcludes() {
372388
return excludes;
373389
}
374390

375-
String getArgs() {
376-
return args;
391+
Map<String, String> getConfig() {
392+
return config;
377393
}
378394

379395
String getArgsProvider() {

apm-agent-attach/src/test/java/co/elastic/apm/attach/RemoteAttacherTest.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
99
* You may obtain a copy of the License at
10-
*
10+
*
1111
* http://www.apache.org/licenses/LICENSE-2.0
12-
*
12+
*
1313
* Unless required by applicable law or agreed to in writing, software
1414
* distributed under the License is distributed on an "AS IS" BASIS,
1515
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -40,22 +40,24 @@ void testArgumentParsing() {
4040
assertThat(RemoteAttacher.Arguments.parse("--continuous").isContinuous()).isTrue();
4141
assertThat(RemoteAttacher.Arguments.parse("-p", "42").getPid()).isEqualTo("42");
4242
assertThat(RemoteAttacher.Arguments.parse("--pid", "42").getPid()).isEqualTo("42");
43-
assertThat(RemoteAttacher.Arguments.parse("--args", "foo=bar").getArgs()).isEqualTo("foo=bar");
44-
assertThat(RemoteAttacher.Arguments.parse("-a", "foo=bar").getArgs()).isEqualTo("foo=bar");
43+
assertThat(RemoteAttacher.Arguments.parse("--args", "foo=bar;baz=qux").getConfig()).containsEntry("foo", "bar").containsEntry("baz", "qux");
44+
assertThat(RemoteAttacher.Arguments.parse("-a", "foo=bar").getConfig()).containsEntry("foo", "bar");
45+
assertThat(RemoteAttacher.Arguments.parse("--config", "foo=bar", "baz=qux").getConfig()).containsEntry("foo", "bar").containsEntry("baz", "qux");
46+
assertThat(RemoteAttacher.Arguments.parse("-C", "foo=bar", "-C", "baz=qux").getConfig()).containsEntry("foo", "bar").containsEntry("baz", "qux");
4547
assertThat(RemoteAttacher.Arguments.parse("--args-provider", "foo").getArgsProvider()).isEqualTo("foo");
4648
assertThat(RemoteAttacher.Arguments.parse("-A", "foo").getArgsProvider()).isEqualTo("foo");
4749

4850
assertThat(RemoteAttacher.Arguments.parse("--exclude", "foo", "bar", "baz").getExcludes()).isEqualTo(Arrays.asList("foo", "bar", "baz"));
49-
assertThat(RemoteAttacher.Arguments.parse("--args", "foo", "-e", "foo", "bar", "baz").getExcludes()).isEqualTo(Arrays.asList("foo", "bar", "baz"));
51+
assertThat(RemoteAttacher.Arguments.parse("--config", "foo=bar", "-e", "foo", "bar", "baz").getExcludes()).isEqualTo(Arrays.asList("foo", "bar", "baz"));
5052
assertThat(RemoteAttacher.Arguments.parse("--include", "foo", "bar", "baz").getIncludes()).isEqualTo(Arrays.asList("foo", "bar", "baz"));
51-
assertThat(RemoteAttacher.Arguments.parse("-i", "foo", "bar", "baz", "--args", "42").getIncludes()).isEqualTo(Arrays.asList("foo", "bar", "baz"));
52-
assertThatThrownBy(() -> RemoteAttacher.Arguments.parse("--args", "foo=bar", "--args-provider", "foo")).isInstanceOf(IllegalArgumentException.class);
53+
assertThat(RemoteAttacher.Arguments.parse("-i", "foo", "bar", "baz", "--config", "foo=bar").getIncludes()).isEqualTo(Arrays.asList("foo", "bar", "baz"));
54+
assertThatThrownBy(() -> RemoteAttacher.Arguments.parse("--config", "foo=bar", "--args-provider", "foo")).isInstanceOf(IllegalArgumentException.class);
5355
assertThatThrownBy(() -> RemoteAttacher.Arguments.parse("--pid", "42", "--exclude", "foo")).isInstanceOf(IllegalArgumentException.class);
5456
assertThatThrownBy(() -> RemoteAttacher.Arguments.parse("--pid", "42", "--continuous")).isInstanceOf(IllegalArgumentException.class);
5557

56-
assertThat(RemoteAttacher.Arguments.parse("-ca", "foo=bar").getArgs()).isEqualTo("foo=bar");
57-
assertThat(RemoteAttacher.Arguments.parse("-ca", "foo=bar").isContinuous()).isTrue();
58+
assertThat(RemoteAttacher.Arguments.parse("-cC", "foo=bar").getConfig()).containsEntry("foo", "bar");
59+
assertThat(RemoteAttacher.Arguments.parse("-cC", "foo=bar").isContinuous()).isTrue();
5860

59-
assertThatThrownBy(() -> RemoteAttacher.Arguments.parse("-lax")).isInstanceOf(IllegalArgumentException.class).hasMessage("Illegal argument: -x");
61+
assertThatThrownBy(() -> RemoteAttacher.Arguments.parse("-lcx")).isInstanceOf(IllegalArgumentException.class).hasMessage("Illegal argument: -x");
6062
}
6163
}

apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/AgentArgumentsConfigurationSource.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ private AgentArgumentsConfigurationSource(Map<String, String> config) {
3636
public static AgentArgumentsConfigurationSource parse(String agentAgruments) {
3737
final Map<String, String> configs = new HashMap<>();
3838
for (String config : StringUtils.split(agentAgruments, ';')) {
39-
final String[] split = StringUtils.split(config, '=');
40-
if (split.length != 2) {
39+
final int indexOfEquals = config.indexOf('=');
40+
if (indexOfEquals < 1) {
4141
throw new IllegalArgumentException(String.format("%s is not a '=' separated key/value pair", config));
4242
}
43-
configs.put(split[0].trim(), split[1].trim());
43+
configs.put(config.substring(0, indexOfEquals).trim(), config.substring(indexOfEquals + 1).trim());
4444
}
4545
return new AgentArgumentsConfigurationSource(configs);
4646
}

apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/AgentArgumentsConfigurationSourceTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ void testParse() {
3232
assertThat(AgentArgumentsConfigurationSource.parse("foo = bar ; baz =qux, quux ").getConfig())
3333
.containsEntry("foo", "bar")
3434
.containsEntry("baz", "qux, quux");
35+
assertThat(AgentArgumentsConfigurationSource.parse("foo = bar ; baz =qux=quux,foo=bar ").getConfig())
36+
.containsEntry("foo", "bar")
37+
.containsEntry("baz", "qux=quux,foo=bar");
3538
assertThatThrownBy(() -> AgentArgumentsConfigurationSource.parse("foo")).isInstanceOf(IllegalArgumentException.class);
3639

3740
}

0 commit comments

Comments
 (0)