因為新入職的公司日志列印比較多,有些與業務無關、排查問題的日志列印沒有必要實時輸出,日志列印多了還會影響到系統性能,是以寫了這個可以在運作時實時更改日志的功能,這個是針對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>