在優化系統響應時間的時候,除了優化業務邏輯/代碼邏輯以外,把日誌改爲異步也是一種不錯的方案html
Log4j2在異步日誌的性能上已經無人能擋了,其異步效率高的主要緣由是使用disruptor來作異步隊列java
可是不少業務系統,尤爲是核心業務系統,須要打印詳細的報文和處理參數來追蹤問題;可是若是在logger.info以前就對報文進行格式化(轉json/xml之類),又會很是影響日誌輸出的性能,由於就算日誌打印這一步是異步的,可是logger.info這一操做是同步的;若是報文較大,格式化的這個操做可能會比較消耗時間git
那麼能不能讓序列化這一步也變成異步呢,讓logger.info直接輸出報文(對象)?github
log4j2固然是支持這種擴展的,只是須要本身定製一下apache
log4j2提供了一個MessageFactory接口,該接口用於根據消息類型的不一樣來建立消息,經過該接口便可實現對不一樣消息類型的格式化json
public class ExtendParameterizedMessageFactory extends AbstractMessageFactory { /** * 匹配對象類型消息,若是用logger.info(object)輸出,則會調用本方法 * @param message * @return */ @Override public Message newMessage(Object message) { Function<Object, String> formattedFunction = MessageTypeMapping.match(message); //建立本身的擴展消息類型 if(formattedFunction != null){ return new ExtendObjectParameterizedMessage(message,formattedFunction); }else{ return super.newMessage(message); } } @Override public Message newMessage(final String message, final Object... params) { return new ParameterizedMessage(message, params); } @Override public Message newMessage(final String message, final Object p0) { return new ParameterizedMessage(message, p0); } @Override public Message newMessage(final String message, final Object p0, final Object p1) { return new ParameterizedMessage(message, p0, p1); } @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) { return new ParameterizedMessage(message, p0, p1, p2); } @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { return new ParameterizedMessage(message, p0, p1, p2, p3); } @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { return new ParameterizedMessage(message, p0, p1, p2, p3, p4); } @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5); } @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) { return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6); } @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7) { return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8) { return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8, final Object p9) { return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } }
MessageFactory實現後,經過環境變量指定具體的MessageFactory實現類便可-Dlog4j2.messageFactory=com.github.kongwur.log4j2test.log4j2.extend.ExtendParameterizedMessageFactory
api
上面已經實現了MessageFactory,那麼怎麼才能作到異步格式化呢?app
log4j2提供了一個@AsynchronouslyFormattable
註解,用該註解修飾的Message Class,在async logger時,會將getFormat
的操做放實際打印以前(即異步以後),而不是異步以前異步
- If the Message is annotated with
AsynchronouslyFormattable
, it can be passed to another thread as is.- Otherwise, asynchronous logging components in the Log4j implementation will call
Message.getFormattedMessage()
before passing the Message object to another thread. This gives the Message implementation class a chance to create a formatted message String with the current value of the mutable object. The intention is that the Message implementation caches this formatted message and returns it on subsequent calls.
因此只須要建立本身的擴展消息類型,用@AsynchronouslyFormattable
修飾便可async
@AsynchronouslyFormattable public class ExtendObjectParameterizedMessage implements Message { private final transient Object obj; private transient String objectString; private final transient Function<Object,String> formattedFunction; public ExtendObjectParameterizedMessage(final Object obj,Function<Object,String> formattedFunction) { this.obj = obj; this.formattedFunction = formattedFunction; } @Override public String getFormattedMessage() { if (objectString == null) { objectString = formattedFunction.apply(obj); } return objectString; } @Override public String getFormat() { return getFormattedMessage(); } @Override public Object[] getParameters() { return new Object[] {obj}; } @Override public Throwable getThrowable() { return null; } @Override public int hashCode() { return obj != null ? obj.hashCode() : 0; } @Override public String toString() { return getFormattedMessage(); } }
實際項目中,可能會對多種對象/報文進行異步格式化,因此最好能夠動態匹配報文的類型,定製不一樣的格式化規則
這裏還能夠寫一個簡單類型格式化的映射:
public class MessageTypeMapping { private static final Map<Class, Function<Object,String>> MAPPING = new HashMap<>(); static { //添加對應類型的映射規則 MAPPING.put(BizObj.class,t -> { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } return t.toString(); }); } public static Function<Object,String> match(Object message){ if(message == null){ return null; } Class messageClass = message.getClass(); return MAPPING.get(messageClass); } }
實際調用時,沒法經過slf4j之類的api來打印,由於slf4j並無提供直接打印對象的方法
org.slf4j.Logger#info(java.lang.String) org.slf4j.Logger#info(java.lang.String, java.lang.Object) org.slf4j.Logger#info(java.lang.String, java.lang.Object, java.lang.Object) org.slf4j.Logger#info(java.lang.String, java.lang.Object...) org.slf4j.Logger#info(java.lang.String, java.lang.Throwable) org.slf4j.Logger#isInfoEnabled(org.slf4j.Marker) org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String) org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object) org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object) org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object...) org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Throwable)
可是咱們能夠直接經過log4j2的api來打印:
org.apache.logging.log4j.Logger logger = LogManager.getLogger(getClass()); //這裏打印對象會調用上面定義的ExtendObjectParameterizedMessage,實現「異步格式化」 logger.info(new BizObj());