標籤:
在方法層級的java日誌輸出控制(一)這篇文章中主要討論了通過properties設定檔以及AOP技術批量控制方法層級的日誌輸出。
用properties設定檔的好處是不用更改程式即可控制日誌的輸出,然而大型的應用通常是分布式的,會有很多的伺服器,需要更改全部伺服器上的設定檔,然後再重啟應用。這將會是一件非常麻煩的事情。事實上在大型叢集應用中有更好的方法實現他。zookeeper的特性決定著它有一個應用情境就是叢集配置中心。本文不介紹zookeeper原理以及搭建,將直接使用zookeeper實現即時更改配置。本文表面上是做方法層級的日誌輸出控制在叢集中的實現,但實際上完全是一個zookeeper叢集配置中心的簡單實現。
先看ZkHelper工具類:
package com.lf.testLog4j.Util;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.concurrent.CountDownLatch;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.apache.zookeeper.*;import org.apache.zookeeper.data.Stat;/** * Created by lufei3 on 2015/7/29. */public class ZkHelper { private static final Logger logger = LogManager.getLogger(ZkHelper.class); Stat stat = null; private static ZooKeeper zk; static private ZkHelper instance; //本機搭建的zk偽叢集 public static String hostports = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"; //存放所有的log方法配置索引值對,系統系統時會首先從zk上拿到所有的節點資料放在map裡,只有當節點發生改變的時候 // 才會更新map,這樣避免每取一個節點就去zk裡面讀資料,從而節省了io時間。 public static Map<String, String> nodeList; //等待zk串連的方法,若沒有串連時會報錯 public static void waitUntilConnected(ZooKeeper zooKeeper, CountDownLatch connectedLatch) { if (ZooKeeper.States.CONNECTING == zooKeeper.getState()) { try { connectedLatch.await(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } } //watcher類,該watcher可以watch到zk串連以及每個節點的變化 static class ConnectedWatcher implements Watcher { private CountDownLatch connectedLatch; ConnectedWatcher(CountDownLatch connectedLatch) { this.connectedLatch = connectedLatch; } @Override public void process(WatchedEvent event) { if (event.getState() == Event.KeeperState.SyncConnected) { connectedLatch.countDown(); } logger.info("process : " + event.getType() + " " + event.getPath()); //當節點發生變化時更新map nodeListMapRefresh(String.valueOf(event.getType()), event.getPath()); } } public static void nodeListMapRefresh(String eventType, String path) { if (eventType.equals("NodeDeleted")) { nodeList.remove(path.substring(path.lastIndexOf("/") + 1)); } else if (eventType.equals("NodeCreated") || eventType.equals("NodeDataChanged")) { nodeList.put(path.substring(path.lastIndexOf("/") + 1), getNode(path)); } } //單例 public static ZkHelper Instance() { if (instance == null) { instance = new ZkHelper(hostports, 1000); } return instance; } //初始化串連並裝載節點列表 public boolean Init(String hostports, int times) { try { CountDownLatch connectedLatch = new CountDownLatch(1); Watcher watcher = new ConnectedWatcher(connectedLatch); zk = new ZooKeeper(hostports, times, watcher); waitUntilConnected(zk, connectedLatch); nodeList = getNodeList("/root/log"); } catch (Exception e) { System.out.println(e); return false; } return true; } //構造方法,構造時完成初始化 ZkHelper(String hostports, int times) { Init(hostports, times); } //擷取節點資訊 public static String getNode(String keys) { String re = ""; String ppath = keys; Stat stat = new Stat(); try { byte[] b = zk.getData(ppath, false, stat); //擷取節點的資訊及儲存的資料 re = new String(b); } catch (Exception e) { System.out.println(e); } return re; } //建立節點或更新節點資料 public void create(String key, String value) { try { stat = null; stat = zk.exists("/root", false); if (stat == null) { zk.create("/root", "rootData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } stat = null; stat = zk.exists("/root/log", false); if (stat == null) { zk.create("/root/log", "logData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } stat = null; stat = zk.exists("/root/log/" + key, true); if (stat == null) { zk.create("/root/log/" + key, value.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } else { zk.setData("/root/log/" + key, value.getBytes(), -1); } } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } //刪除節點 public void delete(String key) { stat = null; try { stat = zk.exists("/root/log/" + key, true); if (stat != null) { zk.delete("/root/log/" + key, -1); } } catch (InterruptedException e) { e.printStackTrace(); } catch (KeeperException e) { e.printStackTrace(); } } //擷取節點列表 public Map getNodeList(String path) { Map<String, String> map = new HashMap<String, String>(); List<String> nodeList = null; try { nodeList = zk.getChildren(path, true); for (String s : nodeList) { byte[] bytes = zk.getData(path + "/" + s, true, null); map.put(s, new String(bytes)); } } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return map; }}
LogConfig類:
package com.lf.zookeeperConfigCenter;import com.lf.testLog4j.Util.ZkHelper;public class LogConfig {public static void main(String args[]) {ZkHelper conf = ZkHelper.Instance();// conf.delete("TestLogAOP.test2");// conf.delete("TestLogAOP.test");conf.create("TestLogAOP.test2", "{\"level\":\"TRACE\",\"on\":1}");String str = conf.getNode("/root/log/TestLogAOP.test2");conf.create("TestLogAOP.test", "{\"level\":\"TRACE\",\"on\":0}");}}
這個類主要完成了zk的節點建立兩個節點的路徑和值分別為
/root/log/TestLogAOP.test,{"level":"TRACE","on":0}
/root/log/TestLogAOP.test2,{"level":"TRACE","on":1}與上篇的properties檔案配置一致。完善的配置中心應當有一個圖形介面。
LogActiveZK類:
package com.lf.testLog4j.aop;import com.google.gson.Gson;import com.lf.testLog4j.Util.ZkHelper;import com.lf.testLog4j.common.CommonLogUtil;import com.lf.testLog4j.domain.ConfigLog;import org.apache.commons.lang.StringUtils;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.aspectj.lang.JoinPoint;import java.util.Map;/** * Created by lufei3 on 2015/7/29. */public class LogActiveZK { private static final Logger logger = LogManager.getLogger(LogActive.class); public void before(JoinPoint jp){ ZkHelper zkHelper = ZkHelper.Instance(); Map map = zkHelper.nodeList; Gson gson = new Gson(); ConfigLog configLog = null; String cName = jp.getThis().toString(); Object[] args = jp.getArgs(); //獲得參數列表 String className = cName.substring(cName.lastIndexOf(".")+1,cName.lastIndexOf("@")); String methodName = jp.getSignature().getName(); //獲得方法名 String key = className + "." + methodName; String configValue = (String) map.get(key); try { configLog = gson.fromJson(configValue,ConfigLog.class); } catch (Exception e) { logger.error("Gson Format Exception!! logLevel:{}",configValue); e.printStackTrace(); return; } if(configLog == null) { return; } String logLevel = configLog.getLevel(); int offset = configLog.getOn(); if(StringUtils.isBlank(logLevel)){ logger.warn("method:{} log not config", key); return; } if(CommonLogUtil.isInfoEnable(logLevel, offset)) { logger.info("====Method:{};", key); if(args.length <=0){ logger.info("===={}方法沒有參數", methodName); } else{ for(int i=0; i<args.length; i++){ logger.info("====參數 {}:{} ", (i + 1), args[i]); } } } } public void after(){ logger.info("調用完畢!!"); }}
Spring配置:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" default-autowire="byName"> <bean id="LogActiveZK" class="com.lf.testLog4j.aop.LogActiveZK"></bean> <!--將日誌類注入到bean中。--> <bean id="testLogAOP" class="com.lf.testLog4j.service.TestLogAOP"></bean> <aop:aspectj-autoproxy proxy-target-class="true"/> <aop:config> <!--攔截service層的所有方法--> <aop:pointcut id="service" expression="execution(* com.lf.testLog4j.service.*.*(..))"/> <aop:aspect id="log" ref="LogActiveZK"> <aop:before pointcut-ref="service" method="before"/> <aop:after pointcut-ref="service" method="after"/> </aop:aspect> </aop:config></beans>
TestLogAOP類:
package com.lf.testLog4j.service;import org.springframework.stereotype.Component;/** * Created by lufei3 on 2015/7/14. */@Componentpublic class TestLogAOP { public void test(){ System.out.println("測試類別的test方法被調用"); } public void test2() { System.out.println("測試2的方法被調用!"); }}
測試:
package com.lf.testLog4j;import com.lf.testLog4j.service.TestLogAOP;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;/** * Created by lufei3 on 2015/7/14. */public class LogAOPMain { public static void main(String... args) { ApplicationContext act = new ClassPathXmlApplicationContext("spring/spring-config.xml"); TestLogAOP testLogAOP = (TestLogAOP) act.getBean("testLogAOP"); testLogAOP.test(); testLogAOP.test2(); }}
結果:
可見成功用zk作為log配置中心並通過Spring AOP攔截了方法層級的log輸出。
方法層級的java日誌輸出控制(二)叢集