在某些系统中,对于一些敏感信息(手机号,身份证号等),不宜直接打印到日志中,需要对这些敏感信息做打码处理,这里提供一个简单的示例。
一般情况,在输出日志时,系统会对log框架做一个简单的封装(类似对exception封装),将常用的debug,info,error等统一封装在logUtil中,系统统一调用。所以可以在这里做一个扩展,将日志先行处理,再调用日志框架输出,即可实现打码效果。
调用日志输出:
CmsLogger.audit("my mobile is : 18575541234 xxxxxxxx");
CmsLogger.error("my phone1 is : 3462777 xxxxxxxx");
CmsLogger.trace("my phone2 is : 0111-3462777 xxxxxxxx");
CmsLogger.audit("my phone3 is : 0111-3462777-011 xxxxxxxx");
CmsLogger.audit("my idNo is : 420611199111110655 xxxxxxxx");
CmsLogger.audit("my email is : [email protected] xxxxxxxx");
CmsLogger.audit("my passport is : 141234567 xxxxxxxx");
CmsLogger.audit("my soldier is : 3462745222222222 xxxxxxxx");
日志工具类:
package com.xxx.elis.elis_smp_cms.common.logging;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import com.xxx.elis.elis_smp_cms.common.util.PropertiesUtils;
@Component("CmsLogger")
public class CmsLogger {
private static final String COLON = ":";
private static Log tracerLogger;
private static Log auditLogger;
private static Log errorLogger;
private static Log taskLogger;
private static Log performanceLogger;
private static Log breakdownLogger;
private static Log redisLogger;
private static String tracerLoggerName="tracer";
private static String auditLoggerName="auditLogger";
private static String errorLoggerName="errorLogger";
private static String taskLoggerName="tasklog";
private static String breakdownLoggerName="breakdown";
private static String performanceLoggerName="performance";
private static String redisLoggerName="redislog";
private static boolean ouputClassAndMethodMsg=true;
private static boolean isFilter = Boolean.parseBoolean(PropertiesUtils.getPropertyValues("log.filter.open","false"));
public static ExecutorService executorService = Executors.newFixedThreadPool(5);
static{try {
afterPropertiesSet();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}}
private static int stackDeepth = 3;
public static void trace(Object message) {
message = generateMsg(message);
tracerLogger.debug(message);
}
public static void audit(Object message) {
message = generateMsg(message);
auditLogger.info(message);
}
public static void error(Object message) {
error(message,null);
// message = generateMsg(message);
// errorLogger.error(message);
}
public static void tasklog(Object message) {
message = generateMsg(message);
taskLogger.info(message);
}
public static void permlog(Object message) {
message = generateMsg(message);
performanceLogger.info(message);
}
public static void redislog(Object message) {
message = generateMsg(message);
redisLogger.info(message);
}
public static void breakdownlog(Object message) {
message = generateMsg(message);
breakdownLogger.info(message);
}
public static void afterPropertiesSet() throws Exception {
tracerLogger = LogFactory.getLog(tracerLoggerName);
auditLogger = LogFactory.getLog(auditLoggerName);
errorLogger = LogFactory.getLog(errorLoggerName);
taskLogger = LogFactory.getLog(taskLoggerName);
performanceLogger = LogFactory.getLog(performanceLoggerName);
breakdownLogger = LogFactory.getLog(breakdownLoggerName);
redisLogger = LogFactory.getLog(redisLoggerName);
}
public static void error(final Object message, Throwable t) {
Object msg = generateMsg(message);
errorLogger.error(msg, t);
}
//统一处理日志生成的方法
private static Object generateMsg(Object message) {
if (ouputClassAndMethodMsg) {
StackTraceElement[] stackTraceElement = Thread.currentThread()
.getStackTrace();
if ((stackTraceElement.length > getStackDeepth())
&& (stackTraceElement[getStackDeepth()] != null)) {
StackTraceElement stack = stackTraceElement[getStackDeepth()];
String clazzName = stack.getClassName();
message = clazzName.substring(clazzName.lastIndexOf(".") + 1,
clazzName.length())
+ COLON
+ stack.getMethodName()
+ COLON + stack.getLineNumber() + COLON + message;
}
}
//这里添加开关
if(isFilter) {
message = LogFilter.filter(message.toString());
}
return message;
}
public static boolean isTracerEnabled() {
return tracerLogger.isDebugEnabled();
}
public static boolean isAuditEnabled() {
return auditLogger.isInfoEnabled();
}
public static boolean isErrorEnabled() {
return errorLogger.isErrorEnabled();
}
public static boolean isOuputClassAndMethodMsg() {
return ouputClassAndMethodMsg;
}
public static void setOuputClassAndMethodMsg(boolean ouputClassAndMethodMsg) {
CmsLogger.ouputClassAndMethodMsg = ouputClassAndMethodMsg;
}
public static int getStackDeepth() {
return stackDeepth;
}
public static void setStackDeepth(int stackDeepth) {
CmsLogger.stackDeepth = stackDeepth;
}
public static void setTracerLoggerName(String tracerLoggerName) {
CmsLogger.tracerLoggerName = tracerLoggerName;
}
public static void setAuditLoggerName(String auditLoggerName) {
CmsLogger.auditLoggerName = auditLoggerName;
}
public static void setErrorLoggerName(String errorLoggerName) {
CmsLogger.errorLoggerName = errorLoggerName;
}
public static void setTaskLoggerName(String taskLoggerName) {
CmsLogger.taskLoggerName = taskLoggerName;
}
public static void setPerformanceLoggerName(String performanceLoggerName) {
CmsLogger.performanceLoggerName = performanceLoggerName;
}
public static void setBreakdownLoggerName(String breakdownLoggerName) {
CmsLogger.breakdownLoggerName = breakdownLoggerName;
}
public static void setRedisLoggerName(String redisLoggerName) {
CmsLogger.redisLoggerName = redisLoggerName;
}
}
日志拦截类:
package com.xxx.elis.elis_smp_cms.common.logging;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import com.xxx.elis.elis_smp_cms.common.util.PropertiesUtils;
//日志过滤器,最多支持可配置100个正则
public class LogFilter {
private static List<LogReg> LOG_REG_LIST;
private static Object lock = new Object();
private static void init() {
if (LOG_REG_LIST != null) {
return;
}
synchronized (lock) {
List<LogReg> temp = new ArrayList<LogReg>();
for (int i = 0; i < 100; i++) {
String val = PropertiesUtils.getPropertyValues("log.filter.list_" + i);
if (StringUtils.isEmpty(val)) {
break;
}
String[] reg = val.split(":");
String rank[] = reg[1].split("-");
int start = Integer.parseInt(rank[0]), end = Integer.parseInt(rank[1]);
LogReg logReg = new LogReg();
logReg.setReg(reg[0]);
logReg.setStart(start);
logReg.setEnd(end);
logReg.setType(reg[2]);
temp.add(logReg);
}
LOG_REG_LIST = temp;
}
}
public static String filter(String message) {
if (LOG_REG_LIST == null) {
init();
}
for (LogReg reg : LOG_REG_LIST) {
message = reg.filter(message);
}
return message;
}
}
日志拦截实体类:
package com.xxx.elis.elis_smp_cms.common.logging;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* log过滤的正则表达式
*
*/
public class LogReg {
private String reg;
private int start;
private int end;
private String type;
private Pattern pattern;
public String getReg() {
return reg;
}
public void setReg(String reg) {
this.reg = reg;
pattern = Pattern.compile(reg);
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String filter(String msg) {
Matcher result = pattern.matcher(msg);
while (result.find()) {
String ret = result.group();
if(type.equals("email")) {
end = ret.indexOf("@");
} else if(type.equals("phone") || type.equals("passport") || type.equals("soldier")) {
start = ret.length() - 4;
end = ret.length();
}
String replace = getStars(start,end);
String value = ret.substring(start, end);
msg = msg.replaceAll(value, replace);
}
return msg;
}
private static String getStars(int start ,int end) {
String stars = "";
for(int i = 0 ; i < (end - start) ; i++) {
stars += "*";
}
return stars;
}
}
配置文件读取类:
package com.xxx.elis.elis_smp_cms.common.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
public class PropertiesUtils {
private PropertiesUtils() {
}
// 属性文件名称
public static String propertiesFileName = "context-elis_smp_cms.properties";
private static Properties properties = null;
private static long lastModifed = 0;// 文件最后修改时间
private static long lastReadTime = 0;// 最后读取时间
private static void initProperties() {
if (System.currentTimeMillis() - lastReadTime > 60000) {// 间隔60秒检查文件是否被修改(修改配置项最多60秒后生效)
synchronized (PropertiesUtils.class) {
if (System.currentTimeMillis() - 60000 > lastReadTime) {
File file = null;
FileInputStream fis = null;
try {
URI uri = PropertiesUtils.class.getClassLoader().getResource(propertiesFileName).toURI();
file = new File(uri);
if (file.lastModified() > lastModifed) {
lastModifed = file.lastModified();
fis = new FileInputStream(file);
properties = new Properties();
properties.load(fis); // 从输入流中读取属性文件的内容
}
lastReadTime = System.currentTimeMillis();
} catch (Exception e) {
e.printStackTrace();
// CoreLogger.logError(null,"ConfigRead.init","UM2加载配置文件um-client-migrate.properties异常",e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
}
}
}
static {
initProperties();
}
/**
* 根据配置文件键获取其值
*
* @param key
* 属性名称
* @return 属性值
*/
public static String getPropertyValues(String key, String defaultVal) {
String result = getPropertyValues(key);
if (StringUtils.isBlank(result)) {
return defaultVal;
}
return result;
}
public static String getPropertyValues(String key) {
initProperties();
return StringUtils.trim((String) properties.get(key));
}
}
配置文件:
log.filter.open=true
#telphone
log.filter.list_0=(\\D1|$)(3|4|5|6|7|8|9)\\d{9}(\\D|$):4-8:mobile
#phone
log.filter.list_1=\\D((0\\d{2,3})-)?(\\d{7,8})(-(\\d{1,}))?:3-10:phone
#idNo
log.filter.list_2=\\D(\\d{14}|\\d{17})(\\d|[xX])(\\D|$):2-18:idNo
#email
log.filter.list_3=[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+:1-10:email
#soldier
log.filter.list_4=\\D[1-9]{1}(\\d{15}|\\d{18})(\\D|$):3-15:soldier
#passport
log.filter.list_5=\\D1[45][0-9]{7}|([PpSs]\\d{7})|([SsGg]\\d{8})|([GgTtSsLlQqDdAaFf]\\d{8})|([HhMm]\\d{8,10}):3-12:passport
代码不难理解,测试输出如下:
后续有其他的方案或者扩展再优化吧