dubbo的accessLog可以记录请求信息,配置如下:
<dubbo:protocol accesslog="true" />
然后配置一下日志文件就OK了,具体百度一下,这里只分析一下其中的原理。
accessLog的处理是在filter中,具体为AccessLogFilter,看下类的定义
@Activate(group = Constants.PROVIDER, value = Constants.ACCESS_LOG_KEY)
public class AccessLogFilter implements Filter {
//....
}
类上有Activate注解,其中有两个属性,一个是group,值为provider,代表该filter对provider有效;另外一个是value,值为accesslog,根据扩展机制,代表当url中有accesslog属性,那么该Filter就会被激活,加入到过滤器链中。
接下来,分析一下调用过程,Filter触发入口是invoke方法
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
try {
String accesslog = invoker.getUrl().getParameter(Constants.ACCESS_LOG_KEY);
if (ConfigUtils.isNotEmpty(accesslog)) {//判断accesslog属性是否有值
//从上下文中获取调用信息,拼装成字符串
RpcContext context = RpcContext.getContext();
String serviceName = invoker.getInterface().getName();
String version = invoker.getUrl().getParameter(Constants.VERSION_KEY);
String group = invoker.getUrl().getParameter(Constants.GROUP_KEY);
StringBuilder sn = new StringBuilder();
sn.append("[").append(new SimpleDateFormat(MESSAGE_DATE_FORMAT).format(new Date())).append("] ").append(context.getRemoteHost()).append(":").append(context.getRemotePort())
.append(" -> ").append(context.getLocalHost()).append(":").append(context.getLocalPort())
.append(" - ");
if (null != group && group.length() > 0) {
sn.append(group).append("/");
}
sn.append(serviceName);
if (null != version && version.length() > 0) {
sn.append(":").append(version);
}
sn.append(" ");
sn.append(inv.getMethodName());
sn.append("(");
Class<?>[] types = inv.getParameterTypes();
if (types != null && types.length > 0) {
boolean first = true;
for (Class<?> type : types) {
if (first) {
first = false;
} else {
sn.append(",");
}
sn.append(type.getName());
}
}
sn.append(") ");
Object[] args = inv.getArguments();
if (args != null && args.length > 0) {
sn.append(JSON.json(args));
}
String msg = sn.toString();
// 字符串拼装完成,判断accesslog的值走两个流程
if (ConfigUtils.isDefault(accesslog)) {
LoggerFactory.getLogger(ACCESS_LOG_KEY + "." + invoker.getInterface().getName()).info(msg);
} else {
log(accesslog, msg);
}
}
} catch (Throwable t) {//ERROR}
return invoker.invoke(inv);
}
逻辑简单,最后判断accesslog的值如果是true或者default就直接调用日志框架进行写日志,如果不是则走另外的流程
这种情况下,accesslog属性是一个文件的地址,那么看下是如何处理的
private void log(String accesslog, String logmessage) {
init();// 初始化
Set<String> logSet = logQueue.get(accesslog);// 以文件路径为key获取集合
if (logSet == null) {
logQueue.putIfAbsent(accesslog, new ConcurrentHashSet<String>());
logSet = logQueue.get(accesslog);
}
if (logSet.size() < LOG_MAX_BUFFER) {// 集合上限数量是LOG_MAX_BUFFER
logSet.add(logmessage);
}
}
首先调用init方法初始化,然后将日志信息放到了一个集合当中,以文件名为key,区分不同的日志,那么很显然,会有一个线程去获取集合中的数据,看下init方法
private void init() {
if (logFuture == null) {
synchronized (logScheduled) {
if (logFuture == null) {
logFuture = logScheduled.scheduleWithFixedDelay(new LogTask(), LOG_OUTPUT_INTERVAL, LOG_OUTPUT_INTERVAL, TimeUnit.MILLISECONDS);
}
}
}
}
初始化了一个定时器,5s执行一次LogTask任务,LogTask里应该就是取队列写日志的地方,看下其run方法逻辑
for (Map.Entry<String, Set<String>> entry : logQueue.entrySet()) {
try {
String accesslog = entry.getKey();
Set<String> logSet = entry.getValue();
File file = new File(accesslog);
File dir = file.getParentFile();
if (null!=dir&&! dir.exists()) {
dir.mkdirs();
}
if (logger.isDebugEnabled()) {
logger.debug("Append log to " + accesslog);
}
if (file.exists()) {
String now = new SimpleDateFormat(FILE_DATE_FORMAT).format(new Date());
String last = new SimpleDateFormat(FILE_DATE_FORMAT).format(new Date(file.lastModified()));
if (! now.equals(last)) {
File archive = new File(file.getAbsolutePath() + "." + last);
file.renameTo(archive);
}
}
FileWriter writer = new FileWriter(file, true);
try {
for(Iterator<String> iterator = logSet.iterator();
iterator.hasNext();
iterator.remove()) {
writer.write(iterator.next());
writer.write("\r\n");
}
writer.flush();
} finally {
writer.close();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
非常简单,就是创建日志文件,写集合中的字符串,即日志写入文件中
网友评论