Analysis of SkyWalking Java Agent configuration initialization process

Posted by highphilosopher on Sat, 08 Jan 2022 10:55:25 +0100

Based on skywalking Java agent version 8.8.0

Today, we want to analyze the content related to SkyWalking Java Agent configuration. Most of the frameworks we contact need some configuration files, such as application. Com in SpringBoot yml. The first thing SkyWalking Java Agent does in the premain method is to use the snifferconfinitializer initializeCoreConfig(agentArgs); Initialize 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 part of the code
        
    }
    // Omit part of the code
}        
Copy code

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

SnifferConfigInitializer.initializeCoreConfig(agentArgs);
Copy code

Initialize core configuration

The SnifferConfigInitializer class initializes the configuration in many ways. The internal implementation has the following important steps:

1.loadConfig() loads the configuration file

  • Read the contents of the configuration file from the specified configuration file path through - Dskywalking_config=/xxx/yyy can specify the location of the configuration file;
  • If no configuration file path is specified, the default configuration file config / agent Config read;
  • Load the contents of the configuration file 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() throws AgentPackageNotFoundException, ConfigNotFoundException {
    // System.getProperty() reads the system properties in the Java virtual machine. The system properties in the Java virtual machine are configured through java -Dk1=v1 when running Java programs
    String specifiedConfigPath = System.getProperty(SPECIFIED_CONFIG_PATH);
    // Use the specified configuration file or the default configuration file, agentpackagepath Getpath() get skywalking agent Jar directory
    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 code

2.replacePlaceholders() parse placeholder placeholders

  • The configuration values read from the configuration file exist in the form of placeholder (such as agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}). Here, you need to resolve the placeholder to the actual value.
/**
 * 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) {
            return getConfigValue(placeholderName, properties);
        }
    });
}

// Priority system Properties(-D) > System environment variables > Config file
private String getConfigValue(String key, final Properties properties) {
    // Get (- D) from Java virtual machine system properties
    String value = System.getProperty(key);
    if (value == null) {
        // Get from operating system environment variables, such as Java_ Environment variables such as home and Path
        value = System.getenv(key);
    }
    if (value == null) {
        // Get from configuration file
        value = properties.getProperty(key);
    }
    return value;
}
Copy code

3.overrideConfigBySystemProp() reads system In getproperties(), use skywalking The system attribute at the beginning overrides the configuration;

/**
 * Override the config by system properties. The property key must start with `skywalking`, the result should be as
 * same as in `agent.config`
 * <p>
 * such as: Property key of `agent.service_name` should be `skywalking.agent.service_name`
 */
private static void overrideConfigBySystemProp() throws IllegalAccessException {
    Properties systemProperties = System.getProperties();
    for (final Map.Entry<Object, Object> prop : systemProperties.entrySet()) {
        String key = prop.getKey().toString();
        // Must be skywalking Properties at the beginning
        if (key.startsWith(ENV_KEY_PREFIX)) {
            String realKey = key.substring(ENV_KEY_PREFIX.length());
            AGENT_SETTINGS.put(realKey, prop.getValue());
        }
    }
}
Copy code

4.overrideConfigByAgentOptions() resolves the agentArgs parameter configuration;

  • agentArgs is the first parameter of the premain method, with - javaagent: / path / to / skywalking agent Pass values in the form of jar = K1 = V1, K2 = v2.

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

6.configureLogger() according to the configured config Logging. The resolver reconfigures the Log. For more information about logs, see the article SkyWalking Java Agent log component analysis

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

7. Required parameter validation, non empty parameter validation service_ Name and collector servers.

Locate skywalking agent Jar directory

The directory structure of skywalking agent is as follows:

The config directory stores the default configuration file agent Skywalking agent is required for config, reading the default configuration file, and loading plug-ins later Jar directory.

/**
 * Load the specified config file or default config file
 *
 * @return the config file {@link InputStream}, or null if not needEnhance.
 */
private static InputStreamReader loadConfig() throws AgentPackageNotFoundException, ConfigNotFoundException {
    // System.getProperty() reads the system properties in the Java virtual machine. The system properties in the Java virtual machine are configured through java -Dk1=v1 when running Java programs
    String specifiedConfigPath = System.getProperty(SPECIFIED_CONFIG_PATH);
    // Use the specified configuration file or the default configuration file, agentpackagepath Getpath() get skywalking agent Jar directory
    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 code

new File(AgentPackagePath.getPath(), DEFAULT_CONFIG_FILE_NAME) locate the location of the default configuration file, agentpackagepath The getpath () method is used to get skywalking agent Jar directory

/**
 * 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() throws AgentPackageNotFoundException {
        if (AGENT_PACKAGE_PATH == null) {
            // Return skywalking agent The directory where the jar file is located is e: \ development \ source \ sample \ source \ skywalking Java \ skywalking agent
            AGENT_PACKAGE_PATH = findPath();
        }
        return AGENT_PACKAGE_PATH;
    }

    public static boolean isPathFound() {
        return AGENT_PACKAGE_PATH != null;
    }

    private static File findPath() throws AgentPackageNotFoundException {
        // Add AgentPackagePath to the full class name Replace with/
        // org/apache/skywalking/apm/agent/core/boot/AgentPackagePath.class
        String classResourcePath = AgentPackagePath.class.getName().replaceAll("\.", "/") + ".class";
        // Use AppClassLoader to load resources. Usually, 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);

            // Determine whether the url contains!, If yes, agentpackagepath Class is contained in 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 skywalking agent The directory where the jar file is located
                    return agentJarFile.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 code

Load agentpackagepath. Path through class loader AppClassLoader Class resource, locate skywalking agent The directory where jar is located and saved to the static member variable agent_ PACKAGE_ In path, read the static variable directly next time.

Configure priority

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

System.getProperties() and system See the article for the difference between getenv() www.cnblogs.com/clarke157/p...

  • System.getProperties() obtains the system properties related to the Java virtual machine (such as java.version, java.io.tmpdir, etc.) and configures them through java -D;
  • System.getenv() obtains system environment variables (such as JAVA_HOME, Path, etc.) and configures them through the operating system.

Map configuration information to Config class

In our daily development, we usually read the required configuration items directly from the Properties. SkyWalking Java Agent does not do this. Instead, it defines a configuration class config and maps the configuration items to the static Properties of Config class. When configuration items are needed in other places, it can be obtained directly from the static Properties of class, which is very convenient to use.

ConfigInitializer is responsible for mapping the key/value pair in Properties to the static property of a class (such as Config class). 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 part of the code
    }
    
    public static class Collector {
        /**
         * Collector skywalking trace receiver service addresses.
         */
        public static String BACKEND_SERVICE = "";
        
        // Omit part of the code
    }
    
    // Omit part of the 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 part of the code

}
Copy code

For example, through agent Config configuration file configuration service name

# The service name in UI
agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}
Copy code
  • Agent corresponds to the static internal class agent of Config class;
  • SERVICE_NAME corresponds to the static attribute service of the static internal class Agent_ NAME.

SkyWalking Java Agent uses underscores instead of humps to name configuration items. It is very convenient to convert the static property name of a class into an underlined configuration name. You can get the corresponding value through Properties by directly converting it to lowercase.

Confinitializer class source code:

/**
 * 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 to static attributes by reflection
                        field.set(null, convertedValue);
                    }
                }
            }
        }
        // recentConfigType.getClasses() gets the classes and interfaces of public
        for (Class<?> innerConfiguration : recentConfigType.getClasses()) {
            // parentDesc stacks the class (Interface) name
            parentDesc.append(innerConfiguration.getSimpleName());
            // Recursive call
            initNextLevel(properties, innerConfiguration, parentDesc);
            // parentDesc name the class (Interface) out of the stack
            parentDesc.removeLastDesc();
        }
    }

    // Omit part of the code
}

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

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

    void removeLastDesc() {
        descs.removeLast();
    }

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

Copy code

ConfigInitializer. The technical points involved in the initnextlevel method include reflection, recursive call, stack, etc.

The comments I added to some of the code during reading the source code have been submitted to GitHub. See github.com/geekymv/sky...

Topics: Java Spring Boot Back-end Deep Learning