forked from aeron-io/aeron
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Java] Dynamic logging (aeron-io#1167)
* [Git] Exclude `.run` directory where Intellij stores run configs. * [Java] Dynamically attach logging agent to a running Java process + add support for starting and stopping logging. * [Java] Translate exception into user-friendly message + print information about which log reader is used and file the log goes to. * [Java] Do not pass file name or log reader class name, i.e. all config options are non-mandatory. * [Java] Spelling. * [Java] Make `aeron-all` depend on `aeron-agent` module so that it can access Agent classes. * [Bash] Add a script to start and stop dynamic logging. Usage example: ``` JVM_OPTS="-Daeron.event.log=admin -Daeron.event.archive.log=all" ./dynamic-logging 1111 start ``` * [Bash] Fix number of args check. * [Batch] Add a script for dynamic logging. * [Java] Show cause for the failed agent command. * [Java] Restore public API. * [Java] Enums should throw IllegalArgumentException if ID is unknown to avoid NPE when looked up code is to be used.
- Loading branch information
1 parent
9e5f11a
commit a2f7df5
Showing
19 changed files
with
937 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
aeron-agent/src/main/java/io/aeron/agent/ConfigOption.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/* | ||
* Copyright 2014-2021 Real Logic Limited. | ||
* | ||
* Licensed 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 | ||
* | ||
* https://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 io.aeron.agent; | ||
|
||
import org.agrona.Strings; | ||
import org.agrona.concurrent.Agent; | ||
|
||
import java.util.EnumMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* A set of configuration options. | ||
*/ | ||
enum ConfigOption | ||
{ | ||
/** | ||
* Event Buffer log file name system property. If not set then output will default to {@link System#out}. | ||
*/ | ||
LOG_FILENAME("aeron.event.log.filename"), | ||
|
||
/** | ||
* Event reader {@link Agent} which consumes the {@link EventConfiguration#EVENT_RING_BUFFER} to output log events. | ||
*/ | ||
READER_CLASSNAME("aeron.event.log.reader.classname"), | ||
|
||
/** | ||
* Driver Event tags system property. This is either: | ||
* <ul> | ||
* <li>A comma separated list of {@link DriverEventCode}s to enable</li> | ||
* <li>"all" which enables all the codes</li> | ||
* <li>"admin" which enables the codes specified by {@link EventConfiguration#ADMIN_ONLY_EVENT_CODES} which | ||
* is the admin commands</li> | ||
* </ul> | ||
*/ | ||
ENABLED_DRIVER_EVENT_CODES("aeron.event.log"), | ||
|
||
/** | ||
* Disabled Driver Event tags system property. Follows the format specified for | ||
* {@link #ENABLED_DRIVER_EVENT_CODES}. This property will disable any codes in the set | ||
* specified there. Defined on its own has no effect. | ||
*/ | ||
DISABLED_DRIVER_EVENT_CODES("aeron.event.log.disable"), | ||
|
||
/** | ||
* Archive Event tags system property. This is either: | ||
* <ul> | ||
* <li>A comma separated list of {@link ArchiveEventCode}s to enable</li> | ||
* <li>"all" which enables all the codes</li> | ||
* </ul> | ||
*/ | ||
ENABLED_ARCHIVE_EVENT_CODES("aeron.event.archive.log"), | ||
|
||
/** | ||
* Disabled Archive Event tags system property. Follows the format specified for | ||
* {@link #ENABLED_ARCHIVE_EVENT_CODES}. This property will disable any codes in the | ||
* set specified there. Defined on its own has no effect. | ||
*/ | ||
DISABLED_ARCHIVE_EVENT_CODES("aeron.event.archive.log.disable"), | ||
|
||
/** | ||
* Cluster Event tags system property. This is either: | ||
* <ul> | ||
* <li>A comma separated list of {@link ClusterEventCode}s to enable</li> | ||
* <li>"all" which enables all the codes</li> | ||
* </ul> | ||
*/ | ||
ENABLED_CLUSTER_EVENT_CODES("aeron.event.cluster.log"), | ||
|
||
/** | ||
* Disabled Cluster Event tags system property name. Follows the format specified for | ||
* {@link #ENABLED_CLUSTER_EVENT_CODES}. This property will disable any codes in the | ||
* set specified there. Defined on its own has no effect. | ||
*/ | ||
DISABLED_CLUSTER_EVENT_CODES("aeron.event.cluster.log.disable"); | ||
|
||
static final String START_COMMAND = "start"; | ||
static final String STOP_COMMAND = "stop"; | ||
|
||
private static final char VALUE_SEPARATOR = '='; | ||
private static final char OPTION_SEPARATOR = '|'; | ||
private static final ConfigOption[] OPTIONS = values(); | ||
|
||
private final String propertyName; | ||
|
||
ConfigOption(final String propertyName) | ||
{ | ||
this.propertyName = propertyName; | ||
} | ||
|
||
String propertyName() | ||
{ | ||
return propertyName; | ||
} | ||
|
||
static EnumMap<ConfigOption, String> fromSystemProperties() | ||
{ | ||
final EnumMap<ConfigOption, String> values = new EnumMap<>(ConfigOption.class); | ||
for (final ConfigOption option : OPTIONS) | ||
{ | ||
final String value = System.getProperty(option.propertyName()); | ||
if (null != value) | ||
{ | ||
values.put(option, value); | ||
} | ||
} | ||
return values; | ||
} | ||
|
||
static String buildAgentArgs(final EnumMap<ConfigOption, String> configOptions) | ||
{ | ||
if (configOptions.isEmpty()) | ||
{ | ||
return null; | ||
} | ||
|
||
final StringBuilder builder = new StringBuilder(); | ||
for (final Map.Entry<ConfigOption, String> entry : configOptions.entrySet()) | ||
{ | ||
builder.append(entry.getKey()) | ||
.append(VALUE_SEPARATOR) | ||
.append(entry.getValue()) | ||
.append(OPTION_SEPARATOR); | ||
} | ||
if (builder.length() > 0) | ||
{ | ||
builder.deleteCharAt(builder.length() - 1); | ||
} | ||
return builder.toString(); | ||
} | ||
|
||
static EnumMap<ConfigOption, String> parseAgentArgs(final String agentArgs) | ||
{ | ||
if (Strings.isEmpty(agentArgs)) | ||
{ | ||
throw new IllegalArgumentException("cannot parse empty value"); | ||
} | ||
|
||
final EnumMap<ConfigOption, String> values = new EnumMap<>(ConfigOption.class); | ||
|
||
int optionIndex = -1; | ||
do | ||
{ | ||
final int valueIndex = agentArgs.indexOf(VALUE_SEPARATOR, optionIndex); | ||
if (valueIndex <= 0) | ||
{ | ||
break; | ||
} | ||
|
||
int nameIndex = -1; | ||
while (optionIndex < valueIndex) | ||
{ | ||
nameIndex = optionIndex; | ||
optionIndex = agentArgs.indexOf(OPTION_SEPARATOR, optionIndex + 1); | ||
if (optionIndex < 0) | ||
{ | ||
break; | ||
} | ||
} | ||
|
||
final String optionName = agentArgs.substring(nameIndex + 1, valueIndex); | ||
final ConfigOption option = valueOf(optionName); | ||
final String value = agentArgs.substring( | ||
valueIndex + 1, | ||
optionIndex > 0 ? optionIndex : agentArgs.length()); | ||
values.put(option, value); | ||
} | ||
while (optionIndex > 0); | ||
|
||
return values; | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
aeron-agent/src/main/java/io/aeron/agent/DynamicLoggingAgent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Copyright 2014-2021 Real Logic Limited. | ||
* | ||
* Licensed 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 | ||
* | ||
* https://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 io.aeron.agent; | ||
|
||
import net.bytebuddy.agent.ByteBuddyAgent; | ||
import org.agrona.PropertyAction; | ||
import org.agrona.Strings; | ||
import org.agrona.SystemUtil; | ||
|
||
import java.io.File; | ||
import java.nio.file.Paths; | ||
import java.util.EnumMap; | ||
|
||
import static io.aeron.agent.ConfigOption.*; | ||
import static java.lang.System.out; | ||
|
||
/** | ||
* Attach/detach logging agent dynamically to a running process. | ||
*/ | ||
public class DynamicLoggingAgent | ||
{ | ||
/** | ||
* Attach logging agent jar to a running process. | ||
* | ||
* @param args program arguments. | ||
*/ | ||
public static void main(final String[] args) | ||
{ | ||
if (args.length < 3) | ||
{ | ||
printHelp(); | ||
System.exit(-1); | ||
} | ||
|
||
final File agentJar = Paths.get(args[0]).toAbsolutePath().toFile(); | ||
if (!agentJar.exists()) | ||
{ | ||
throw new IllegalArgumentException(agentJar + " does not exist!"); | ||
} | ||
|
||
final String processId = args[1]; | ||
if (Strings.isEmpty(processId)) | ||
{ | ||
throw new IllegalArgumentException("no PID provided!"); | ||
} | ||
|
||
final String command = args[2]; | ||
switch (command) | ||
{ | ||
case START_COMMAND: | ||
{ | ||
for (int i = 3; i < args.length; i++) | ||
{ | ||
SystemUtil.loadPropertiesFile(PropertyAction.PRESERVE, args[i]); | ||
} | ||
|
||
final EnumMap<ConfigOption, String> configOptions = fromSystemProperties(); | ||
final String agentArgs = buildAgentArgs(configOptions); | ||
attachAgent(START_COMMAND, agentJar, processId, agentArgs); | ||
out.println("Logging started."); | ||
break; | ||
} | ||
|
||
case STOP_COMMAND: | ||
{ | ||
attachAgent(STOP_COMMAND, agentJar, processId, command); | ||
out.println("Logging stopped."); | ||
break; | ||
} | ||
|
||
default: | ||
throw new IllegalArgumentException("invalid command: " + command); | ||
} | ||
} | ||
|
||
private static void printHelp() | ||
{ | ||
out.println("Usage: <agent-jar> <java-process-id> <command> [property files...]"); | ||
out.println(" <agent-jar> - fully qualified path to the agent jar"); | ||
out.println(" <java-process-id> - PID of the Java process to attach an agent to"); | ||
out.println(" <command> - either '" + START_COMMAND + "' or '" + STOP_COMMAND + "'"); | ||
out.println(" [property files...] - an optional list of property files to configure logging options"); | ||
out.println("Note: logging options can be specified either via system properties or the property files."); | ||
} | ||
|
||
private static void attachAgent( | ||
final String command, final File agentJar, final String processId, final String agentArgs) | ||
{ | ||
try | ||
{ | ||
ByteBuddyAgent.attach(agentJar, processId, agentArgs); | ||
} | ||
catch (final Throwable t) | ||
{ | ||
out.println("Command '" + command + "' failed, cause: " + getCause(t)); | ||
System.exit(-1); | ||
} | ||
} | ||
|
||
private static Throwable getCause(final Throwable t) | ||
{ | ||
Throwable cause = t; | ||
while (null != cause.getCause()) | ||
{ | ||
cause = cause.getCause(); | ||
} | ||
return cause; | ||
} | ||
|
||
} |
Oops, something went wrong.