在方法级别的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 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 map = new HashMap();
List 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
logger.info("====参数 {}:{} ", (i + 1), args[i]);
}
}
}
}
public void after(){
logger.info("调用完毕!!");
}
}
Spring配置:
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">
TestLogAOP类:
package com.lf.testLog4j.service;
import org.springframework.stereotype.Component;
/**
* Created by lufei3 on 2015/7/14.
*/
@Component
public 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();
}
}

结果:

java json打印日志过滤长字符串 java 输出日志_apache

可见成功用zk作为log配置中心并通过Spring AOP拦截了方法级别的log输出。