因为新入职的公司日志打印比较多,有些与业务无关、排查问题的日志打印没有必要实时输出,日志打印多了还会影响到系统性能,所以写了这个可以在运行时实时更改日志的功能,这个是针对logback的,如果使用的是log4j,请看LogLeverChangeController 类的main方法中被注释掉的示例。
效果如下:
下图中树形列表显示出所有可以设置日志级别的包和类,下图选中了org.apache.commons.beanutils.BeanUtils这个类,树形列表上面实现了它的日志级别。

从下拉选择框中选择WARN,点击修改按钮,将BeanUtils类的日志级别从info更改为warn:
提示修改成功:
直接贴实现代码:
spring controller类
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fintell.model.vo.BaseResponse;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
/**
* @author zqz 2018-06-27 12:02
* 应用运行时动态更改logback的日志打印级别。
*
* 一、指定某个类或包的日志打印级别,举个栗子:
*
* 类:com.fintell.common.task.ConfigLoaderTask
* 级别:WARN
* http://xxxxx/log/level?level=WARN&target=com.fintell.common.task.ConfigLoaderTask
*
* 二、指定整个应用的日志打印级别,慎用此功能,举个栗子:
* 指定应用的日志级别为ERROR
* http://xxxxx/log/level/all?level=ERROR
*
*/
@Controller
@RequestMapping(value = "/log")
public class LogLeverChangeController {
private static Logger log = LoggerFactory.getLogger(LogLeverChangeController.class);
private final static String PARAM_CHECH_LOG_LEVEL_ERROR = "请输入正确的日志级别:OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL;您输入的级别为:";
private final static String PARAM_CHECH_LOG_TARGET_ERROR = "目标类或包不能为空";
/**日志logger不存在提示信息**/
private final static String LOGGER_NOT_EXIST_CHECK = "日志logger不存在";
/** logback日志级别*/
private final static Set<String> logLevelCheck = new HashSet<String>();
static{
logLevelCheck.add("OFF");
logLevelCheck.add("ERROR");
logLevelCheck.add("WARN");
logLevelCheck.add("INFO");
logLevelCheck.add("DEBUG");
logLevelCheck.add("TRACE");
logLevelCheck.add("ALL");
}
/**
* 跳转到日志级别管理列表页面
* @return
*/
@RequestMapping(value = "/level/toList")
public String goToLogLevelPage() {
return "log/logTree";
}
@RequestMapping(value = "/list")
@ResponseBody
public List<LogInfoVO> listLogger() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
//获取应用中的所有logger实例
List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
List<LogInfoVO> allLogger = new ArrayList<LogInfoVO>();
for(ch.qos.logback.classic.Logger logger : loggerList) {
LogInfoVO info = new LogInfoVO();
info.setLevel(logger.getEffectiveLevel().levelStr);
info.setName(logger.getName());
allLogger.add(info);
}
return allLogger;
}
/**
* 获取某个类或包的日志级别
* @return
*/
@RequestMapping(value = "/get/level")
@ResponseBody
public BaseResponse getLoggerLevel(@RequestParam(value="target", required=true) String target) {
if(target == null || "".equals(target)) {
return BaseResponse.getFailInstance(PARAM_CHECH_LOG_TARGET_ERROR);
}
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger logger = loggerContext.getLogger(target);
if(logger!=null) {
LogInfoVO llc = new LogInfoVO();
llc.setName(logger.getName());
llc.setLevel(logger.getEffectiveLevel().levelStr);
return BaseResponse.getSuccessInstance(llc);
}
return BaseResponse.getFailInstance(LOGGER_NOT_EXIST_CHECK);
}
/**
* 获取日志tree列表
* @return
*/
@RequestMapping(value = "/tree")
@ResponseBody
public BaseResponse listLoggerTree() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
//获取应用中的所有logger实例
List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
List<LogInfoVO> allLogger = new ArrayList<LogInfoVO>();
for(ch.qos.logback.classic.Logger logger : loggerList) {
LogInfoVO info = new LogInfoVO();
info.setLevel(logger.getEffectiveLevel().levelStr);
info.setName(logger.getName());
allLogger.add(info);
}
ZtreeNodeVO root = null;
try {
root =this.getTree(allLogger);
}catch(Exception e) {
e.printStackTrace();
}
List<ZtreeNodeVO> nodes = root.getChildren();
return BaseResponse.getSuccessInstance(nodes);
}
/**
* 设置某个类或某个包的日志输出级别
* @param level 日志级别 只能取值:OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL
* @param target 类或包名
* @return
*/
@RequestMapping(value = "/level")
@ResponseBody
public BaseResponse changeLogLevel(/**@PathVariable("logLevel")**/
@RequestParam(value="level" , required=true) String level,
@RequestParam(value="target", required=true) String target) {
if(!checkLogLevel(level)) {
return BaseResponse.getFailInstance(PARAM_CHECH_LOG_LEVEL_ERROR+level);
}
if(target == null || "".equals(target)) {
return BaseResponse.getFailInstance(PARAM_CHECH_LOG_TARGET_ERROR);
}
LogLevelChangeVO llc = this.setLevel(target, level);
return llc == null ? BaseResponse.getFailInstance(LOGGER_NOT_EXIST_CHECK) : BaseResponse.getSuccessInstance(llc);
}
/**
* 设置整个系统的日志级别,慎用
* @param level
* @param target
* @return
*/
@RequestMapping(value = "/level/all")
@ResponseBody
public String changeAllLevel(
@RequestParam(value="level" , required=true) String level) {
if(!checkLogLevel(level)) {
return PARAM_CHECH_LOG_LEVEL_ERROR+level;
}
return this.setLevel(level);
}
/**
* 修改日志级别
* @param target
* @param level
* @return
*/
private LogLevelChangeVO setLevel(String target,String level) {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger logger = loggerContext.getLogger(target);
if(logger!=null) {
LogLevelChangeVO llc = new LogLevelChangeVO();
llc.setFullName(logger.getName());
llc.setBeforeLevel(logger.getEffectiveLevel().levelStr);
logger.setLevel(Level.valueOf(level));
llc.setAfterLevel(logger.getEffectiveLevel().levelStr);
return llc;
}
return null;
}
/**
* 设置整个应用的日志级别,慎用此功能
* @param level
* @return
*/
private String setLevel(String level) {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
//获取应用中的所有logger实例
List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
//返回设置结果
StringBuilder sb = new StringBuilder();
sb.append("修改前日志级别:\r\n");
//遍历更改每个logger实例的级别
for (ch.qos.logback.classic.Logger logger:loggerList){
//记录修改前的日志级别
this.recordLogLevel(sb, logger);
//设置日志级别
logger.setLevel(Level.toLevel(level));
}
sb.append("修改后日志级别:\r\n");
for(ch.qos.logback.classic.Logger logger:loggerList) {
//记录修改后的日志级别
this.recordLogLevel(sb, logger);
}
return sb.toString();
}
/**
* 检查日志级别是否正确
* @return true 验证通过 false 验证未通过
* **/
private boolean checkLogLevel(String level) {
return level==null || !logLevelCheck.contains(level)?false:true;
}
/**
* 构建树形结构
* @param allLogger
*/
private ZtreeNodeVO getTree(List<LogInfoVO> allLogger) {
HashMap<String,ZtreeNodeVO> treeMap = new HashMap<String,ZtreeNodeVO>();
String fullName;
// 根节点
ZtreeNodeVO root = new ZtreeNodeVO();
for(LogInfoVO logger : allLogger) {
fullName = logger.getName();
//如果是一級節點
if(!fullName.contains(".")) {
// 如果節點已經存在,則跳過
if(treeMap.containsKey(fullName)) {
continue;
}
// 如果節點不存在
ZtreeNodeVO topNode = new ZtreeNodeVO();
topNode.setName(fullName);
topNode.setFullName(fullName);
root.addChild(topNode);
treeMap.put(fullName, topNode);
continue;
}
// 如果非一级节点
String[] names = fullName.split("\\.");
// 父节点全名
String parentFullName ="";
ZtreeNodeVO paretnNode = null;
// 构建节点树
for(int i = 0 ,j = names.length ; i < j ; i++) {
ZtreeNodeVO node = treeMap.get(i==0?names[i]:parentFullName+"."+names[i]);
if(node == null) {
// 一级节点
if( i == 0 ) {
node = new ZtreeNodeVO();
node.setName(names[i]);
node.setFullName(names[i]);
// 添加到树中
root.addChild(node);
treeMap.put(names[i], node);
}else {
node = new ZtreeNodeVO();
node.setName(names[i]);
node.setFullName(parentFullName+"."+node.getName());
paretnNode.addChild(node);
treeMap.put(fullName, node);
}
}
// 供下次循环使用
if(i == j-1) {
parentFullName = "";
paretnNode = null;
}else {
parentFullName = node.getFullName();
paretnNode = node;
}
}
}
return root;
}
/**
* 记录日志级别
* @param sb
* @param logger
* @return
*/
private StringBuilder recordLogLevel(StringBuilder sb,ch.qos.logback.classic.Logger logger) {
sb.append(logger.getName());
if(logger.getEffectiveLevel()!=null) {
sb.append(",EffectiveLevel=").append(logger.getEffectiveLevel().levelStr);
}
if(logger.getLevel()!=null) {
sb.append(",level=").append(logger.getLevel().levelStr);
}
sb.append("#\r\n");
return sb;
}
/**
* test
* @param args
*/
public static void main(String[] args) {
//logback
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
//获取应用中的所有logger实例
List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
//遍历更改每个logger实例的级别
for (ch.qos.logback.classic.Logger logger:loggerList){
System.out.print("name:"+logger.getName());
if(logger.getLevel()!=null)
System.out.print("\t getLevel:"+logger.getLevel().levelStr);
System.out.println("\t getEffectiveLevel:"+logger.getEffectiveLevel().levelStr);
logger.setLevel(Level.toLevel("ALL"));
}
/**
//log4j
Enumeration enumeration = LogManager.getCurrentLoggers();
while (enumeration.hasMoreElements()){
org.apache.log4j.Logger logger = (org.apache.log4j.Logger) enumeration.nextElement();
logger.setLevel(org.apache.log4j.Level.toLevel("error"));
}
//log4j2
Collection<org.apache.logging.log4j.core.Logger> notCurrentLoggerCollection = org.apache.logging.log4j.core.LoggerContext.getContext(false).getLoggers();
Collection<org.apache.logging.log4j.core.Logger> currentLoggerCollection = org.apache.logging.log4j.core.LoggerContext.getContext().getLoggers();
Collection<org.apache.logging.log4j.core.Logger> loggerCollection = notCurrentLoggerCollection;
loggerCollection.addAll(currentLoggerCollection);
for (org.apache.logging.log4j.core.Logger logger:loggerCollection){
logger.setLevel(org.apache.logging.log4j.Level.toLevel("error"));
}
**/
log.info("info");
log.error("error");
}
/**
* 更改日志级别
* @author zqz
*
*/
static class LogLevelChangeVO{
/**全名**/
private String fullName;
/**更改前的级别**/
private String beforeLevel;
/**更改后的级别**/
private String afterLevel;
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getBeforeLevel() {
return beforeLevel;
}
public void setBeforeLevel(String beforeLevel) {
this.beforeLevel = beforeLevel;
}
public String getAfterLevel() {
return afterLevel;
}
public void setAfterLevel(String afterLevel) {
this.afterLevel = afterLevel;
}
}
/**
* 日志信息
* @author zqz
*/
static class LogInfoVO{
/**logger 名稱**/
private String name;
/**logger 級別**/
private String level;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
}
/**
* Ztree树形目录节点
* @author zqz
*/
static class ZtreeNodeVO {
// 最后一级名称
private String name;
// 全路径名
private String fullName;
// 日志級別,注意不能取名为level,与Ztree中默认节点字段冲突
// private String type;
// 子节点列表
private List<ZtreeNodeVO> children= null;
// 图标
private String icon;
public void addChild(ZtreeNodeVO node) {
if(children ==null) {
children = new ArrayList<ZtreeNodeVO>();
}
children.add(node);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public List<ZtreeNodeVO> getChildren() {
return children;
}
public void setChildren(List<ZtreeNodeVO> children) {
this.children = children;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
}
}
jsp页面(树形列表使用ztree实现,下面代码没有导入样式文件,没有导入jquery,自己去找吧)
<div>
<!--显示当前选中的包或类的日志级别-->
<div>
<span id="fullName">^_^</span>
=
<span id="level">^_^</span>
</div>
<!---更改当选中的包或类的日志级别 - 下拉选择列表--->
<div>
<select id="updateLevelSelect">
<option selected="selected" disabled="disabled" style='display:none' value=''></option>
<option value="OFF">OFF</option>
<option value="ERROR">ERROR</option>
<option value="WARN">WARN</option>
<option value="INFO">INFO</option>
<option value="DEBUG">DEBUG</option>
<option value="TRACE">TRACE</option>
<option value="ALL">ALL</option>
</select>
<!--提交按钮-->
<button id="modifyLogLevelBtn" type="button" class="btn btn-bold ">修改</button>
</div>
</div>
<!--class 树形目录结构-->
<div id="logLeverTree" class="ztree"></div>
<script type="text/javascript">
var treeName="logLeverTree";
var zTreeObj;
var fullName = $("#fullName");
var level = $("#level");
var updateLevelSelect = $("#updateLevelSelect");
/**当前选中的节点**/
var currentTreeNode;
var setting = {
data: {
key:{
title:"type"
}
},
callback: {
onClick:function(e,treeId,treeNode){
currentTreeNode = treeNode;
$.post("${basePath}/log/get/level",{"target":currentTreeNode.fullName}, function(result) {
if(result.status == 1){
updateLevelSelect.val(result.obj.level);
fullName.html(result.obj.name);
level.html(result.obj.level);
if(result.obj.level.indexOf(".") > -1){
var firstStr = currentTreeNode.name.substring(0,1);
if(firstStr == firstStr.toUpperCase()){
}
}
}else if(result.status == 0){
fullName.html("error");
level.html("");
currentTreeNode = null;
alert("请求失败:"+result.message);
}
});
}
}
};
$("#modifyLogLevelBtn").click(function(){
if(!currentTreeNode){
alert("请选择要修改的日志包或类");
return false;
}
var value =updateLevelSelect.val();
var target = currentTreeNode.fullName;
$.post("${basePath}/log/level",{"target":target,"level":value}, function(result) {
if(result.status == 1){
fullName.html(result.obj.name);
level.html(result.obj.afterLevel);
alert("操作成功!更改前级别:"+result.obj.beforeLevel+","+"更改后级别:"+result.obj.afterLevel);
} else if(result.status == 0){
alert(result.message);
}
});
});
$(document).ready(function(){
$.post("${basePath}/log/tree", function(result) {
if(result.status == 1){
zTreeObj = $.fn.zTree.init($("#"+treeName), setting, result.obj);
}
});
});
</script>
<script src="ztree/jquery.ztree.core.js"></script>