Dubbo过滤器 - AccessLogFilter
概述
AccessLogFilter 是一个日志过滤器,在服务提供端生效,主要用于记录服务每一次的请求日志。虽然 AccessLogFilter 默认会被激活,但还是需要手动配置来开启日志的打印。注意:此日志量比较大,请注意磁盘容量。
配置
- 标签
1 2 3
| <dubbo:protocol accesslog="xxx"> <dubbo:provider accesslog="xxx"> <dubbo:service accesslog="xxx">
|
- 配置方式
- accesslog = “true” 或 accesslog=”default” : 向日志组件 Logger 中输出访问日志,如logbak,将日志输出到应用本身的 log 目录下。
- accesslog = “文件路径” :直接把访问日志输出到指定文件中。
日志打印规则:
如果配置的是将日志输出到日志组件,则立即写入。如果配置的是将日志输出到文件中,则将日志放入内存日志集合中,并开启定时任务进行日志持久化。
代码实现
属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| @Activate(group = Constants.PROVIDER, value = Constants.ACCESS_LOG_KEY) public class AccessLogFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AccessLogFilter.class);
private static final String ACCESS_LOG_KEY = "dubbo.accesslog";
private static final String FILE_DATE_FORMAT = "yyyyMMdd";
private static final String MESSAGE_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final int LOG_MAX_BUFFER = 5000;
private static final long LOG_OUTPUT_INTERVAL = 5000;
private final ConcurrentMap<String, Set<String>> logQueue = new ConcurrentHashMap<String, Set<String>>();
private final ScheduledExecutorService logScheduled = Executors.newScheduledThreadPool(2, new NamedThreadFactory("Dubbo-Access-Log", true));
private volatile ScheduledFuture<?> logFuture = null;
}
|
invoke 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| +--- AccessLogFilter @Override public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { try { String accesslog = invoker.getUrl().getParameter(Constants.ACCESS_LOG_KEY); if (ConfigUtils.isNotEmpty(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.toJSONString(args)); } String msg = sn.toString(); if (ConfigUtils.isDefault(accesslog)) { LoggerFactory.getLogger(ACCESS_LOG_KEY + "." + invoker.getInterface().getName()).info(msg); } else { log(accesslog, msg); } } } catch (Throwable t) { logger.warn("Exception in AcessLogFilter of service(" + invoker + " -> " + inv + ")", t); } return invoker.invoke(inv); }
|
辅助方法
仅用于设置日志写入到文件的情况下。
写日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| +--- AccessLogFilter
private void log(String accesslog, String logmessage) { init();
Set<String> logSet = logQueue.get(accesslog); if (logSet == null) { logQueue.putIfAbsent(accesslog, new ConcurrentHashSet<String>()); logSet = logQueue.get(accesslog); }
if (logSet.size() < LOG_MAX_BUFFER) { logSet.add(logmessage); } }
|
初始化任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| +--- AccessLogFilter
private void init() { if (logFuture == null) { synchronized (logScheduled) { if (logFuture == null) { logFuture = logScheduled.scheduleWithFixedDelay(new LogTask(), LOG_OUTPUT_INTERVAL, LOG_OUTPUT_INTERVAL, TimeUnit.MILLISECONDS); } } } }
|
日志任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| +--- AccessLogFilter
private class LogTask implements Runnable { @Override public void run() { try { if (logQueue != null && logQueue.size() > 0) { 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); } } } } catch (Exception e) { logger.error(e.getMessage(), e); } } }
|
小结
- AccessLogFilter 中会开启一个定时线程池,该线程池只有在指定了输出的文件时才会用到,该定时线程池会定时将队列中的日志写入文件中。
- 如果用户配置了使用应用本身的日志组件,则直接通过封装的 LoggerFactory 打印日志。如果用户配置了日志要输出到自定义的文件中,则会把日志加入到Map缓存中,key 是定义的 accesslog 的值,value 是对应的日志集合。后续等待定时线程不断遍历Map缓存,把日志写入到对应的文件中。
- 如果是日志输入到文件的情况,会有两个问题:
- 由于Set集合是无序的,因此日志输出到文件也是无序的
- 由于是异步刷盘,如果服务突然宕机会导致一部分日志丢失