Dubbo

Dubbo源码分析----过滤器之AccessLogFilter

2018-07-17  本文已影响84人  _六道木

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);
        }
    }

非常简单,就是创建日志文件,写集合中的字符串,即日志写入文件中

上一篇下一篇

猜你喜欢

热点阅读