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...