天天看點

java項目運作時更改類或包的日志列印級别

因為新入職的公司日志列印比較多,有些與業務無關、排查問題的日志列印沒有必要實時輸出,日志列印多了還會影響到系統性能,是以寫了這個可以在運作時實時更改日志的功能,這個是針對logback的,如果使用的是log4j,請看LogLeverChangeController 類的main方法中被注釋掉的示例。

效果如下:

下圖中樹形清單顯示出所有可以設定日志級别的包和類,下圖選中了org.apache.commons.beanutils.BeanUtils這個類,樹形清單上面實作了它的日志級别。

java項目運作時更改類或包的日志列印級别

從下拉選擇框中選擇WARN,點選修改按鈕,将BeanUtils類的日志級别從info更改為warn:

java項目運作時更改類或包的日志列印級别

提示修改成功:

java項目運作時更改類或包的日志列印級别

直接貼實作代碼:

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>