log4j2、log4j和logback动态修改日志级别工具类

工作中,在排查线上问题时,有以下场景在不重新部署或重启服务的情况下,需要动态调整线上日志级别

1、线上有些日志打印过多干扰有用的日志,需要动态修改线上日志记录器的打印日志级别,调高一些日志级别,打印出更少的干扰日志

2、由于线上日志级别打印的相关有用日志太少,需要动态修改线上日志记录器的打印日志级别,调低一些日志级别,打印出更多的有用日志

   本文的两个工具类使用的版本如下:

----------------logback---------------------

 <dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-core</artifactId>
   <version>1.1.8</version>
</dependency>
-----------------log4j2--------------------
<dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-log4j2</artifactId>

   <version>1.5.12.RELEASE</version>

</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>2.7</version>
 </dependency>

-----------------Log4j2Controller-------------------------

@Api(tags = "Log4j2Controller",description = "Log4j2控制类")
@RestController
@RequestMapping("/log/control")
public class Log4j2Controller {
private static final Logger logger = LoggerFactory.getLogger(Log4j2Controller.class);

@RequestMapping(value = "/getLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public Level getLoglevel(@RequestParam(value="package") String packageName) throws Exception {//package 指定修改日志级别的包路径 ,如 org.mybatis
logger.info("getLoglevel packageName is {}",packageName);
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
return ctx.getLogger(packageName).getLevel();
}

@RequestMapping(value = "/setLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public String setLoglevel(@RequestParam("loglevel") String logLevel, @RequestParam(value="package") String packageName) throws Exception {

logger.info("setLoglevel packageName is {},logLevel is {}",packageName,logLevel);
try{
Configurator.setLevel(packageName,Level.valueOf(logLevel));
}catch (Exception e){
logger.error("Exception setLoglevel packageName is {},logLevel is {}",packageName,logLevel,e);
return "fail";
}
return "success";
}
@RequestMapping(value = "/setRootLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public String setRootLoglevel(@RequestParam("loglevel") String logLevel) throws Exception {
logger.info("setRootLoglevel ,logLevel is {}",logLevel);
try {
//设置根记录器
Configurator.setRootLevel(Level.valueOf(logLevel));
}catch (Exception e){
logger.error("Exception setRootLoglevel ,logLevel is {}",logLevel,e);
return "fail";
}
return "success";
}
@RequestMapping(value = "/testLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public String testLoglevel() throws Exception {
logger.debug("current log level is debug..........debug...");
logger.info("current log level is info.........info......");
logger.warn("current log level is warn.......warn.......");
logger.error("current log level is error.......error.......");

return "success";
}
}


-----------------LogbackController-------------------------
@Api(tags = "LogbackController",description = "logback控制类")
@RestController
@RequestMapping("/log/control")
public class LogbackController {
private static Logger logger = LoggerFactory.getLogger(LogbackController.class);

@RequestMapping(value = "/getLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public Level getLoglevel(@RequestParam(value="package") String packageName) throws Exception {//package 指定修改日志级别的包路径 ,如 org.mybatis
logger.info("getLoglevel packageName is {}",packageName);
LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
return loggerContext.getLogger(packageName).getLevel();
}

@RequestMapping(value = "/setLoglevel", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String setLoglevel(@RequestParam("loglevel") String logLevel, @RequestParam(value="package") String packageName) throws Exception {
logger.info("setLoglevel packageName is {},logLevel is {}",packageName,logLevel);
try{
LoggerContext loggerContext =(LoggerContext)LoggerFactory.getILoggerFactory();
loggerContext.getLogger(packageName).setLevel(Level.valueOf(logLevel));
}catch (Exception e){
logger.error("Exception packageName is {},logLevel is {}",packageName,logLevel,e);
return "fail";
}
return "success";
}

@RequestMapping(value = "/setRootLoglevel", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String setRootLoglevel(@RequestParam("loglevel") String logLevel) throws Exception {
logger.info("setRootLoglevel ,logLevel is {}",logLevel);
try{
LoggerContext loggerContext =(LoggerContext)LoggerFactory.getILoggerFactory();
//设置全局日志级别
ch.qos.logback.classic.Logger logger=loggerContext.getLogger("root");
logger.setLevel(Level.toLevel(logLevel));
}catch (Exception e){
logger.error("Exception setRootLoglevel,logLevel is {}",logLevel,e);
return "fail";
}
return "success";
}

@RequestMapping(value = "/testLoglevel", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String testLoglevel() throws Exception {
logger.debug("current log level is debug..........debug...");
logger.info("current log level is info.........info......");
logger.warn("current log level is warn.......warn.......");
logger.error("current log level is error.......error.......");

return "success";
}
}
-----------------Log4jController-------------------------

@Api(tags = "Log4jController",description = "Log4j控制类")
@Controller
@RequestMapping("/log/control")
public class Log4jController extends BaseController {
private static final Logger logger = LoggerFactory.getLogger(Log4jController.class);

@RequestMapping(value = "/getLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public void getLoglevel(@RequestParam(value="package") String packageName,@RequestParam(value="token") String token,HttpServletRequest request, HttpServletResponse response) {
//package 指定修改日志级别的包路径 ,如 org.mybatis
if(validateToken(token,request)==false){
sendJson(response,NO_RIGHT_MSG);
return;
}
logger.info("getLoglevel packageName is {}",packageName);
org.apache.log4j.Logger log = LogManager.getLogger(packageName);
sendJson(response,log.getLevel().toString());;
}

@RequestMapping(value = "/setOneLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public void setOneLoglevel(@RequestParam("loglevel") String logLevel, @RequestParam(value="package") String packageName,@RequestParam(value="token") String token,HttpServletRequest request, HttpServletResponse response) throws Exception {
if(validateToken(token,request)==false){
sendJson(response,NO_RIGHT_MSG);
return;
}
//trace --> debug --> info --> warn --> error -->fatal
logger.info("setOneLoglevel packageName is {},logLevel is {}",packageName,logLevel);
try{
Level level = Level.toLevel(logLevel.toUpperCase());
org.apache.log4j.Logger log = LogManager.getLogger(packageName);
log.setLevel(level);
}catch (Exception e){
logger.error("Exception setLoglevel packageName is "+packageName+",logLevel is "+logLevel,e);
sendJson(response,"fail");
}
sendJson(response,"success");
}
@RequestMapping(value = "/setTwoLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public String setTwoLoglevel(@RequestParam("rootlevel") String rootLevel, @RequestParam("loglevel") String logLevel, @RequestParam(value="package") String packageName,@RequestParam(value="token") String token,HttpServletRequest request, HttpServletResponse response) throws Exception {
if(validateToken(token,request)==false){
sendJson(response,NO_RIGHT_MSG);
return "false";
}
logger.info("setTwoLoglevel packageName is {},rootLevel is {},logLevel is {}",packageName,logLevel);

setRootLoglevel(rootLevel,token,request,response);

setOneLoglevel(logLevel,token,packageName,request,response);

return "success";
}
@RequestMapping(value = "/setRootLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public void setRootLoglevel(@RequestParam("loglevel") String logLevel,@RequestParam(value="token") String token,HttpServletRequest request, HttpServletResponse response) throws Exception {
if(validateToken(token,request)==false){
sendJson(response,NO_RIGHT_MSG);
return;
}
logger.info("setRootLoglevel ,logLevel is {}",logLevel);
//all --> trace --> debug --> info --> warn --> error -->fatal --> off
try {
//设置根记录器
org.apache.log4j.Logger log = LogManager.getRootLogger();
log.setLevel(Level.toLevel(logLevel.toUpperCase()));
}catch (Exception e){
logger.error("Exception setRootLoglevel ,logLevel is {}",logLevel,e);
sendJson(response,"fail");
}
sendJson(response,"success");
}
@RequestMapping(value = "/getRootlevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public void getRootlevel(@RequestParam(value="token") String token,HttpServletRequest request, HttpServletResponse response) {//package 指定修改日志级别的包路径 ,如 org.mybatis
if(validateToken(token,request)==false){
sendJson(response,NO_RIGHT_MSG);
return;
}
logger.info("getRootlevel....");

org.apache.log4j.Logger log = LogManager.getRootLogger();
sendJson(response,log.getLevel().toString());;
}
@RequestMapping(value = "/testLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public void testLoglevel(@RequestParam(value="token") String token,HttpServletRequest request, HttpServletResponse response) throws Exception {
if(validateToken(token,request)==false){
sendJson(response,NO_RIGHT_MSG);
return;
}
logger.debug("current log level is debug..........debug...");
logger.info("current log level is info.........info......");
logger.warn("current log level is warn.......warn.......");
logger.error("current log level is error.......error.......");

sendJson(response,"success");

}

/**
* todo 此API不太准,覆盖的级别不全,导致输出结果可能不准确
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/getCurrentLoglevel", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public void getCurrentLoglevel(@RequestParam(value="token") String token,HttpServletRequest request, HttpServletResponse response) throws Exception {
if(validateToken(token,request)==false){
sendJson(response,NO_RIGHT_MSG);
return;
}
String result ="wrong level recognized";
if(logger.isDebugEnabled()){
logger.debug("current log level is debug..........debug...");
result= "current log level is debug";
}else if (logger.isInfoEnabled()){
logger.info("current log level is info.........info......");
result= "current log level is info";
}else if(logger.isErrorEnabled()){
logger.info("current log level is error.........error......");
result= "current log level is error";
}else if(logger.isWarnEnabled()){
logger.warn("current log level is warn.......warn.......");
result= "current log level is warn";
}else {
logger.error("current log level is not right.......wrong.......");
result= "current log level is right";
}
sendJson(response,result);

}
/**
* 查看现在包的日志级别
* @return
*/
@RequestMapping(value = "/loggers" , method = RequestMethod.GET)
public void index(@RequestParam(value="token") String token,HttpServletRequest request, HttpServletResponse response) {
if(validateToken(token,request)==false){
sendJson(response,NO_RIGHT_MSG);
return;
}
StringBuilder sb = new StringBuilder();
try {
sb.append( "<html>" );
Writer writer = response.getWriter();
response.setCharacterEncoding( "UTF-8" );
response.setContentType( "text/html; charset=utf-8" );
Enumeration logs = LogManager.getCurrentLoggers();
while (logs.hasMoreElements()) {
org.apache.log4j.Logger logger = (org.apache.log4j.Logger) logs.nextElement();
sb.append( "<span style='display:block;'>" );
sb.append(logger.getName()).append( ",\t" ).append(logger.getEffectiveLevel());
sb.append( "</span>" );
}
sb.append( "</html>" );
writer.write(sb.toString());
writer.flush();
if (writer != null ){
writer.close();
}
System.out.println(sb.toString());
sendJson(response,sb.toString());
} catch (Exception e) {
logger.error("Exception loggers ",e);
sendJson(response,"fail");
} }


@Autowired
private CacheUtil cacheUtil;

//令牌指定长度
private static final int token_length = 20;
//token使用限制次数
private static final int token_use_max_times = 10;
//缓存时间常量-1小时(计量秒)
public static final int EXPIRE_ONE_HOUR = 3600;
private static final String LOGCONTROL_CACHE_KEY_PREFIX = "LOGCONTROL_CACHE_KEY_";
protected final String NO_RIGHT_MSG="您没有API权限,请联系管理员!";

private boolean validateToken(String token,HttpServletRequest request) { String userName = LoginUtils.getuserName(request)
;
logger.info("validateToken token is {} ,userName is {}",token,userName);
if(checkuserNameAuth(userName)==false){
logger.info("没有API权限 validateToken ,userName is {}",userName);
return false;
} Object obj =
cacheUtil.get(LOGCONTROL_CACHE_KEY_PREFIX+token);
if(obj!=null){ Integer count = Integer.
valueOf(obj.toString());
if(count!=null && count<=token_use_max_times){
cacheUtil.incr(LOGCONTROL_CACHE_KEY_PREFIX+token);
return true;
} }


return false;

}
//是否是超管
private boolean checkuserNameAuth(String userName) { String surperUsers =

cacheUtils.getSpuers();
logger.error("surperUsers is "+surperUsers);
return Arrays.asList(surperUsers.split(",")).contains(userName);
}
/**
* @param request
* @param response
* @throws Exception
*/
// @RequestMapuserNameg(value = "/getAccessToken", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json;charset=UTF-8")
public void getAccessToken(HttpServletRequest request, HttpServletResponse response) throws Exception { String userName = LoginUtils.getuserName(request)
;
logger.info("getAccessToken userName is {}",userName);
if(checkuserNameAuth(userName)==false){
logger.info("没有API权限 validateToken ,userName is {}",userName);
sendJson(response,NO_RIGHT_MSG);
return;
} String token =
getRandomString(token_length);
logger.error("getAccessToken value " + token);
try{
cacheUtil.setEx(LOGCONTROL_CACHE_KEY_PREFIX+token,0,EXPIRE_ONE_HOUR);
}catch (Exception e){
logger.error("getAccessToken jimdb exception ",e);
} sendJson(response
,token);
}
//length用户要求产生字符串的长度
public static String getRandomString(int length){ String str=
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int i=0;i<length;i++){
int number=random.nextInt(52);
sb.append(str.charAt(number));
}
return sb.toString();
}}
 


备注:
1、logback和log4j2也有其它方式动态修改日志,但需要有方案或权限修改线上部署服务器的修改权限
比如logback.xml中
的configuration标签有<configuration scan="true" scanPeriod="60 seconds" debug="false">这种配置,
其中的scan如果设置为true的话,项目在启动后就会默认每一分钟自动扫描配置文件,如果有改变则重新加载,而我们还可以设置自动扫描的时间间隔属性scanPeriod,可以设置成30秒或者5分钟。
现在只要在logback.xml中添加一个属性,以后可以直接在服务器上修改logback.xml中root的level属性或者直接替换整个logback.xml文件,然后过会项目的日志输出级别就会改变了,不需要再重启项目。
2、本文主要介绍代码动态修改线上日志级别
3、log4j2遇到的坑,如果把某个logger的日志级别修改成debug,日志文件一直不会打印,但控制台会正常打印。
经排查是log4j2.xml中appender的过滤级别配置导致的,如下:
修改前:
<Filters>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
   修改后:
<Filters>

<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
</Filters>

简单对比说明:
onMatch和onMismatch都有三个属性值:Accept、DENY和NEUTRAL
onMatch="ACCEPT" 表示匹配该级别及以上
onMatch="DENY" 表示不匹配该级别及以上
onMatch="NEUTRAL" 表示该级别及以上的,由下一个filter处理,如果当前是最后一个,则表示匹配该级别及以上
onMismatch="ACCEPT" 表示匹配该级别以下
onMismatch="NEUTRAL" 表示该级别及以下的,由下一个filter处理,如果当前是最后一个,则不匹配该级别以下的
onMismatch="DENY" 表示不匹配该级别以下的

4、参照Log4jController可以增加AccessToken鉴权和权限过期机制





posted @ 2020-02-13 23:00  xuzhujack  阅读(1016)  评论(0编辑  收藏  举报
;