因为apollo配置中心不支持.xml配置的写入,文档说明了当logback.xml文件放在apollo配置中心时,如何通过读取apollo缓存在本地的文件,实现日志配置的写入与热更新,实现通过apollo对logback.xml配置进行管理。
分步指南
1.首先把本地的logback.xml配置文件放到apollo配置中心,新建logback.xml配置文件的namespace,写入内容。
2.修改项目中的apollo配置,增加参数
apollo.bootstrap.namespaces=application,logback.xml
这个参数是指定项目用了哪些namespace的,后面的值要加上所有项目中用到的namespace,因为不加这个参数时apollo只会默认加载.properties的namespace,因为要用到logback.xml所以要加这个参数
3.apollo会在本地缓存配置文件,
默认的目录对于Mac/Linux,文件位置为
/opt/data/+appId+/config-cache/
对于Windows,文件位置为
C:/opt/data/+appId+/config-cache/
apollo在本地缓存的配置文件名称格式为
{appId}+{cluster}+{namespace}.properties
因此logback.xml这个namespace在本地的名称格式是{appId}+{cluster}+logback.xml.properties
首先通过代码去获取当前系统名称,根据apollo缓存文件默认规则去获取到logback.xml缓存在本地的配置文件
新建LogBackConfigLoader类,增加getPathName方法
private String getPathName(){
String pathName;
String system = System.getProperty("os.name");
if(system.toLowerCase().startsWith("win")){
pathName = "C:/opt/data/"+appId+"/config-cache/";
}else {
//除了win其他系统路径一样
pathName = "/opt/data/"+appId+"/config-cache/";
}
String cluster = "";
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
for (String in : inputArgs){
if(in.contains("Dapollo") && in.contains("cluster")){
String[] clusters = in.split("=");
cluster = clusters[1].replaceAll(" ","");
}
}
pathName += appId+"+"+cluster+"+"+"logback.xml.properties";
return pathName;
}
4.因为apollo缓存在本地的xml配置文件是经过转义与添加了一些内容的,所以要通过代码去反转义与去掉多余的内容,并生成正确的logback.xml文件,代码中增加方法
private String prop2Xml(String path) throws Exception{
StringBuffer fileContent = new StringBuffer();
File filename = new File(path);
InputStreamReader reader = new InputStreamReader(new FileInputStream(filename));
BufferedReader br = new BufferedReader(reader);
int f = 0;
String line = "";
line = br.readLine();
while(line != null) {
if(f>1){
//前两行注释不要
fileContent.append(line);
}
line = br.readLine();
f++;
}
//去掉content=
fileContent.replace(0,8,"");
//java反转义
String outContent = StringEscapeUtils.unescapeJava(fileContent.toString());
//生成xml文件
String outPath = path.replaceAll(".properties","");
File file=new File(outPath);
if(!file.exists()){
file.createNewFile();
}else {
//先删除再重新创建不然会报错
file.delete();
file.createNewFile();
}
FileOutputStream out=new FileOutputStream(file,true);
out.write(outContent.getBytes("utf-8"));
out.close();
return outPath;
}
5.已经获取到正确的logback.xml文件后,需要通过代码让logback重新加载配置,增加方法load
private void load (String externalConfigFileLocation) throws IOException, JoranException{
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
File externalConfigFile = new File(externalConfigFileLocation);
if(!externalConfigFile.exists()){
throw new IOException("Logback配置文件不存在");
}else{
if(!externalConfigFile.isFile()){
throw new IOException("Logback配置文件不正确");
}else{
if(!externalConfigFile.canRead()){
throw new IOException("Logback配置文件不能被读取");
}else{
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();
configurator.doConfigure(externalConfigFileLocation);
StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
}
}
}
}
6.在spring启动时,apollo已经启动完成了,因此可以在spring启动时对logback配置进行重新加载,调用上述方法,代码如下
@PostConstruct
private void initLog(){
try {
String pathName = getPathName();
String xmlPath = prop2Xml(pathName);
load(xmlPath);
}catch (Exception e){
logger.warn("获取apollo日志logback.xml配置失败,logback使用默认配置! 原因:"+e.getMessage());
}
}
7.要实现logback配置的热更新,就要实时监听apollo中logback.xml的变化,因此在spring中使用@ApolloConfigChangeListener注解来实现,当配置内容发生变化时再执行一遍上述操作。
@ApolloConfigChangeListener("logback.xml")
private void anotherOnChange(ConfigChangeEvent changeEvent) {
//当logback.xml文件改变的时候动态更新
try {
String pathName = getPathName();
String xmlPath = prop2Xml(pathName);
load(xmlPath);
}catch (Exception e){
logger.warn("apollo日志logback.xml配置热更新失败! 原因:"+e.getMessage());
}
}
8.完整的LogBackConfigLoader的代码如下。
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.util.List;
@Component
public class LogBackConfigLoader {
/**
* logger:日志对象.
* @since JDK 1.7
*/
private static Logger logger = LoggerFactory.getLogger(LogBackConfigLoader.class);
@Value("${app.id}")
private String appId;
private void load (String externalConfigFileLocation) throws IOException, JoranException{
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
File externalConfigFile = new File(externalConfigFileLocation);
if(!externalConfigFile.exists()){
throw new IOException("Logback配置文件不存在");
}else{
if(!externalConfigFile.isFile()){
throw new IOException("Logback配置文件不正确");
}else{
if(!externalConfigFile.canRead()){
throw new IOException("Logback配置文件不能被读取");
}else{
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();
configurator.doConfigure(externalConfigFileLocation);
StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
}
}
}
}
@PostConstruct
private void initLog(){
try {
String pathName = getPathName();
String xmlPath = prop2Xml(pathName);
load(xmlPath);
}catch (Exception e){
logger.warn("获取apollo日志logback.xml配置失败,logback使用默认配置! 原因:"+e.getMessage());
}
}
@ApolloConfigChangeListener("logback.xml")
private void anotherOnChange(ConfigChangeEvent changeEvent) {
//当logback.xml文件改变的时候动态更新
try {
String pathName = getPathName();
String xmlPath = prop2Xml(pathName);
load(xmlPath);
}catch (Exception e){
logger.warn("apollo日志logback.xml配置热更新失败! 原因:"+e.getMessage());
}
}
private String getPathName(){
String pathName;
String system = System.getProperty("os.name");
if(system.toLowerCase().startsWith("win")){
pathName = "C:/opt/data/"+appId+"/config-cache/";
}else {
//除了win其他系统路径一样
pathName = "/opt/data/"+appId+"/config-cache/";
}
String cluster = "";
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
for (String in : inputArgs){
if(in.contains("Dapollo") && in.contains("cluster")){
String[] clusters = in.split("=");
cluster = clusters[1].replaceAll(" ","");
}
}
pathName += appId+"+"+cluster+"+"+"logback.xml.properties";
return pathName;
}
private String prop2Xml(String path) throws Exception{
StringBuffer fileContent = new StringBuffer();
File filename = new File(path);
InputStreamReader reader = new InputStreamReader(new FileInputStream(filename));
BufferedReader br = new BufferedReader(reader);
int f = 0;
String line = "";
line = br.readLine();
while(line != null) {
if(f>1){
//前两行注释不要
fileContent.append(line);
}
line = br.readLine();
f++;
}
//去掉content=
fileContent.replace(0,8,"");
//java反转义
String outContent = StringEscapeUtils.unescapeJava(fileContent.toString());
//生成xml文件
String outPath = path.replaceAll(".properties","");
File file=new File(outPath);
if(!file.exists()){
file.createNewFile();
}else {
//先删除再重新创建不然会报错
file.delete();
file.createNewFile();
}
FileOutputStream out=new FileOutputStream(file,true);
out.write(outContent.getBytes("utf-8"));
out.close();
return outPath;
}
}