咱們怎麼去作日誌同步呢?java
方案一:在Log4j的體系中有個東西叫作LoggerFilter,這個類的工具是用來作日誌過濾,每次咱們打印日誌的時候都會通過這個filter,來決定是否打印日誌。好比:apache
public int decide(LoggingEvent event) { if(this.levelMin != null) { if (event.getLevel().isGreaterOrEqual(levelMin) == false) { // level of event is less than minimum return Filter.DENY; } } if(this.levelMax != null) { if (event.getLevel().toInt() > levelMax.toInt()) { // level of event is greater than maximum // Alas, there is no Level.isGreater method. and using // a combo of isGreaterOrEqual && !Equal seems worse than // checking the int values of the level objects.. return Filter.DENY; } } if (acceptOnMatch) { // this filter set up to bypass later filters and always return // accept if level in range return Filter.ACCEPT; } else { // event is ok for this filter; allow later filters to have a look.. return Filter.NEUTRAL; } }
能夠看到咱們在配置文件裏面配置的緩存
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMax" value="ERROR" />
<param name="LevelMin" value="info" />
</filter>
根據上面的原理,咱們能夠定義一個filter,而後每次打印日誌的時候異步把日誌發送出去。服務器
/** * Alipay.com Inc. * Copyright (c) 2004-2016 All Rights Reserved. */ package com.zhangwei.learning.utils.log; import java.util.Date; import org.apache.log4j.spi.Filter; import org.apache.log4j.spi.LoggingEvent; import com.zhangwei.learning.model.Constants; import com.zhangwei.learning.model.LogResource; import com.zhangwei.learning.model.LogResourceDTO; /** * 日誌filter * @author Administrator * @version $Id: LogFilter.java, v 0.1 2016年7月4日 下午9:41:26 Administrator Exp $ */ public class LogSendFilter extends Filter implements Constants { /** 暫存日誌的DTO,當日志內容大小到達sendSize的時候,會把日誌發送給服務器 */ private ThreadLocal<LogResourceDTO> dtoThreadLocal = new ThreadLocal<LogResourceDTO>() { protected LogResourceDTO initialValue() { LogResourceDTO logResourceDTO = new LogResourceDTO(); return logResourceDTO; }; }; /** 日誌長度爲多少字符的時候會觸發發送日誌事件 */ private int sendSize = 100; /** 系統名 */ private String systemName = null; /** 用於接收日誌的服務器 */ private String logServerAddress = null; private LogSender logSender = new LogSender(); /** * @see org.apache.log4j.spi.Filter#decide(org.apache.log4j.spi.LoggingEvent) */ @Override public int decide(LoggingEvent event) { initOnEveryTime(); sendLogs(event); return Filter.ACCEPT; } /** * 每次調用Filter得時候都會初始化下 */ private void initOnEveryTime() { LogResourceDTO dto = dtoThreadLocal.get(); dto.setSystemName(systemName); } /** * 按照日誌條數發送 * @param event * @return */ private void sendLogs(LoggingEvent event) { //先檢查大小 String content = event.getMessage() + EMPTY_STRING; LogResourceDTO logResourceDTO = dtoThreadLocal.get(); logResourceDTO.addLogResource(new LogResource(new Date(), event.getLoggerName(), content)); //當數據沒到sendSize那麼保存日誌 if (logResourceDTO.getLogs().size() < sendSize) { return; } //日誌超sendSize了,那麼發送日誌,而後把新的日誌保存 //TODO send data try { logSender.sendLog(logResourceDTO.reflectToString(), logServerAddress); logResourceDTO.clear(); } catch (Exception e) { e.printStackTrace(); } finally { } return; } /** * Setter method for property <tt>sendSize</tt>. * * @param sendSize value to be assigned to property sendSize */ public void setSendSize(int sendSize) { this.sendSize = sendSize; } /** * 設置系統名 * @param systemName */ public void setSystemName(String systemName) { this.systemName = systemName; } /** * Setter method for property <tt>logServerAddress</tt>. * * @param logServerAddress value to be assigned to property logServerAddress */ public void setLogServerAddress(String logServerAddress) { this.logServerAddress = logServerAddress; } }
這個filter,實現了以下的功能,使用threadLocal的方式,每一個線程都會緩存一個日誌DTO對象,發送日誌的時候按照條數,達到定義的上限之後,就會把日誌發送出去,若是沒有達到上限,那就先把日誌緩存到本地緩存。app
/** * 發送日誌的task * @author Administrator * @version $Id: LogSendTask.java, v 0.1 2016年7月4日 下午11:55:21 Administrator Exp $ */ public class LogSender { /** 發送日誌的線程池 */ private ExecutorService THREAD_POOL = Executors .newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 3); public void sendLog(final String content, final String serverUrl) { THREAD_POOL.submit(new Callable<String>() { public String call() throws Exception { long start = System.currentTimeMillis(); HttpClientUtil.postData(serverUrl, new HashMap<String, String>() { /** */ private static final long serialVersionUID = 5828324817130371646L; { put(LogResourceDTO.LOG_HTTP_KEY, content); } }); long end = System.currentTimeMillis(); return (end - start) + "ms"; } }); } }
上面採用單獨的線程池,將日誌發送給指定的服務器,採用的是http協議。less
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss:SSS} %c %m%n" /> </layout> <!--限制輸出級別 --> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="LevelMax" value="ERROR" /> <param name="LevelMin" value="info" /> </filter> <filter class="com.zhangwei.learning.utils.log.LogSendFilter"> <param name="sendSize" value="0" /> <param name="systemName" value="test" /> <param name="logServerAddress" value="http://45.62.100.209:8080/DataReceiver/" /> </filter> </appender> <root> <priority value="debug" /> <appender-ref ref="CONSOLE" /> </root> </log4j:configuration>
filter配置,主要是配置了日誌接受服務器,以及本地緩存大小。異步
缺點分析:1.日誌同步在業務主鏈路上,若是有什麼問題會對系統有很大的影響。ide