本源码来自于skywalking-agent 8.9.0版本
本节主要讲解skywalking-agent的配置文件加载流程,该实现在 apm-sniffer模块的apm-agent-core 模块里面。但是入口依然是在 apm-agent 模块的 SkyWalkingAgent 的premain方法里面。
注:本篇文章主要是作为自己看书后的总结,内容有可能会存在一些个人理解上的偏差,如果有网友找出问题欢迎提出,感谢!!!如果我理解上的错误误导了您,在此表示抱歉!!!
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);
}
这里 SnifferConfigInitializer.initializeCoreConfig(agentArgs) 就是加载配置的入口。
这里来个小问题,为什么加载配置的时候再catch的里面打印日志时需要通过 LogManager.getLogger 重新获取日志类型,而不是直接打印日志?这个等文章结尾再讲解
public static void initializeCoreConfig(String agentOptions) {
AGENT_SETTINGS = new Properties();
try (final InputStreamReader configFileStream = loadConfig()) {
AGENT_SETTINGS.load(configFileStream);
for (String key : AGENT_SETTINGS.stringPropertyNames()) {
String value = (String) AGENT_SETTINGS.get(key);
AGENT_SETTINGS.put(key, PropertyPlaceholderHelper.INSTANCE.replacePlaceholders(value, AGENT_SETTINGS));
}
} catch (Exception e) {
LOGGER.error(e, "Failed to read the config file, skywalking is going to run in default config.");
}
try {
overrideConfigBySystemProp();
} catch (Exception e) {
LOGGER.error(e, "Failed to read the system properties.");
}
agentOptions = StringUtil.trim(agentOptions, ',');
if (!StringUtil.isEmpty(agentOptions)) {
try {
agentOptions = agentOptions.trim();
LOGGER.info("Agent options is {}.", agentOptions);
overrideConfigByAgentOptions(agentOptions);
} catch (Exception e) {
LOGGER.error(e, "Failed to parse the agent options, val is {}.", agentOptions);
}
}
initializeConfig(Config.class);
// reconfigure logger after config initialization
configureLogger();
LOGGER = LogManager.getLogger(SnifferConfigInitializer.class);
if (StringUtil.isEmpty(Config.Agent.SERVICE_NAME)) {
throw new ExceptionInInitializerError("`agent.service_name` is missing.");
} else {
if (StringUtil.isNotEmpty(Config.Agent.NAMESPACE) || StringUtil.isNotEmpty(Config.Agent.CLUSTER)) {
Config.Agent.SERVICE_NAME = StringUtil.join(
SERVICE_NAME_PART_CONNECTOR,
Config.Agent.SERVICE_NAME,
Config.Agent.NAMESPACE,
Config.Agent.CLUSTER
);
}
}
if (StringUtil.isEmpty(Config.Collector.BACKEND_SERVICE)) {
throw new ExceptionInInitializerError("`collector.backend_service` is missing.");
}
if (Config.Plugin.PEER_MAX_LENGTH <= 3) {
LOGGER.warn(
"PEER_MAX_LENGTH configuration:{} error, the default value of 200 will be used.",
Config.Plugin.PEER_MAX_LENGTH
);
Config.Plugin.PEER_MAX_LENGTH = 200;
}
IS_INIT_COMPLETED = true;
}
1、加载配置文件
2、通过系统变量重新设置相关配置
3、通过启动参数重新设置相关配置
4、初始化配置
5、重新设置日志类型
6、做一些配置信息的检查及容错
private static InputStreamReader loadConfig() throws AgentPackageNotFoundException, ConfigNotFoundException {
// 获取是否存在 SPECIFIED_CONFIG_PATH 系统变量,这个变量用于设置配置文件路径,
// 如果存在查看这个变量的值是否为空,非空加载该路径下的文件;空的话加载默认路径(/config/agent.config)下的文件
// 所以可能存在有些场景下需要将配置文件放在指定位置,那么可以通过这种方式设置
String specifiedConfigPath = System.getProperty(SPECIFIED_CONFIG_PATH);
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.");
}
其中 AgentPackagePath.getPath() 的作用是用于定位出 skywalking-agent.jar包的路径,因为知道了它的路径,那么其他的一些包的路径也就知道了,因为在默认情况下打包后的目录结构是固定的。
private static void overrideConfigBySystemProp() {
Properties systemProperties = System.getProperties();
for (final Map.Entry<Object, Object> prop : systemProperties.entrySet()) {
String key = prop.getKey().toString();
if (key.startsWith(ENV_KEY_PREFIX)) {
String realKey = key.substring(ENV_KEY_PREFIX.length());
AGENT_SETTINGS.put(realKey, prop.getValue());
}
}
}
如果系统中设置了相关的gaent配置,那么就进行替换。
public static void initializeConfig(Class configClass) {
if (AGENT_SETTINGS == null) {
LOGGER.error("Plugin configs have to be initialized after core config initialization.");
return;
}
try {
// 将 Properties 内的值赋值给 Config ,这里 Config 内的属性是以模块+配置的形式设计的,同样 配置文件中也是以模块+配置的形式设计的,不同的是配置文件内的属性都是小写的。只要将大小写相互转化就可以找到相互的映射关系
ConfigInitializer.initialize(AGENT_SETTINGS, configClass);
} catch (IllegalAccessException e) {
LOGGER.error(e,
"Failed to set the agent settings {}"
+ " to Config={} ",
AGENT_SETTINGS, configClass
);
}
}
static void configureLogger() {
switch (Config.Logging.RESOLVER) {
case JSON:
LogManager.setLogResolver(new JsonLogResolver());
break;
case PATTERN:
default:
LogManager.setLogResolver(new PatternLogResolver());
}
}
这里会根据配置中设置的日志格式进行设置
// 服务名是必须的,因为这涉及到服务注册
if (StringUtil.isEmpty(Config.Agent.SERVICE_NAME)) {
throw new ExceptionInInitializerError("`agent.service_name` is missing.");
} else {
// 命名空间也是必须的,这也涉及到服务注册
if (StringUtil.isNotEmpty(Config.Agent.NAMESPACE) || StringUtil.isNotEmpty(Config.Agent.CLUSTER)) {
Config.Agent.SERVICE_NAME = StringUtil.join(
SERVICE_NAME_PART_CONNECTOR,
Config.Agent.SERVICE_NAME,
Config.Agent.NAMESPACE,
Config.Agent.CLUSTER
);
}
}
// 后端地址也是必须的,这涉及到服务注册和心跳,因为数据上报可以通过kafka,所以数据上报可以不受他的影响
if (StringUtil.isEmpty(Config.Collector.BACKEND_SERVICE)) {
throw new ExceptionInInitializerError("`collector.backend_service` is missing.");
}
// 因为peer地址一般都大于3,所以如果设置的太小会强制改为200
if (Config.Plugin.PEER_MAX_LENGTH <= 3) {
LOGGER.warn(
"PEER_MAX_LENGTH configuration:{} error, the default value of 200 will be used.",
Config.Plugin.PEER_MAX_LENGTH
);
Config.Plugin.PEER_MAX_LENGTH = 200;
}
大家应该看到了,在加载配置的代码里面加载完配置后会重新设置日志类型,这个时候新的日志类型可能和原先刚开始的日志类型不一致,所以需要在最外面重新获取一次日志类型,不然日志类型还是原先skywalking默认的日志类型。