├── src ├── META-INF │ └── MANIFEST.MF ├── main │ ├── java │ │ └── com │ │ │ └── aliyun │ │ │ └── openservices │ │ │ └── log │ │ │ └── log4j2 │ │ │ ├── example │ │ │ └── Log4j2AppenderExample.java │ │ │ ├── LoghubAppenderCallback.java │ │ │ └── LoghubAppender.java │ └── resources │ │ └── log4j2-example.xml └── test │ ├── resources │ └── log4j2-test.xml │ └── java │ └── com │ └── aliyun │ └── openservices │ └── log │ └── log4j2 │ └── LoghubAppenderTest.java ├── .gitignore ├── .travis.yml ├── pom.xml ├── README.md ├── README_CN.md └── LICENSE /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: gson-2.2.4.jar json-lib-2.4-jdk15.jar protobuf-java-2.5.0. 3 | jar httpclient-4.5.1.jar log-loghub-producer-0.1.8.jar aliyun-log-0.6 4 | .10.jar lz4-1.3.0.jar commons-lang-2.5.jar commons-validator-1.4.0.ja 5 | r log4j-core-2.0.2.jar commons-logging-1.1.1.jar commons-codec-1.9.ja 6 | r fastjson-1.2.9.jar log4j-api-2.0.2.jar httpcore-4.4.3.jar commons-b 7 | eanutils-1.8.0.jar commons-collections-3.2.1.jar commons-digester-1.8 8 | .jar ezmorph-1.0.6.jar 9 | Main-Class: 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | # IntelliJ related files 25 | *.iml 26 | .idea/* 27 | 28 | target/ 29 | 30 | # Local test files 31 | log4j2-test-wubo.xml 32 | log4j2-wubo.xml 33 | 34 | # macos 35 | .DS_Store 36 | -------------------------------------------------------------------------------- /src/main/java/com/aliyun/openservices/log/log4j2/example/Log4j2AppenderExample.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.log4j2.example; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | /** 7 | * Created by brucewu on 2018/1/8. 8 | */ 9 | public class Log4j2AppenderExample { 10 | 11 | private static final Logger LOGGER = LogManager.getLogger(Log4j2AppenderExample.class); 12 | 13 | public static void main(String[] args) throws Exception { 14 | LOGGER.trace("log4j2 trace log"); 15 | LOGGER.debug("log4j2 debug log"); 16 | LOGGER.info("log4j2 info log"); 17 | LOGGER.warn("log4j2 warn log"); 18 | LOGGER.error("log4j2 error log", new RuntimeException("Runtime Exception")); 19 | Thread.sleep(1000 * 10); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/log4j2-example.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/com/aliyun/openservices/log/log4j2/LoghubAppenderCallback.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.log4j2; 2 | 3 | import com.aliyun.openservices.aliyun.log.producer.Callback; 4 | import com.aliyun.openservices.aliyun.log.producer.Result; 5 | import com.aliyun.openservices.log.common.LogItem; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by brucewu on 2018/1/5. 12 | */ 13 | public class LoghubAppenderCallback implements Callback { 14 | 15 | private Logger logger; 16 | 17 | private String project; 18 | 19 | private String logStore; 20 | 21 | private String topic; 22 | 23 | private String source; 24 | 25 | private List logItems; 26 | 27 | public LoghubAppenderCallback(Logger logger, String project, String logStore, String topic, 28 | String source, List logItems) { 29 | super(); 30 | this.logger = logger; 31 | this.project = project; 32 | this.logStore = logStore; 33 | this.topic = topic; 34 | this.source = source; 35 | this.logItems = logItems; 36 | } 37 | 38 | @Override 39 | public void onCompletion(Result result) { 40 | if (!result.isSuccessful()) { 41 | logger.error( 42 | "Failed to send log, project=" + project 43 | + ", logStore=" + logStore 44 | + ", topic=" + topic 45 | + ", source=" + source 46 | + ", logItem=" + logItems 47 | + ", errorCode=" + result.getErrorCode() 48 | + ", errorMessage=" + result.getErrorMessage()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/aliyun/openservices/log/log4j2/LoghubAppenderTest.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.log4j2; 2 | 3 | import com.aliyun.openservices.aliyun.log.producer.ProducerConfig; 4 | import org.apache.logging.log4j.Level; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | import org.apache.logging.log4j.ThreadContext; 8 | import org.apache.logging.log4j.status.StatusData; 9 | import org.apache.logging.log4j.status.StatusLogger; 10 | import org.junit.AfterClass; 11 | import org.junit.Test; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertNotEquals; 17 | 18 | public class LoghubAppenderTest { 19 | 20 | private static final Logger LOGGER = LogManager.getLogger(LoghubAppenderTest.class); 21 | 22 | private static void sleep() { 23 | ProducerConfig producerConfig = new ProducerConfig(); 24 | try { 25 | Thread.sleep(2 * producerConfig.getLingerMs()); 26 | } catch (InterruptedException e) { 27 | e.printStackTrace(); 28 | } 29 | } 30 | 31 | @AfterClass 32 | public static void checkStatusDataList() { 33 | sleep(); 34 | List statusDataList = StatusLogger.getLogger().getStatusData(); 35 | for (StatusData statusData : statusDataList) { 36 | statusData.getLevel(); 37 | } 38 | for (StatusData statusData : statusDataList) { 39 | Level level = statusData.getLevel(); 40 | assertNotEquals(statusData.getMessage().toString(), Level.ERROR, level); 41 | } 42 | } 43 | 44 | @Test 45 | public void testLogCommonMessage() { 46 | LOGGER.warn("This is a test common message logged by log4j2."); 47 | } 48 | 49 | @Test 50 | public void testLogThrowable() { 51 | ThreadContext.put("THREAD_ID1", "name1"); 52 | ThreadContext.put("THREAD_ID2", "name2"); 53 | LOGGER.error("This is a test error message logged by log4j2.", 54 | new UnsupportedOperationException("Log4j2 UnsupportedOperationException")); 55 | } 56 | 57 | @Test 58 | public void testLogLevelInfo() { 59 | LOGGER.info("This is a test error message logged by log4j2, level is info, should not be logged."); 60 | } 61 | 62 | @Test 63 | public void testParseStrToInt() { 64 | int val = LoghubAppender.parseStrToInt("123", 10); 65 | assertEquals(123, val); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true 5 | script: mvn test 6 | env: 7 | global: 8 | - secure: uX/godMDixveaHdt2SLLvqQfFeKJ10JYZq0oCBcvARTkm5LglnD25a2uWvj6mhswQ/Fsaw2ZwbSXxCXlm84U7X5+fpIErHMAlOkZIGEJtIF8QQ1RX5mnr89sxuB6ZH8UUhOLJFXWf32VPNmYaIb5v5EwCyCbVKEGdSWqvmt87jU1A920PBZvuXx3I/jqQ63l/o0/e6xy98sgSydfYjpJcuRHXoZIhBHUXBy8v7hB6SarXvvcAhv+RKzbm//OV1UJU+Y3HWWB0flRVXi70fNS+Ee7Y0uPHSJktgTzrTfGux0wzy6vMrnAXs/kF9ilTFOzbHPGOBqOAAqTKK8+3yV88EZPXt7lhR0FxIIOXW/f0S4oA+aGMtgRqKS+K/mp6kvGurgZqxUaS8XaEd/zgIpjJ3oZ+gSlKQzP8yipCV4a43SJk/NNoKaqXSwPIcLqcINxU2EGDJoih+heqUpXhVBY69JoNmehnWUFLgo65pUb+WNdBRM+ocbZqkIIGyCL/d0BYCRip3bT7emJ2Wo4GgeIpgDYqdGSoM0Y/82f2zuSsQFI1GOr5MOMrp7IJ3ZhACzEHJLMspAHoo6R+M8XVZ8q2sjK2o7LhfkSqxE628u4033DcBKDl74E6DUM1RrTMQ+o0KDWBVH23Hx4xmIyuXJ/Ev5zEcntLwSCA41BEWq83Og= 9 | - secure: vjfPUuE/L4igkSFb8uphrHXpaTo2M3SMBdViod1BZvuyFXXTFcmaKMV2zKlxhyVTXJGAwhvkPr2DUEYnU+uYWSNbgIjhi8hWCIZ7uN2xySUt5L2adUknE1iUo9kCUYlPX4gGRDNyN3mQqDN/Y68w2M8VHdeok66TUI4cBu5OTqvhiNrkHSegTNgxIZlDtb0oN8+yBuIj/8p4iOTqfxISoI0AHRJS/uuNQ62pAGxk+Lu0uA6IsOaSJ1yctbNrFYQYJdwZaMnG7S0sNmO2vZMSfm8rzcK8ITkjDKq5DAY8ecRY1P+l85uAgZIqHZwk7r842ZnOK4mDogDZYNKV5MnrQe4PQMm0LJTxYjxexn8wJ1TISCDKW6qhslm1Pdy3Bo3fyf668KVzr1YL6SmeZiDbFxqLBSKqBTSQ8CuC0n9Sh4mGZYJJzc9xT1FZ5onTWv9SBRCUUdDyWVEZB8qC+9BYdsN+3dDNbCzrt7btNSn57gpFqyR7YTik/x1Zbtb+GKMHuDyRaIbtrHyOyKwLVu4Yj8ym0cf61CsiV9Xk5EqgvYfDHvjdV8sjZkSgCbfCHn54u7KsF4E2BlAWN5tSERh7bCLKDSzX7afn4LFeiu6oTb96kNovUurWW9PV2xZbaVUoWqoOwTfZJMVQKMzUyeTq2DQR85KVmq+3E3FzGcPgIh8= 10 | - secure: met36qgbSpAUFdM0K1QNHITLAzcndalDhYOme/DWVEj1ddLOXYkJLjsy/dJrisvjYYFkbULPYKYAs5KnmrW+pZsVNumy09hB14lbpwzCA6+0n8d+I7ya6wHYy8AjuDlLGWoTRsFMnw4LYeq7sFY6HhUHyMhKvHuc3APwyfSPqnoHcqkxhBEXslzUiSuoMX6N7kuyHi12VADX7rqQ/uYPzC6copNJPqCoi1ddVcoTdSX72SlteMVZEyko/NDLBgSUcKcllhSs39wPb9mBjhAGQeuWkK11hCPidMKvA9ba8IQigk7+J2U5fMAg3coiooQyca7f45t7FIwXA0sQCSGAwitnGdeQ7AwAyKkWhjPdcuuuPq3UabAv9H+oUNapOGpDgQ4NatUpKihTMV9wPUNp2Yxl3tD1yYIL9OJfC+8//mPsggFUy/YewWUDgPmBoDdufySh5ZRd95GhAJClCx7JS9eNoUgB4Bg9K1kIrh6mT8SPGLYDAK5+j7HdJnjCplw0jJzSiDWbWusQxpTsPyEpr2G8Vgp2I8SPEMpG/A3nySExJD6N6Z9u5NU7fHlifpJJRWMqrMkGu6YtreiVwYsoxC80z01aVmrLavr2C1dh+uJ2OGH6ypv+uVXrIIqH+4Ncy6PbjVxdkEOQt7eU/b1FbDLe3ywZHWH57Lh32isgxCU= 11 | - secure: XgCvjBI/QpUFn4I3TQOv4Qw3NyaIUrbi0ThxSsSldWCzXdAJUEIiXlGFjYX2jv9kDxjfOTboMB/T8PRzqg2Gx8o0R8NE/A59T+Ny80ZzUhuov6BSjDbI5RtBkeGGDmFFv6G3oGOdpyS3nV0BC4DPm64tYKE1ON5Ij0EJ14OnrALxMc3dt0gnbnnio7pHZhstG9hG8wzTGEdmuaTu59pN0AqEpadat34uAfofV+YrtHaqUo0xbqv4yd65eV3Rtu6IYJR2AhqYVLH646eOeIsq36qcM7lC9OO6I4mPt/uRYt0R9124FUHg6I4uunllYPp8VPvDFC+QRQiAczlMOFyD1fufwX4nwG4r5FlLFIVoO95J7vMgbvCUxUZmJipb7urpyJC6QHeeG1e29WLwyz7t/56IvXVqGxvd2kBSHg92vN2God47ZxEh9Bvmq8e43B7r/SOJ3jc+XjfkA3hlx2rS+mkGAGR43/scadB7azTBPuS1V/KxyGli+8Ai/4I/rpAYI9z+zGNKdI16LcTne2inu5G6vCYwcPdmfX1aaEyquCFnUEU6bwvRUJiZPTXvklEWoHzWjCfF+OeMeTD2ldmyFpQba8H+rgpH/wZlvWEaLTvJ8oxwzwV4f8ns4PWIptRBd6gVU6ml57BOU00RH4IN5TbFA+klCyqfwELtYdX7Vbg= 12 | - secure: PCT4UU+iY+FVAwZDAn6ZOG0nPpIAeBDBTafeLC8MqN6yUFdUxgig7eZoOBBI9PZ2uoo7P5IqzrYGDm8mOzLWhJMBPk6Cweeqpf8qxzQC6+GzaWSV/NHKBLjWpOeLUosOTIuas9DiPt1JSuGwzm+wRzRNAds2s/Z1MzTQZHXJW1FvQ4BnRgMxmQaAqQ64eTAVqF0OMaPm+h7F2NjzUK9G1jmdQBbf6H0pWtkJIfM7owjqJq8SH7nI9GzBprAj6Bjp+NEIGe7dM9fVqOA+zGDzIYj80qsDT+C21qfrzI2aYBRRY6Yb7AHxGaB433E/F+ZlaxJXgZY+dyjeec7G6Gv/pyjKhLE4hrIZYa91zE6c6wiZCMgldRImHjbEd/gq9tRMuPN/7iw9heX8jAZxLaYr2tfb0Tl2nGiJeOE5as031oQnvdEap+xXtVZ67XP1WUMiPChwMIiGX7JHueqyikdr/5d9uglIfFchtfG/8a/U1x8f2LkXhc5Vk31ut19OR5Lr3bnKt2LWvb8jMfE9AS4oh7ZbnKr3H1K1StpdUmeKKJSas6G1YyvLAzxtDjHgd8nlFelgmOUvljW6i5fE5d1Mo/yZ4FMpJjUCCgcusQmgu6n7GHMRZkhMroF7uc9QEZuC0uU/Wv4Ik6z8wcvZZIfMDk3IYInHrB2ZOmErBMIkIgM= 13 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.sonatype.oss 7 | oss-parent 8 | 9 9 | 10 | 11 | com.aliyun.openservices 12 | aliyun-log-log4j2-appender 13 | 0.1.15 14 | 15 | aliyun log log4j2 appender 16 | http://www.aliyun.com 17 | aliyun log log4j2 appender 18 | 19 | 20 | 21 | 22 | 23 | repo 24 | Copyright (C) Alibaba Cloud Computing. All rights reserved. 25 | 26 | 27 | 28 | 29 | https://github.com/aliyun/aliyun-log-log4j2-appender 30 | scm:git:https://github.com/aliyun/aliyun-log-log4j2-appender.git 31 | scm:git:https://github.com/aliyun/aliyun-log-log4j2-appender.git 32 | 33 | 34 | 35 | aliyunproducts 36 | Aliyun Log development team 37 | aliyunsdk@aliyun.com 38 | 39 | 40 | 41 | 42 | UTF-8 43 | 1.6 44 | 1.6 45 | 46 | 47 | 48 | 49 | junit 50 | junit 51 | 4.12 52 | test 53 | 54 | 55 | org.apache.logging.log4j 56 | log4j-core 57 | 2.17.0 58 | 59 | 60 | com.google.protobuf 61 | protobuf-java 62 | 2.5.0 63 | 64 | 65 | com.aliyun.openservices 66 | aliyun-log-producer 67 | 0.3.24 68 | 69 | 70 | joda-time 71 | joda-time 72 | 2.9.9 73 | 74 | 75 | 76 | 77 | 78 | sonatype-nexus-snapshots 79 | https://s01.oss.sonatype.org/content/repositories/snapshots 80 | 81 | 82 | sonatype-nexus-staging 83 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-gpg-plugin 91 | 1.6 92 | 93 | 94 | sign-artifacts 95 | verify 96 | 97 | sign 98 | 99 | 100 | 101 | 102 | 103 | org.sonatype.plugins 104 | nexus-staging-maven-plugin 105 | 1.6.3 106 | true 107 | 108 | sonatype-nexus-staging 109 | https://s01.oss.sonatype.org/ 110 | true 111 | 112 | 113 | 114 | maven-compiler-plugin 115 | 3.5.1 116 | 117 | ${maven.compiler.source} 118 | ${maven.compiler.target} 119 | ${project.build.sourceEncoding} 120 | 121 | 122 | 123 | org.apache.maven.plugins 124 | maven-source-plugin 125 | 3.0.0 126 | 127 | 128 | attach-sources 129 | 130 | jar 131 | 132 | 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-javadoc-plugin 138 | 2.10.4 139 | 140 | ${project.build.sourceEncoding} 141 | ${project.build.sourceEncoding} 142 | 143 | 144 | author 145 | X 146 | 147 | 148 | 149 | 150 | 151 | attach-javadocs 152 | 153 | jar 154 | 155 | 156 | 157 | 158 | 159 | org.apache.maven.plugins 160 | maven-release-plugin 161 | 2.1 162 | 163 | forked-path 164 | false 165 | -Psonatype-oss-release -Dmaven.test.skip=true 166 | 167 | 168 | 169 | org.apache.maven.plugins 170 | maven-compiler-plugin 171 | 172 | 8 173 | 8 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Log4j2 Appender 2 | 3 | [![Build Status](https://travis-ci.org/aliyun/aliyun-log-log4j2-appender.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-log-log4j2-appender) 4 | [![License](https://img.shields.io/badge/license-Apache2.0-blue.svg)](/LICENSE) 5 | 6 | [中文版README](/README_CN.md) 7 | 8 | ## Aliyun Log Log4j2 Appender 9 | 10 | Apache Log4j2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x. You can control the destination of the log through Log4j2. It can be console, file, GUI components, socket, NT event log, syslog. You can control the output format for each log as well. You can control the generation process of the log through log level. The most interesting thing is you can complete the above things through a configuration file and without any code modification. 11 | 12 | You can set the destination of your log to AliCloud Log Service through `Aliyun Log Log4j2 Appender`. The format of the log in AliCloud Log Service is as follows: 13 | ``` 14 | level: ERROR 15 | location: com.aliyun.openservices.log.logback.example.LogbackAppenderExample.main(LogbackAppenderExample.java:18) 16 | message: error log 17 | throwable: java.lang.RuntimeException: xxx 18 | thread: main 19 | time: 2018-01-02T03:15+0000 20 | log: 2018-01-02 11:15:29,682 ERROR [main] com.aliyun.openservices.log.logback.example.LogbackAppenderExample: error log 21 | __source__: xxx 22 | __topic__: yyy 23 | ``` 24 | Field Specifications: 25 | + `level` stands for log level 26 | + `location` is logs's output position 27 | + `message` is the content of the log 28 | + `throwable` is exception of the log (this field will appear only if the exception is recorded) 29 | + `thread` stands for thread name 30 | + `time` is the log's generation time (you can configure it's format through timeFormat and timeZone) 31 | + `log` is custom log format (this field will appear only if you configure the layout) 32 | + `__source__` is the log's source, you can specify its value in conf file 33 | + `__topic__` is the log's topic, you can specify its value in conf file 34 | 35 | ## Advantage 36 | + `Disk Free`: the generation data will be send to AliCloud Log Service in real time through network. 37 | + `Without Refactor`: if your application already use Log4j2, you can just add Log4j2 appender to your configuration file. 38 | + `Asynchronous and High Throughput`: the data will be send to AliCloud Log Service asynchronously. It is suitable for high concurrent write. 39 | + `Context Query`: at server side, in addition to searching log with keywords, you can obtain the context information of original log as well. 40 | 41 | 42 | ## Supported Version 43 | * aliyun-log-producer 0.3.9 44 | * protobuf-java 2.5.0 45 | 46 | > This version is mainly suitable for Log4j 2.X versions. For Log4j 1.x, please refer to 47 | [aliyun-log-log4j-appender](https://github.com/aliyun/aliyun-log-log4j-appender) 48 | 49 | 50 | ## Configuration Steps 51 | 52 | ### 1. Adding the Dependencies in pom.xml 53 | 54 | ``` 55 | 56 | com.google.protobuf 57 | protobuf-java 58 | 2.5.0 59 | 60 | 61 | com.aliyun.openservices 62 | aliyun-log-log4j2-appender 63 | 0.1.12 64 | 65 | ``` 66 | 67 | ### 2. Modify the Configuration File 68 | 69 | Take `log4j2.xml` as an example, you can configure the appender and logger related to AliCloud Log Services as follows: 70 | ``` 71 | 72 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | ``` 101 | 102 | ## Parameter Description 103 | 104 | The `Aliyun Log Log4j2 Appender` provides the following parameters. 105 | 106 | 107 | | Configuration | Default Value | Description | 108 | |--------------------------------|----------------------------|---------------------------------------------------------------------------------------------------------| 109 | | project | | Specify the project name of your log services, required | 110 | | logStore | | Specify the logstore of your log services, required | 111 | | endpoint | | Specify the HTTP endpoint of your log services, required | 112 | | accessKeyId | | Specify the account information of your log services, required | 113 | | accessKeySecret | | Specify the account information of your log services, required | 114 | | totalSizeInBytes | 104857600 | The upper limit log size that a single producer instance can hold, default is 100MB. | 115 | | maxBlockMs | 0 | If the producer has insufficient free space, the caller's maximum blocking time on the send method. It is strongly recommended to set this value to 0. | 116 | | ioThreadCount | 8 | The thread pool size for executing log sending tasks, defaults is the number of processors available. | 117 | | batchSizeThresholdInBytes | 524288 | When the size of the cached log in a Producer Batch is greater than or equal batchSizeThresholdInBytes, the batch will be sent, default is 512KB, maximum can be set to 5MB. | 118 | | batchCountThreshold | 4096 | When the number of log entries cached in a ProducerBatch is greater than or equal to batchCountThreshold, the batch will be sent. | 119 | | lingerMs | 2000 | A ProducerBatch has a residence time from creation to sending, defaulting is 2 seconds and a minimum of 100 milliseconds. | 120 | | retries | 10 | The number of times a Producer Batch can be retried if it fails to send for the first time, default is 10. | 121 | | baseRetryBackoffMs | 100 | The backoff time for the first retry, default 100 milliseconds. | 122 | | maxRetryBackoffMs | 100 | The maximum backoff time for retries, default is 50 seconds. | 123 | | topic | | Specify the topic of your log, default is "", optional | 124 | | source | | Specify the source of your log, default is host ip, optional | 125 | | timeFormat | yyyy-MM-dd'T'HH:mm:ssZ | Specify time format of the field time, default is yyyy-MM-dd'T'HH:mm:ssZ, optional | 126 | | timeZone | UTC | Specify timezone of the field time, default is UTC, optional | 127 | | ignoreExceptions | true | Whether to ignore exceptions | 128 | | timePrecision | secs | Specify time precision of the log time sent to Aliyun Log Service, the option `ns` uses `SystemNanoClock` to get time, and may be performance-intensive.
Valid options are:
- secs: seconds
- ms: milliseconds
- ns: nanoseconds | 129 | 130 | 131 | ## Sample Code 132 | 133 | [Log4j2AppenderExample.java](/src/main/java/com/aliyun/openservices/log/log4j2/example/Log4j2AppenderExample.java) 134 | 135 | [log4j2-example.xml](/src/main/resources/log4j2-example.xml) 136 | 137 | ## Troubleshooting 138 | 139 | The `Aliyun Log Log4j2 Appender` will record the exceptions generated by log4j2 appender itself through `org.apache.logging.log4j.status.StatusLogger`. By default, log4j2 will register a listener named `StatusConsoleListener` for StatusLogger. So the exceptions generated by log4j2 appender will appear in console by default. When you encounter issue, please pay attention to the error message in console. 140 | 141 | ## Contributors 142 | [@LNAmp](https://github.com/LNAmp) [@zzboy](https://github.com/zzboy) made a great contribution to this project. 143 | 144 | Thanks for the excellent work by [@LNAmp](https://github.com/LNAmp) [@zzboy](https://github.com/zzboy). 145 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Log4j2 Appender 2 | 3 | [![Build Status](https://travis-ci.org/aliyun/aliyun-log-log4j2-appender.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-log-log4j2-appender) 4 | [![License](https://img.shields.io/badge/license-Apache2.0-blue.svg)](/LICENSE) 5 | 6 | [README in English](/README.md) 7 | 8 | ## Aliyun Log Log4j2 Appender 9 | Log4j2 是 log4j 的升级版本。通过使用 Log4j2,您可以控制日志信息输送的目的地是控制台、文件、GUI 组件、甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;您也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,您能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。 10 | 11 | 通过 Aliyun Log Log4j2 Appender,您可以控制日志的输出目的地为阿里云日志服务。需要注意的是,Aliyun Log Log4j2 Appender 不支持设置日志的输出格式,写到日志服务中的日志的样式如下: 12 | ``` 13 | level: ERROR 14 | location: com.aliyun.openservices.log.log4j2.example.Log4j2AppenderExample.main(Log4j2AppenderExample.java:16) 15 | message: error log 16 | throwable: java.lang.RuntimeException: xxx 17 | thread: main 18 | time: 2018-01-02T03:15+0000 19 | log: 2018-01-02 11:15:29,682 ERROR [main] com.aliyun.openservices.log.log4j2.example.Log4j2AppenderExample: error log 20 | __source__: xxx 21 | __topic__: yyy 22 | ``` 23 | 其中: 24 | + level 日志级别。 25 | + location 日志打印语句的代码位置。 26 | + message 日志内容。 27 | + throwable 日志异常信息(只有记录了异常信息,这个字段才会出现)。 28 | + thread 线程名称。 29 | + time 日志打印时间(可以通过 timeFormat 或 timeZone 配置 time 字段呈现的格式和时区)。 30 | + log 自定义日志格式(只有设置了 encoder,这个字段才会出现)。 31 | + \_\_source\_\_ 日志来源,用户可在配置文件中指定。 32 | + \_\_topic\_\_ 日志主题,用户可在配置文件中指定。 33 | 34 | 35 | ## 功能优势 36 | + 日志不落盘:产生数据实时通过网络发给服务端。 37 | + 无需改造:对已使用 Log4j2 应用,只需简单配置即可采集。 38 | + 异步高吞吐:高并发设计,后台异步发送,适合高并发写入。 39 | + 上下文查询:服务端除了通过关键词检索外,给定日志能够精确还原原始日志文件上下文日志信息。 40 | 41 | 42 | ## 版本支持 43 | * aliyun-log-producer 0.3.9 44 | * protobuf-java 2.5.0 45 | 46 | > 该版本主要适配于Log4J 2.X以上版本,以下版本请参考[aliyun-log-log4j-appender](https://github.com/aliyun/aliyun-log-log4j-appender) 47 | 48 | 49 | ## 配置步骤 50 | 51 | ### 1. maven 工程中引入依赖 52 | 53 | ``` 54 | 55 | com.google.protobuf 56 | protobuf-java 57 | 2.5.0 58 | 59 | 60 | com.aliyun.openservices 61 | aliyun-log-log4j2-appender 62 | 0.1.12 63 | 64 | ``` 65 | 66 | ### 2. 修改配置文件 67 | 68 | 以xml型配置文件`log4j2.xml`为例(不存在则在项目根目录创建),配置 Loghub 相关的 appender 与 Logger,可参考文件 [log4j2-example.xml](/src/main/resources/log4j2-example.xml),例如: 69 | ``` 70 | 71 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | ``` 100 | 其中: 101 | level 是日志记录的优先级,优先级从高到低分别是 ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,您可以控制应用程序中相应级别的日志信息的开关。比如在这里定义了 WARN 级别,则应用程序中所有 INFO、DEBUG 102 | 级别的日志信息将不被打印出来。 103 | 104 | ## 参数说明 105 | 106 | Aliyun Log Log4j2 Appender 可供配置的属性(参数)如下,其中注释为必选参数的是必须填写的,可选参数在不填写的情况下,使用默认值。 107 | 108 | | 配置项 | 默认值 | 说明 | 109 | |-----------------------------|----------------------------|-----------------------------------------------------------------------------------------------| 110 | | project | | 日志服务的 project 名,必选参数 | 111 | | logStore | | 日志服务的 logstore 名,必选参数 | 112 | | endpoint | | 日志服务的 HTTP 地址,必选参数 | 113 | | accessKeyId | | 用户身份标识,必选参数 | 114 | | accessKeySecret | | 用户身份标识,必选参数 | 115 | | totalSizeInBytes | 104857600 | 单个 producer 实例能缓存的日志大小上限,默认为 100MB。 | 116 | | maxBlockMs | 0 | 如果 producer 可用空间不足,调用者在 send 方法上的最大阻塞时间,默认为 0 秒。为了不阻塞打印日志的线程,强烈建议将该值设置成 0。| 117 | | ioThreadCount | 8 | 执行日志发送任务的线程池大小,默认为可用处理器个数。 | 118 | | batchSizeThresholdInBytes | 524288 | 当一个 ProducerBatch 中缓存的日志大小大于等于 batchSizeThresholdInBytes 时,该 batch 将被发送,默认为 512 KB,最大可设置成 5MB。| 119 | | batchCountThreshold | 4096 | 当一个 ProducerBatch 中缓存的日志条数大于等于 batchCountThreshold 时,该 batch 将被发送,默认为 4096,最大可设置成 40960。| 120 | | lingerMs | 2000 | 一个 ProducerBatch 从创建到可发送的逗留时间,默认为 2 秒,最小可设置成 100 毫秒。 | 121 | | retries | 10 | 如果某个 ProducerBatch 首次发送失败,能够对其重试的次数,默认为 10 次。如果 retries 小于等于 0,该 ProducerBatch 首次发送失败后将直接进入失败队列。| 122 | | maxReservedAttempts | 11 | 该参数越大能让您追溯更多的信息,但同时也会消耗更多的内存。 | 123 | | baseRetryBackoffMs | 100 | 首次重试的退避时间,默认为 100 毫秒。Producer 采样指数退避算法,第 N 次重试的计划等待时间为 baseRetryBackoffMs * 2^(N-1)。| 124 | | maxRetryBackoffMs | 100 | 重试的最大退避时间,默认为 50 秒。 | 125 | | topic | | 指定日志主题,默认为 "" | 126 | | source | | 指定日志来源,默认为应用程序所在宿主机的 IP | 127 | | timeFormat | yyyy-MM-dd'T'HH:mm:ssZ | 输出到日志服务的时间的格式,默认是 yyyy-MM-dd'T'HH:mm:ssZ,可选参数 | 128 | | timeZone | UTC | 输出到日志服务的时间的时区,默认是 UTC,可选参数(如果希望 time 字段的时区为东八区,可将该值设定为 Asia/Shanghai)| 129 | | ignoreExceptions | true | 是否要忽略异常信息,默认为 true | 130 | | timePrecision | secs | 指定日志的时间精度, 这个选项只会影响日志的日志时间,不会影响 time 字段字符串格式。如果指定为`ns`选项,会调用 `SystemNanoClock` 来获取纳秒时间, 可能会增加一定的性能消耗。
允许的选项为:
- secs: 秒
- ms: 毫秒
- ns: 纳秒 | 131 | 132 | 133 | 参阅:https://github.com/aliyun/aliyun-log-producer-java 134 | 135 | ## 使用示例 136 | 137 | 可参考文件 [log4j2-example.xml](/src/main/resources/log4j2-example.xml) 在 resource 目录下创建你的日志配置文件 `log4j2.xml`, 该示例文件配置了两个appender: 138 | 139 | + loghubAppender1:将日志输出到阿里云日志服务,输出 INFO 级别的日志。 140 | + STDOUT:将日志输出到控制台。由于没有对日志级别进行过滤,会输出root中配置的日志级及以上的所有日志。 141 | 142 | [Log4j2AppenderExample.java](/src/main/java/com/aliyun/openservices/log/log4j2/example/Log4j2AppenderExample.java) 提供了打印日志的示例程序。 143 | 144 | 145 | 146 | ## 错误诊断 147 | 148 | 如果您发现数据没有写入日志服务,可通过如下步骤进行错误诊断。 149 | 1. 检查配置文件 log4j2.xml 是否限定了 appender 只输出特定级别的日志。比如,是否设置了 root,logger 或 appender 的 level 属性,是否在 appender 中设使用了 [filter](https://logging.apache.org/log4j/2.0/manual/filters.html)。 150 | 2. 检查您项目中引入的 protobuf-java,aliyun-log-log4j2-appender 这两个 jar 包的版本是否和文档中`maven 工程中引入依赖`部分列出的 jar 包版本一致。 151 | 3. 通过观察控制台的输出来诊断您的问题。Aliyun Log Log4j2 Appender 会将 appender 运行过程中产生的异常通过 `org.apache.logging.log4j.status.StatusLogger` 记录下来,默认情况下 log4j2 框架会为 StatusLogger 注册一个 StatusConsoleListener,因此 Aliyun Log Log4j2 Appender 自己运行过程中产生的异常会在默认情况下会输出到控制台。 152 | 4. 检查是否配置了 Configuration 节点的 `package` 参数为 `com.aliyun.openservices.log.log4j2`。 153 | ```xml 154 | 155 | 156 | 157 | ``` 158 | ## 常见问题 159 | 160 | **Q**:是否支持自定义 log 格式? 161 | 162 | **A**:在 0.1.9 及以上版本新增了 log 字段。您可以通过配置 layout 来自定义 log 格式,例如: 163 | ``` 164 | 165 | ``` 166 | log 输出样例: 167 | ``` 168 | log: 2018-07-15 21:12:29,682 INFO [main] TestAppender: info message. 169 | ``` 170 | 171 | **Q**: 如何采集宿主机 IP? 172 | 173 | **A**: 不要在 log4j2.xml 中设置 source 字段的值,这种情况下 source 字段会被设置成应用程序所在宿主机的 IP。 174 | 175 | **Q**:用户可以自定义 source 字段的取值吗? 176 | 177 | **A**:0.1.6 以及之前的版本不支持,在这些版本中 source 字段会被设置成应用程序所在宿主机的 IP。在最新的版本中,您可以参考上面的配置文件指定 source 的取值。 178 | 179 | **Q**:在网络发生异常的情况下,`aliyun-log-log4j2-appender` 会如何处理待发送的日志? 180 | 181 | **A**:`aliyun-log-log4j2-appender` 底层使用 `aliyun-log-producer-java` 发送数据。producer 会根据您在配置文件中设置的 `retryTimes` 进行重试,如果超过 `retryTimes` 次数据仍没有发送成功,会将错误信息输出,并丢弃该条日志。关于如何查看错误输出,可以参考错误诊断部分。 182 | 183 | **Q**:如何关闭某些类输出的日志? 184 | 185 | **A**:通过在 log4j2.xml 文件中添加 `` 可屏蔽相应包下日志的输出。 186 | 例如,当您在 log4j2.xml 文件中添加如下内容会屏蔽 package 名为 `com.aliyun.openservices.log.producer.inner` 下所有类的日志输出。 187 | ``` 188 | 189 | 190 | 191 | 192 | 193 | 194 | ``` 195 | 196 | **Q**:如果想设置 `time` 字段的时区为东八区或其他时区,该如何指定 `timeZone` 的取值? 197 | 198 | **A**:当您将 `timeZone` 指定为 `Asia/Shanghai` 时,`time` 字段的时区将为东八区。timeZone 字段可能的取值请参考 [java-util-timezone](http://tutorials.jenkov.com/java-date-time/java-util-timezone.html)。 199 | 200 | **Q**:如果想在Spring Boot 2 中引入 aliyun-log-log4j2-appender? 201 | 202 | **A**:使用以下依赖: 203 | ``` 204 | 205 | org.springframework.boot 206 | spring-boot-starter 207 | 208 | 209 | org.springframework.boot 210 | spring-boot-starter-logging 211 | 212 | 213 | org.springframework 214 | spring-jcl 215 | 216 | 217 | 218 | 219 | org.springframework.boot 220 | spring-boot-starter-log4j2 221 | 222 | 223 | com.google.protobuf 224 | protobuf-java 225 | 2.5.0 226 | 227 | 228 | com.aliyun.openservices 229 | aliyun-log-log4j2-appender 230 | 0.1.12 231 | 232 | ``` 233 | 234 | **Q**:为什么程序在运行时会抛出`java.lang.InterruptedException`? 235 | 236 | **A**:aliyun-log-log4j2-appender 会调用 [Producer.send()](https://github.com/aliyun/aliyun-log-java-producer/blob/master/src/main/java/com/aliyun/openservices/aliyun/log/producer/Producer.java#L16) 方法发送数据。执行 send() 方法的线程如果被中断了,如调用了 Thread.interrupted() 方法,就会抛出这样的异常。 237 | 调用 [Producer.send()](https://github.com/aliyun/aliyun-log-java-producer/blob/master/src/main/java/com/aliyun/openservices/aliyun/log/producer/Producer.java#L16) 方法所属的线程和您调用 LOGGER.info() 打印日志的线程是相同的线程,请检查您的程序在何时会调用 Thread.interrupted() 方法。 238 | 239 | ## 贡献者 240 | [@LNAmp](https://github.com/LNAmp) [@zzboy](https://github.com/zzboy) 对项目作了很大贡献。 241 | 242 | 感谢 [@LNAmp](https://github.com/LNAmp) [@zzboy](https://github.com/zzboy) 的杰出工作。 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/main/java/com/aliyun/openservices/log/log4j2/LoghubAppender.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.log4j2; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | import com.aliyun.openservices.aliyun.log.producer.LogProducer; 10 | import com.aliyun.openservices.aliyun.log.producer.Producer; 11 | import com.aliyun.openservices.aliyun.log.producer.ProducerConfig; 12 | import com.aliyun.openservices.aliyun.log.producer.ProjectConfig; 13 | import com.aliyun.openservices.log.common.LogItem; 14 | import org.apache.logging.log4j.core.Filter; 15 | import org.apache.logging.log4j.core.Layout; 16 | import org.apache.logging.log4j.core.LogEvent; 17 | import org.apache.logging.log4j.core.appender.AbstractAppender; 18 | import org.apache.logging.log4j.core.config.Configuration; 19 | import org.apache.logging.log4j.core.config.plugins.Plugin; 20 | import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 21 | import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 22 | import org.apache.logging.log4j.core.config.plugins.PluginElement; 23 | import org.apache.logging.log4j.core.config.plugins.PluginFactory; 24 | import org.apache.logging.log4j.core.impl.Log4jLogEvent; 25 | import org.apache.logging.log4j.core.util.Booleans; 26 | import org.apache.logging.log4j.core.util.DummyNanoClock; 27 | import org.apache.logging.log4j.core.util.NanoClock; 28 | import org.apache.logging.log4j.core.util.SystemNanoClock; 29 | import org.apache.logging.log4j.core.util.Throwables; 30 | import org.joda.time.DateTime; 31 | import org.joda.time.DateTimeZone; 32 | import org.joda.time.format.DateTimeFormat; 33 | import org.joda.time.format.DateTimeFormatter; 34 | 35 | @Plugin(name = "Loghub", category = "Core", elementType = "appender", printObject = true) 36 | public class LoghubAppender extends AbstractAppender { 37 | 38 | private static final String DEFAULT_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; 39 | 40 | private static final String DEFAULT_TIME_ZONE = "UTC"; 41 | 42 | protected String project; 43 | protected String logStore; 44 | protected String endpoint; 45 | protected String accessKeyId; 46 | protected String accessKeySecret; 47 | protected String stsToken; 48 | 49 | protected int totalSizeInBytes; 50 | protected int maxBlockMs; 51 | protected int ioThreadCount; 52 | protected int batchSizeThresholdInBytes; 53 | protected int batchCountThreshold; 54 | protected int lingerMs; 55 | protected int retries; 56 | protected int baseRetryBackoffMs; 57 | protected int maxRetryBackoffMs; 58 | 59 | private String userAgent = "log4j2"; 60 | private Producer producer; 61 | private String topic; 62 | private String source; 63 | private ProducerConfig producerConfig = new ProducerConfig(); 64 | 65 | private DateTimeFormatter formatter; 66 | private String mdcFields; 67 | private TimePrecision timePrecision; 68 | 69 | protected LoghubAppender(String name, 70 | Filter filter, 71 | Layout layout, 72 | boolean ignoreExceptions, 73 | String project, 74 | String logStore, 75 | String endpoint, 76 | String accessKeyId, 77 | String accessKeySecret, 78 | String stsToken, 79 | int totalSizeInBytes, 80 | int maxBlockMs, 81 | int ioThreadCount, 82 | int batchSizeThresholdInBytes, 83 | int batchCountThreshold, 84 | int lingerMs, 85 | int retries, 86 | int baseRetryBackoffMs, 87 | int maxRetryBackoffMs, 88 | String topic, 89 | String source, 90 | DateTimeFormatter formatter, 91 | String mdcFields, 92 | TimePrecision timePrecision 93 | ) { 94 | super(name, filter, layout, ignoreExceptions); 95 | this.project = project; 96 | this.endpoint = endpoint; 97 | this.accessKeySecret = accessKeySecret; 98 | this.accessKeyId = accessKeyId; 99 | this.stsToken = stsToken; 100 | this.logStore = logStore; 101 | 102 | this.totalSizeInBytes = totalSizeInBytes; 103 | this.retries = retries; 104 | this.ioThreadCount = ioThreadCount; 105 | this.maxBlockMs = maxBlockMs; 106 | this.batchCountThreshold = batchCountThreshold; 107 | this.batchSizeThresholdInBytes = batchSizeThresholdInBytes; 108 | this.lingerMs = lingerMs; 109 | this.baseRetryBackoffMs = baseRetryBackoffMs; 110 | this.maxRetryBackoffMs = maxRetryBackoffMs; 111 | if (topic == null) { 112 | this.topic = ""; 113 | } else { 114 | this.topic = topic; 115 | } 116 | this.source = source; 117 | this.formatter = formatter; 118 | this.mdcFields = mdcFields; 119 | this.timePrecision = timePrecision; 120 | 121 | // set nano clock if timePrecision is NANO 122 | if (TimePrecision.NANO.equals(this.timePrecision)) { 123 | NanoClock current = Log4jLogEvent.getNanoClock(); 124 | if (current instanceof DummyNanoClock) { 125 | Log4jLogEvent.setNanoClock(new SystemNanoClock()); 126 | } 127 | } 128 | } 129 | 130 | @Override 131 | public void start() { 132 | super.start(); 133 | 134 | ProjectConfig projectConfig = buildProjectConfig(); 135 | 136 | producerConfig.setBatchCountThreshold(batchCountThreshold); 137 | producerConfig.setBatchSizeThresholdInBytes(batchSizeThresholdInBytes); 138 | producerConfig.setIoThreadCount(ioThreadCount); 139 | producerConfig.setRetries(retries); 140 | producerConfig.setBaseRetryBackoffMs(baseRetryBackoffMs); 141 | producerConfig.setLingerMs(lingerMs); 142 | producerConfig.setMaxBlockMs(maxBlockMs); 143 | producerConfig.setMaxRetryBackoffMs(maxRetryBackoffMs); 144 | 145 | producer = new LogProducer(producerConfig); 146 | producer.putProjectConfig(projectConfig); 147 | } 148 | 149 | private ProjectConfig buildProjectConfig() { 150 | return new ProjectConfig(project, endpoint, accessKeyId, accessKeySecret, null, userAgent); 151 | } 152 | 153 | @Override 154 | public void stop() { 155 | super.stop(); 156 | if (producer != null) { 157 | try { 158 | producer.close(); 159 | } catch (Exception e) { 160 | this.error("Failed to close LoghubAppender.", e); 161 | } 162 | } 163 | 164 | } 165 | 166 | @Override 167 | public void append(LogEvent event) { 168 | List logItems = new ArrayList(); 169 | LogItem item = new LogItem((int) (event.getTimeMillis() / 1000)); 170 | logItems.add(item); 171 | if (TimePrecision.NANO.equals(timePrecision)) { 172 | item.SetTimeNsPart((int) (event.getNanoTime() % 1000000000)); 173 | } else if (TimePrecision.MILLIS.equals(timePrecision)) { 174 | item.SetTimeNsPart((int) ((event.getTimeMillis() % 1000) * 1000000)); 175 | } 176 | DateTime dateTime = new DateTime(event.getTimeMillis()); 177 | item.PushBack("time", dateTime.toString(formatter)); 178 | item.PushBack("level", event.getLevel().toString()); 179 | item.PushBack("thread", event.getThreadName()); 180 | 181 | StackTraceElement source = event.getSource(); 182 | if (source == null && (!event.isIncludeLocation())) { 183 | event.setIncludeLocation(true); 184 | source = event.getSource(); 185 | event.setIncludeLocation(false); 186 | } 187 | 188 | item.PushBack("location", source == null ? "Unknown(Unknown Source)" : source.toString()); 189 | 190 | String message = event.getMessage().getFormattedMessage(); 191 | item.PushBack("message", message); 192 | 193 | String throwable = getThrowableStr(event.getThrown()); 194 | if (throwable != null) { 195 | item.PushBack("throwable", throwable); 196 | } 197 | 198 | if (getLayout() != null) { 199 | item.PushBack("log", new String(getLayout().toByteArray(event))); 200 | } 201 | 202 | Optional.ofNullable(mdcFields).ifPresent( 203 | f->event.getContextMap().entrySet().stream() 204 | .filter(v->Arrays.stream(f.split(",")).anyMatch(i->i.equals(v.getKey()))) 205 | .forEach(map-> item.PushBack(map.getKey(),map.getValue())) 206 | ); 207 | try { 208 | producer.send(this.project, this.logStore, this.topic, this.source, logItems, new LoghubAppenderCallback(LOGGER, 209 | this.project, this.logStore, this.topic, this.source, logItems)); 210 | } catch (Exception e) { 211 | this.error( 212 | "Failed to send log, project=" + project 213 | + ", logStore=" + logStore 214 | + ", topic=" + topic 215 | + ", source=" + source 216 | + ", logItem=" + logItems, e); 217 | } 218 | } 219 | 220 | private String getThrowableStr(Throwable throwable) { 221 | if (throwable == null) { 222 | return null; 223 | } 224 | StringBuilder sb = new StringBuilder(); 225 | boolean isFirst = true; 226 | for (String s : Throwables.toStringList(throwable)) { 227 | if (isFirst) { 228 | isFirst = false; 229 | } else { 230 | sb.append(System.getProperty("line.separator")); 231 | } 232 | sb.append(s); 233 | } 234 | return sb.toString(); 235 | } 236 | 237 | @PluginFactory 238 | public static LoghubAppender createAppender( 239 | @PluginAttribute("name") final String name, 240 | @PluginElement("Filter") final Filter filter, 241 | @PluginElement("Layout") Layout layout, 242 | @PluginConfiguration final Configuration config, 243 | @PluginAttribute("ignoreExceptions") final String ignore, 244 | @PluginAttribute("project") final String project, 245 | @PluginAttribute("logStore") final String logStore, 246 | @PluginAttribute("endpoint") final String endpoint, 247 | @PluginAttribute("accessKeyId") final String accessKeyId, 248 | @PluginAttribute("accessKeySecret") final String accessKeySecret, 249 | @PluginAttribute("stsToken") final String stsToken, 250 | 251 | @PluginAttribute("totalSizeInBytes") final String totalSizeInBytes, 252 | @PluginAttribute("maxBlockMs") final String maxBlockMs, 253 | @PluginAttribute("ioThreadCount") final String ioThreadCount, 254 | @PluginAttribute("batchSizeThresholdInBytes") final String batchSizeThresholdInBytes, 255 | @PluginAttribute("batchCountThreshold") final String batchCountThreshold, 256 | @PluginAttribute("lingerMs") final String lingerMs, 257 | @PluginAttribute("retries") final String retries, 258 | @PluginAttribute("baseRetryBackoffMs") final String baseRetryBackoffMs, 259 | @PluginAttribute("maxRetryBackoffMs") final String maxRetryBackoffMs, 260 | 261 | @PluginAttribute("topic") final String topic, 262 | @PluginAttribute("source") final String source, 263 | @PluginAttribute("timeFormat") final String timeFormat, 264 | @PluginAttribute("timeZone") final String timeZone, 265 | @PluginAttribute("mdcFields") final String mdcFields, 266 | @PluginAttribute("timePrecision") final String timePrecision) { 267 | 268 | Boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); 269 | 270 | int maxBlockMsInt = parseStrToInt(maxBlockMs, 0); 271 | 272 | int baseRetryBackoffMsInt = parseStrToInt(baseRetryBackoffMs, 100); 273 | 274 | int maxRetryBackoffMsInt = parseStrToInt(maxRetryBackoffMs, 100); 275 | 276 | int lingerMsInt = parseStrToInt(lingerMs, 3000); 277 | 278 | int batchCountThresholdInt = parseStrToInt(batchCountThreshold, 4096); 279 | 280 | int batchSizeThresholdInBytesInt = parseStrToInt(batchSizeThresholdInBytes, 5 * 1024 * 1024); 281 | 282 | int totalSizeInBytesInt = parseStrToInt(totalSizeInBytes, 104857600); 283 | 284 | int retriesInt = parseStrToInt(retries, 3); 285 | 286 | int ioThreadCountInt = parseStrToInt(ioThreadCount, 8); 287 | 288 | String pattern = isStrEmpty(timeFormat) ? DEFAULT_TIME_FORMAT : timeFormat; 289 | String timeZoneInfo = isStrEmpty(timeZone) ? DEFAULT_TIME_ZONE : timeZone; 290 | DateTimeFormatter formatter = DateTimeFormat.forPattern(pattern).withZone(DateTimeZone.forID(timeZoneInfo)); 291 | TimePrecision precision = parseTimePrecision(timePrecision); 292 | return new LoghubAppender(name, filter, layout, ignoreExceptions, project, logStore, endpoint, 293 | accessKeyId, accessKeySecret, stsToken, totalSizeInBytesInt, maxBlockMsInt, ioThreadCountInt, 294 | batchSizeThresholdInBytesInt, batchCountThresholdInt, lingerMsInt, retriesInt, 295 | baseRetryBackoffMsInt, maxRetryBackoffMsInt, topic, source, formatter, mdcFields, precision); 296 | } 297 | 298 | static boolean isStrEmpty(String str) { 299 | return str == null || str.length() == 0; 300 | } 301 | 302 | static int parseStrToInt(String str, final int defaultVal) { 303 | if (!isStrEmpty(str)) { 304 | try { 305 | return Integer.valueOf(str); 306 | } catch (NumberFormatException e) { 307 | return defaultVal; 308 | } 309 | } else { 310 | return defaultVal; 311 | } 312 | } 313 | 314 | static void checkCondition(Boolean condition, String errorMsg) { 315 | if (!condition) { 316 | throw new IllegalArgumentException(errorMsg); 317 | } 318 | } 319 | 320 | public String getProject() { 321 | return project; 322 | } 323 | 324 | public void setProject(String project) { 325 | this.project = project; 326 | } 327 | 328 | public String getEndpoint() { 329 | return endpoint; 330 | } 331 | 332 | public void setEndpoint(String endpoint) { 333 | this.endpoint = endpoint; 334 | } 335 | 336 | public String getAccessKeyId() { 337 | return accessKeyId; 338 | } 339 | 340 | public void setAccessKeyId(String accessKeyId) { 341 | this.accessKeyId = accessKeyId; 342 | } 343 | 344 | public String getAccessKeySecret() { 345 | return accessKeySecret; 346 | } 347 | 348 | public void setAccessKeySecret(String accessKeySecret) { 349 | this.accessKeySecret = accessKeySecret; 350 | } 351 | 352 | public String getUserAgent() { 353 | return userAgent; 354 | } 355 | 356 | public void setUserAgent(String userAgent) { 357 | this.userAgent = userAgent; 358 | } 359 | 360 | public String getLogStore() { 361 | return logStore; 362 | } 363 | 364 | public void setLogStore(String logStore) { 365 | this.logStore = logStore; 366 | } 367 | 368 | public int getTotalSizeInBytes() { 369 | return producerConfig.getTotalSizeInBytes(); 370 | } 371 | 372 | public void setTotalSizeInBytes(int totalSizeInBytes) { 373 | producerConfig.setTotalSizeInBytes(totalSizeInBytes); 374 | } 375 | 376 | public long getMaxBlockMs() { 377 | return producerConfig.getMaxBlockMs(); 378 | } 379 | 380 | public void setMaxBlockMs(long maxBlockMs) { 381 | producerConfig.setMaxBlockMs(maxBlockMs); 382 | } 383 | 384 | public int getIoThreadCount() { 385 | return producerConfig.getIoThreadCount(); 386 | } 387 | 388 | public void setIoThreadCount(int ioThreadCount) { 389 | producerConfig.setIoThreadCount(ioThreadCount); 390 | } 391 | 392 | public int getBatchSizeThresholdInBytes() { 393 | return producerConfig.getBatchSizeThresholdInBytes(); 394 | } 395 | 396 | public void setBatchSizeThresholdInBytes(int batchSizeThresholdInBytes) { 397 | producerConfig.setBatchSizeThresholdInBytes(batchSizeThresholdInBytes); 398 | } 399 | 400 | public int getBatchCountThreshold() { 401 | return producerConfig.getBatchCountThreshold(); 402 | } 403 | 404 | public void setBatchCountThreshold(int batchCountThreshold) { 405 | producerConfig.setBatchCountThreshold(batchCountThreshold); 406 | } 407 | 408 | public int getLingerMs() { 409 | return producerConfig.getLingerMs(); 410 | } 411 | 412 | public void setLingerMs(int lingerMs) { 413 | producerConfig.setLingerMs(lingerMs); 414 | } 415 | 416 | public int getRetries() { 417 | return producerConfig.getRetries(); 418 | } 419 | 420 | public void setRetries(int retries) { 421 | producerConfig.setRetries(retries); 422 | } 423 | 424 | public int getMaxReservedAttempts() { 425 | return producerConfig.getMaxReservedAttempts(); 426 | } 427 | 428 | public void setMaxReservedAttempts(int maxReservedAttempts) { 429 | producerConfig.setMaxReservedAttempts(maxReservedAttempts); 430 | } 431 | 432 | public long getBaseRetryBackoffMs() { 433 | return producerConfig.getBaseRetryBackoffMs(); 434 | } 435 | 436 | public void setBaseRetryBackoffMs(long baseRetryBackoffMs) { 437 | producerConfig.setBaseRetryBackoffMs(baseRetryBackoffMs); 438 | } 439 | 440 | public long getMaxRetryBackoffMs() { 441 | return producerConfig.getMaxRetryBackoffMs(); 442 | } 443 | 444 | public void setMaxRetryBackoffMs(long maxRetryBackoffMs) { 445 | producerConfig.setMaxRetryBackoffMs(maxRetryBackoffMs); 446 | } 447 | 448 | public String getTopic() { 449 | return topic; 450 | } 451 | 452 | public void setTopic(String topic) { 453 | this.topic = topic; 454 | } 455 | 456 | public String getSource() { 457 | return source; 458 | } 459 | 460 | public void setSource(String source) { 461 | this.source = source; 462 | } 463 | 464 | public void setMdcFields(String mdcFields) { 465 | this.mdcFields = mdcFields; 466 | } 467 | 468 | private enum TimePrecision { 469 | SECONDS, 470 | MILLIS, 471 | NANO, 472 | } 473 | 474 | private static TimePrecision parseTimePrecision(String resolution) { 475 | if ("ns".equals(resolution)) { 476 | return TimePrecision.NANO; 477 | } 478 | if ("ms".equals(resolution)) { 479 | return TimePrecision.MILLIS; 480 | } 481 | return TimePrecision.SECONDS; 482 | } 483 | } 484 | --------------------------------------------------------------------------------