Based on SkyWalking Java Agent version 8.8.0

In the last article, we covered the underlying implementation principles of logging with the SkyWalking Java Agent Logging Component Analysis article. Today, we will formally enter the Premain method. See who know righteousness premain method is the main method in our Java program before running method, generally we through – javaagent JVM parameters: / path/to/skywalking – agent. The jar designated agents.

Today we are going to analyze the Configuration of SkyWalking Java Agent. Most of the frameworks we touch on require some configuration files, such as Application.yml in SpringBoot. SkyWalking Java Agent in the premain method first do is through SnifferConfigInitializer. InitializeCoreConfig (agentArgs); Initialize the core configuration.

/** * The main entrance of sky-walking agent, based on javaagent mechanism. */
public class SkyWalkingAgent {
    private static ILog LOGGER = LogManager.getLogger(SkyWalkingAgent.class);

    /** * Main entrance. Use byte-buddy transform to enhance all classes, which define in plugins. */
    public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {
        final PluginFinder pluginFinder;
        try {
            SnifferConfigInitializer.initializeCoreConfig(agentArgs);
        } catch (Exception e) {
            // try to resolve a new logger, and use the new logger to write the error log here
            LogManager.getLogger(SkyWalkingAgent.class)
                    .error(e, "SkyWalking agent initialized failure. Shutting down.");
            return;
        } finally {
            // refresh logger again after initialization finishes
            LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
        }
        // omit some code....
        
    }
    // omit some code....
}        
Copy the code

Today we will focus on the internal implementation of this line of code

SnifferConfigInitializer.initializeCoreConfig(agentArgs);
Copy the code

It is recommended that you read the configuration documentation provided by the SkyWalking – Java project before continuing

  • Locate agent config file by system property About Specifying the configuration file using system attributes.
  • Setting Override Specifies the configuration Override.

Initialize the core configuration

The SnifferConfigInitializer class initializes the configuration in a variety of ways, and the internal implementation has the following important steps:

1. LoadConfig () loads the configuration file

  • Read the content of the configuration file from the specified path. Run -dskywalking_config =/ XXX /yyy to specify the location of the configuration file.
  • If no configuration file path is specified, the config/agent.config file is read from the default configuration file.
  • Load the configuration file content into Properties;
/**
 * Load the specified config file or default config file
 *
 * @return the config file {@link InputStream}, or null if not needEnhance.
 */
private static InputStreamReader loadConfig(a) throws AgentPackageNotFoundException, ConfigNotFoundException {
    // system.getProperty () reads the System properties of the Java virtual machine. The System properties of the Java virtual machine are configured with java-dk1 =v1 when the Java program is running.
    String specifiedConfigPath = System.getProperty(SPECIFIED_CONFIG_PATH);
    // Using the specified configuration file or the default configuration file, agentPackagepath.getPath () gets the directory where Skywalk-agent.jar is located
    File configFile = StringUtil.isEmpty(specifiedConfigPath) ? new File(
        AgentPackagePath.getPath(), DEFAULT_CONFIG_FILE_NAME) : new File(specifiedConfigPath);

    if (configFile.exists() && configFile.isFile()) {
        try {
            LOGGER.info("Config file found in {}.", configFile);

            return new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8);
        } catch (FileNotFoundException e) {
            throw new ConfigNotFoundException("Failed to load agent.config", e); }}throw new ConfigNotFoundException("Failed to load agent.config.");
}
Copy the code

2. Replaceplaceholder () placeholder

  • The configuration values read from the configuration file are placeholder values (such as Agent.service_name =${SW_AGENT_NAME:Your_ApplicationName}), where placeholders need to be resolved to actual values.
/**
 * Replaces all placeholders of format {@code ${name}} with the corresponding property from the supplied {@link
 * Properties}.
 *
 * @param value      the value containing the placeholders to be replaced
 * @param properties the {@code Properties} to use for replacement
 * @return the supplied value with placeholders replaced inline
 */
public String replacePlaceholders(String value, final Properties properties) {
    return replacePlaceholders(value, new PlaceholderResolver() {
        @Override
        public String resolvePlaceholder(String placeholderName) {
            returngetConfigValue(placeholderName, properties); }}); }System.properties (-d) > System Environment variables > Config file
private String getConfigValue(String key, final Properties properties) {
    // Obtain from Java VIRTUAL machine system properties (-d)
    String value = System.getProperty(key);
    if (value == null) {
        // It is obtained from operating system environment variables, such as JAVA_HOME and Path
        value = System.getenv(key);
    }
    if (value == null) {
        // Obtain it from the configuration file
        value = properties.getProperty(key);
    }
    return value;
}
Copy the code

3. OverrideConfigBySystemProp read () System. The getProperties () to skywalking. Initial system properties override configuration;

/** * Override the config by system properties. The property key must start with `skywalking`, the result should be as * same as in `agent.config` * 

* such as: Property key of `agent.service_name` should be `skywalking.agent.service_name` */

private static void overrideConfigBySystemProp(a) throws IllegalAccessException { Properties systemProperties = System.getProperties(); for (final Map.Entry<Object, Object> prop : systemProperties.entrySet()) { String key = prop.getKey().toString(); // Attributes must start with Skywalking if(key.startsWith(ENV_KEY_PREFIX)) { String realKey = key.substring(ENV_KEY_PREFIX.length()); AGENT_SETTINGS.put(realKey, prop.getValue()); }}}Copy the code

4. OverrideConfigByAgentOptions analytical agentArgs parameters configuration coverage configuration ();

  • AgentArgs is the first argument to the premain method-javaagent:/path/to/skywalking-agent.jar=k1=v1,k2=v2Pass the value in the form of.

5. InitializeConfig () maps the configuration information read above to the static attribute of Config class, which will be analyzed later;

ConfigureLogger () reconfigures logs according to config.logging. RESOLVER. For more information about Logging, see SkyWalking Java Agent Log Component analysis.

static void configureLogger(a) {
    switch (Config.Logging.RESOLVER) {
        case JSON:
            LogManager.setLogResolver(new JsonLogResolver());
            break;
        case PATTERN:
        default:
            LogManager.setLogResolver(newPatternLogResolver()); }}Copy the code

7. Mandatory parameter verification to verify non-empty parameters Agent. Service_name and Collector.servers.

Locate the directory where skywalk-agent. jar is located

The skywalk-agent directory structure is as follows:

The config directory stores the default configuration file agent.config. To read the default configuration file and load the plug-in, you need to use the directory where Skywalk-agent. jar resides.

/**
 * Load the specified config file or default config file
 *
 * @return the config file {@link InputStream}, or null if not needEnhance.
 */
private static InputStreamReader loadConfig(a) throws AgentPackageNotFoundException, ConfigNotFoundException {
    // system.getProperty () reads the System properties of the Java virtual machine. The System properties of the Java virtual machine are configured with java-dk1 =v1 when the Java program is running.
    String specifiedConfigPath = System.getProperty(SPECIFIED_CONFIG_PATH);
    // Using the specified configuration file or the default configuration file, agentPackagepath.getPath () gets the directory where Skywalk-agent.jar is located
    File configFile = StringUtil.isEmpty(specifiedConfigPath) ? new File(
        AgentPackagePath.getPath(), DEFAULT_CONFIG_FILE_NAME) : new File(specifiedConfigPath);

    if (configFile.exists() && configFile.isFile()) {
        try {
            LOGGER.info("Config file found in {}.", configFile);

            return new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8);
        } catch (FileNotFoundException e) {
            throw new ConfigNotFoundException("Failed to load agent.config", e); }}throw new ConfigNotFoundException("Failed to load agent.config.");
}
Copy the code

New File(agentPackagepath.getPath (), DEFAULT_CONFIG_FILE_NAME) locates the default configuration File, The agentPackagepath.getPath () method is used to get the directory where Skywalk-agent.jar is located

/** * AgentPackagePath is a flag and finder to locate the SkyWalking agent.jar. It gets the absolute path of the agent jar. * The path is the required metadata for agent core looking up the plugins and toolkit activations. If the lookup * mechanism fails, the agent will exit directly. */
public class AgentPackagePath {
    private static final ILog LOGGER = LogManager.getLogger(AgentPackagePath.class);

    private static File AGENT_PACKAGE_PATH;

    public static File getPath(a) throws AgentPackageNotFoundException {
        if (AGENT_PACKAGE_PATH == null) {
            Jar file E:\develop\source\sample\source\ Skywalking - Java \ Skywalking -agent
            AGENT_PACKAGE_PATH = findPath();
        }
        return AGENT_PACKAGE_PATH;
    }

    public static boolean isPathFound(a) {
        returnAGENT_PACKAGE_PATH ! =null;
    }

    private static File findPath(a) throws AgentPackageNotFoundException {
        // Add AgentPackagePath to the full class name. Replace /
        // org/apache/skywalking/apm/agent/core/boot/AgentPackagePath.class
        String classResourcePath = AgentPackagePath.class.getName().replaceAll("\."."/") + ".class";
        // Use AppClassLoader to load resources. Normally, the AgentPackagePath class is loaded by AppClassLoader.
        URL resource = ClassLoader.getSystemClassLoader().getResource(classResourcePath);
        if(resource ! =null) {
            String urlString = resource.toString();
            //jar:file:/E:/source/skywalking-java/skywalking-agent/skywalking-agent.jar! /org/apache/skywalking/apm/agent/core/boot/AgentPackagePath.class
            LOGGER.debug("The beacon class location is {}.", urlString);

            // Check whether the url contains! AgentPackagePath. Class is contained in the JAR.
            int insidePathIndex = urlString.indexOf('! ');
            boolean isInJar = insidePathIndex > -1;

            if (isInJar) {
                // file:/E:/source/skywalking-java/skywalking-agent/skywalking-agent.jar
                urlString = urlString.substring(urlString.indexOf("file:"), insidePathIndex);
                File agentJarFile = null;
                try {
                    // E:\source\skywalking-java\skywalking-agent\skywalking-agent.jar
                    agentJarFile = new File(new URL(urlString).toURI());
                } catch (MalformedURLException | URISyntaxException e) {
                    LOGGER.error(e, "Can not locate agent jar file by url:" + urlString);
                }
                if (agentJarFile.exists()) {
                    // Return to the directory where the Skywalk-agent. jar file resides
                    returnagentJarFile.getParentFile(); }}else {
                int prefixLength = "file:".length();
                String classLocation = urlString.substring(
                    prefixLength, urlString.length() - classResourcePath.length());
                return new File(classLocation);
            }
        }

        LOGGER.error("Can not locate agent jar file.");
        throw new AgentPackageNotFoundException("Can not locate agent jar file."); }}Copy the code

Use the AppClassLoader to load the AgentPackagePath. Class resource, locate the skywalk-agent. jar directory, and save it in the static member variable AGENT_PACKAGE_PATH. The next fetch reads the static variable directly.

Configuring a Priority

Agent Options > System.Properties(-D) > System environment variables > Config file

System. The getProperties () and System. The getenv (), please refer to the article www.cnblogs.com/clarke157/p…

  • System.getproperties () obtains System properties related to the Java VM (such as java.version and java.io. Tmpdir) and uses Java -D to configure the properties.
  • System.getenv() obtains System environment variables, such as JAVA_HOME and Path, and configures them through the operating System.

Map the configuration information to the Config class

SkyWalking Java Agent does not do this. Instead, it defines a Config class and maps the configuration items to the static Properties of the Config class. When configuration items are needed elsewhere, You can get it directly from the static property of the class, which is very easy to use.

ConfigInitializer is responsible for mapping key/value pairs in Properties to static Properties of classes such as Config, where key corresponds to the static property of the class and value is assigned to the value of the static property.

/** * This is the core config in sniffer agent. */
public class Config {

    public static class Agent {
        /** * Namespace isolates headers in cross process propagation. The HEADER name will be `HeaderName:Namespace`. */
        public static String NAMESPACE = "";

        /** * Service name is showed in skywalking-ui. Suggestion: set a unique name for each service, service instance * nodes share the same code */
        @Length(50)
        public static String SERVICE_NAME = "";
     
        // omit some code....
    }
    
    public static class Collector {
        /** * Collector skywalking trace receiver service addresses. */
        public static String BACKEND_SERVICE = "";
        
        // omit some code....
    }
    
    // omit some code....

    public static class Logging {
        /** * Log file name. */
        public static String FILE_NAME = "skywalking-api.log";

        /**
         * Log files directory. Default is blank string, means, use "{theSkywalkingAgentJarDir}/logs  " to output logs.
         * {theSkywalkingAgentJarDir} is the directory where the skywalking agent jar file is located.
         * <p>
         * Ref to {@link WriterFactory#getLogWriter()}
         */
        public static String DIR = "";
    }

    // omit some code....

}
Copy the code

For example, configure the service name in the agent.config configuration file

# The service name in UI
agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}
Copy the code
  • Agent The static inner class agent corresponding to the Config class.
  • Service_name Corresponds to the static property service_name of the Agent static inner class.

SkyWalking Java Agent uses underscores instead of humps to name configuration items. It is very convenient to convert static property names of classes to underscore configuration names.

ConfigInitializer class

/**
 * Init a class's static fields by a {@link Properties}, including static fields and static inner classes.
 * <p>
 */
public class ConfigInitializer {
    public static void initialize(Properties properties, Class
        rootConfigType) throws IllegalAccessException {
        initNextLevel(properties, rootConfigType, new ConfigDesc());
    }

    private static void initNextLevel(Properties properties, Class
        recentConfigType, ConfigDesc parentDesc) throws IllegalArgumentException, IllegalAccessException {
        for (Field field : recentConfigType.getFields()) {
            if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) {
                String configKey = (parentDesc + "."+ field.getName()).toLowerCase(); Class<? > type = field.getType();if (type.equals(Map.class)) {
                    /* * Map config format is, config_key[map_key]=map_value * Such as plugin.opgroup.resttemplate.rule[abc]=/url/path */
                    // Deduct two generic types of the map
                    ParameterizedType genericType = (ParameterizedType) field.getGenericType();
                    Type[] argumentTypes = genericType.getActualTypeArguments();

                    Type keyType = null;
                    Type valueType = null;
                    if(argumentTypes ! =null && argumentTypes.length == 2) {
                        // Get key type and value type of the map
                        keyType = argumentTypes[0];
                        valueType = argumentTypes[1];
                    }
                    Map map = (Map) field.get(null);
                    // Set the map from config key and properties
                    setForMapType(configKey, map, properties, keyType, valueType);
                } else {
                    /* * Others typical field type */
                    String value = properties.getProperty(configKey);
                    // Convert the value into real type
                    final Length lengthDefine = field.getAnnotation(Length.class);
                    if(lengthDefine ! =null) {
                        if(value ! =null && value.length() > lengthDefine.value()) {
                            value = value.substring(0, lengthDefine.value());
                        }
                    }
                    Object convertedValue = convertToTypicalType(type, value);
                    if(convertedValue ! =null) {
                        // Set values for static properties by reflection
                        field.set(null, convertedValue); }}}}/ / recentConfigType getClasses () to obtain public classes and interfaces
        for(Class<? > innerConfiguration : recentConfigType.getClasses()) {// parentDesc pushes the class (interface) name
            parentDesc.append(innerConfiguration.getSimpleName());
            // recursive call
            initNextLevel(properties, innerConfiguration, parentDesc);
            // parentDesc puts the class (interface) on the stackparentDesc.removeLastDesc(); }}// omit some code....
}

class ConfigDesc {
    private LinkedList<String> descs = new LinkedList<>();

    void append(String currentDesc) {
        if(StringUtil.isNotEmpty(currentDesc)) { descs.addLast(currentDesc); }}void removeLastDesc(a) {
        descs.removeLast();
    }

    @Override
    public String toString(a) {
        return String.join(".", descs); }}Copy the code

ConfigInitializer. InitNextLevel method involves technical points such as reflection, recursive calls, stack.

Some of the comments I added during my reading of the source code have been submitted to GitHub at github.com/geekymv/sky…

More exciting content please pay attention to the public number geekyMV, like please share to more friends oh “if you have any questions, welcome to exchange.