├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── README_CN.md ├── RELEASE_CN.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── aliyun │ │ └── openservices │ │ └── log │ │ └── logback │ │ ├── CredentialsProviderBuilder.java │ │ ├── LoghubAppender.java │ │ ├── LoghubAppenderCallback.java │ │ └── example │ │ ├── ExampleCredentialsProviderBuilder.java │ │ └── LogbackAppenderExample.java └── resources │ └── logback-example.xml └── test ├── java └── com │ └── aliyun │ └── openservices │ └── log │ └── logback │ └── TestAppender.java └── resources └── logback-test.xml /.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 | *.iml 25 | 26 | .idea/* 27 | 28 | target/ 29 | 30 | # Local test files 31 | logback-test-wubo.xml 32 | logback-wubo.xml 33 | LogbackAppenderPackageIDCase*.java 34 | 35 | # release files 36 | release.properties 37 | pom.xml.releaseBackup 38 | -------------------------------------------------------------------------------- /.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: qBLmAQgvXKy6xDn+Hvq3I0jQVCF4TksdSx04komeajsToG85AG2p5csMoyLb9/5akxlsxWqgPN7HUBS4OFkI4hmoLfGLZEMnL3RyuIRs1es08NElAwJDYF/bxwvx20+GbanXz0rVk58Ud/EMYbNDp4ygL50R8IPOKp//nzGS135K5QzoGOwBfyY+3FJ2RNPwF8QuR4B6cXVDvzp2SaeZBEnezvP+zJVxL6bOkhwqk36V90RxaMw0NnfzojunJW70motVGkKchGZPSXXkRn6Or2vcaG7XUFFagTK9NVq6CUBGfaYzOP/l2iMmD5prF/u/AqZeG4s5vGz1ypO+xx+4lYLsgtBw5tIwuzTkLhxSjoiMx5hBZsmDzcFXeM/B9xGMlYDMX1P0OiZZc18H/BFz6niBlwNPNt7QVpqhS6fzQrHu0iMAAJ9unQjazaeX/VlawzaxvyINUSLw+WHJxBSmeBTskvbY+y7w9uqP/OyGs9RjtHB2IZRzPdi7b+Ryqt+qA9ebwvdutnEFXUrsjOQsWvNNWWJjawBoyCaQ6gDLQpAGKySQx/d16GWq81LvUea4H2j/lrwPSZoJHQjYVgDa5v8VR0XPQhFNADf2Y6bkay40rEOQBaC+jsz4uDnAMHH98poaArriWmRbqelef6fDQ5UaP5g5+NJyWaK4prO3ivM= 9 | - secure: ei/FrPm7FNUQOkAxHS6UCV5mX8Gi5dp5yyFkFse6G4W7IRz9X4Ym92Yd1qcfOQ1lneUl5h+MvjBUkJceumersU1ULp4vClIzW8wNvDRdrAlv5R2UYRE7b8hrLIEGDtRU7d0lG4lgp9QDOzsMCrblKnZgrgCfQbm9+4w4zpV026S0TCs5TRFbJwmJdiPZGGgLcb1URQIlVRkDBdBDozFYv2sUCm70CEe9hZUHH0hAJWT/AWUN6aBqIx2TJN3h4POL8h8rY3BrHD7MFj1BqtWUUlwxdDrOfqEF4nIDIm8Y8U8xYPXwZQim2eIdHA11ovUuGCbUd+lVFBgUmTs5cfmJqk1ZnJxwQdbjgRPtKZjeTapE3c3yJIkWvymZEa/whJuV0gcoC9e5qDFgLzeS4oaB+omUFTnLR5ncTG6Vb35XUIEN9MG+tFng3Um6cIWLz/Gh7vOisI3G+Jsk/d18Nit9m9hUCi51zDEf/CFVFTqTRnOSMO5xd2MciTQLoeDiFmVE5zkREAtBwnfexSUOOK5FQkQn1JZq35/3L+Qh82cvCVHkoEfnO8HFtDc7XEHEwpU5s4iDRhda/ca51Pe8mRJ1RcHaS/1ZE9doeex32/O4dBDS5ZDER5RfRwZ1kTLBbAXOLIgEc2fECosSqzbBb7BwQjNDPCWUUN5cPC0lii/lMAY= 10 | - secure: eSK0Fl60fmA58vq0Ov5olZdmrZGM01cHwLr7C2gw84YMbeTdhVIxhzNBOBFzWhpB8FeKT7O79B/OU+VE9fNqwsMXNXB9ScZV29rkUiMrddsuTq+cqARv2cLFmiTF+Md+/dCryu40R4bvXOR99uEv8oqiaQTz2UxBTA7XnTmErb1OzMXZiMEpto2j4nyXy3SafltCogEmoorOJ7ApLKk7jGmj16jtnhi8fuO4T3Gs56OfvIYSwcSbwGk5rrF5gyoT91M8F1sF01W1ZCFkN/MOBbM48QTVGY008X0E801oXYmqalHsmhTZvHopB+X9TVof3s5kQ5F5zhWx2s4xGVW2mLp3QisArkqt2TnRYQPLB7eXQDsPZAeYREzserjbOsE+PDqTVDtuas8g2HkyJBCgMmZtNE4x/Tzi1BzAr14nooWCJfnelsfeOUQu3fRtzLlAveo3OwuMmW2tG3IxAYo+9jmSelejpml9tABn7V0eWXGHzo1Su2xeLgj6MQEEVpzjgNDtXm9v/KqgBubxOfe3C6naUiFDF/rcGAFlItqn7jDzDZgeOadasB7SMPJQnz3MbzPqQoml7Y3z02ovmad5PIVKvd3xziz86biIIiuuJTg6I6HcMsCL8e61htgOWNPlkFhF7hNAQsd4fBlJfxp27PfULHmFflkFzjNVbpvm/XM= 11 | - secure: TOnb/FLx67h2tBUwSh7AOyD/WKF93fAWOLOjojAGhs7nM69n8Xvnm2tbxuVZka/8Y1AQr+vBRqvARZlNnx7wWKxK1aTeKkxdTAr8lXs1JeEwlmrHT3bFiVsdl2flZnrTCT602R6K06DBRqEw/vVQaEQTIPESV9+qUik6FfHQ6g4nyXx/kgPKDDFbUssc6eAbzzKGcPI+jNIijlI6zdIstc8eyvQtnGeXtn2zCMjLjG5Rb3iZNx8vLENEkNeIJ8TfIFrHQpXn2unp1M2EXG3LI3Ww2W6pQ17C09eMZNQWrewtdQjPpujPKKk4WKM38hkpPAcKg3Rtjoeo+CWHksUTBTXqnahNSED0isADrMvwabfhP13HcySNw16HSeDfZ60E1K1HJ9uAGKzNUutsri3P1bwPK/j+Q3lGsz0wlfIuJHCExFavkDQUGvHxXBLq2ak9hgKC4vMyJn7QpiWNqljFSbZlAtAnAsZ440AS4iBFZfj2cZQlHUVXjV0HFk4LbLF+MebqfnT6fckF6HCYqsQvOv13he54jo8EFwtVqnCPPIZFiHQ6pPdnXqJnStC84qC1CN0Tt4NZYCsJvHbUEBi0ig9a8r8bqD0VmAW/M1A2uhpRJ62/XyHdKxcNLOBEDxJpRsHYJExj0pvbWxjQVMHYoINcRk8BSKEHmdavt03qLp8= 12 | - secure: kJa6tBhehWS3m1VqACgdVBQhr/aBwnMRGt7AY1P/Zl3Ro6z5BS9f6m35ssn5CeDMRyrjxSAWQMSR0EgWH6/zNgtOM5xs5xgJGJHwH+N1jF3RJ4rxaxcBYabNAX92ChBZOxeWLn9QekwjydaK3QkWVfD7O7zQno7JaWjC0eRCWakCGdn+HSL6PvWZ9n5e4BQhc131+vBj2wSYQMjxFdmkz5YIqpmuEs04jEh3mgmI54XjuvnDE5M7eI6sBEvSKLITOHQl0CisthJKEjYXGzpuKA9j33eGia4Wq8vZ5FvjGHQEZ/sGi1pl3AUYhl+dlEH9BDlcpNg7p5fSdxEMpYCUdSkB4OIIhjPg1DYqMj2da+WCSf5PP8O3oG9X3Fmba5Nfr9tRxqlfOm4RS4dcXN3sAGKSVyeVFolk24Z8YBT3zA4vGBSxC1O+725zjKrsB8x1MtEidyPUqoq6cqMACsngSDm2eJa97wlLfVMu6GvhGbTBeXamhFdocSnnPkqOTRRoELDVMWIkCv887GE5u/J8tOXKEBW5G+uoMj8TDceeyE+pX68naUIAmkNEJkooGigdz7XksMmKm3NTZ3j6Gvi2+yjc3+PjWJvYV+VigwOZxZScMdiDO8uHVYwwJ1HyxYOGMGfHXjjpZmyeJGmHpoHjK5mc/KLJhsrOI/0jjn6LsPk= 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: deploy 2 | deploy: 3 | mvn clean deploy -Dmaven.test.skip=true 4 | 5 | .PHONY: release 6 | release: deploy 7 | mvn -Darguments="-DskipTests" release:clean release:prepare release:perform 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logback Appender 2 | 3 | [![Build Status](https://travis-ci.org/aliyun/aliyun-log-logback-appender.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-log-logback-appender) 4 | [![License](https://img.shields.io/badge/license-Apache2.0-blue.svg)](/LICENSE) 5 | 6 | [中文版README](/README_CN.md) 7 | 8 | ## Aliyun Log Logback Appender 9 | 10 | Logback is intended as a successor to the popular log4j project. You can control the destination of the log through logback. 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 Logback 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, can be disabled by configuration 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 encoder) 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 logback, you can just add logback 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 | * logback 1.2.3 44 | * aliyun-log-producer 0.3.8 45 | * protobuf-java 2.5.0 46 | 47 | 48 | ## Configuration Steps 49 | 50 | ### 1. Adding the Dependencies in pom.xml 51 | 52 | ``` 53 | 54 | com.google.protobuf 55 | protobuf-java 56 | 2.5.0 57 | 58 | 59 | com.aliyun.openservices 60 | aliyun-log-logback-appender 61 | 0.1.28 62 | 63 | ``` 64 | 65 | ### 2. Modify the Configuration File 66 | 67 | Take `logback.xml` as an example, you can configure the appender and logger related to AliCloud Log Services as follows: 68 | ``` 69 | 70 | 71 | 72 | 73 | 74 | 75 | your project endpoint 76 | your accesskey id 77 | your accesskey 78 | 79 | 80 | your project 81 | your logStore 82 | 83 | 84 | 85 | your topic 86 | your source 87 | 88 | 89 | 104857600 90 | 0 91 | 8 92 | 524288 93 | 4096 94 | 2000 95 | 10 96 | 100 97 | 50000 98 | 99 | 100 | 101 | %d %-5level [%thread] %logger{0}: %msg 102 | 103 | 104 | 105 | yyyy-MM-dd'T'HH:mmZ 106 | 107 | UTC 108 | 109 | true 110 | 111 | true 112 | 113 | 114 | 115 | ${accessKeyId} 116 | ${accessKeySecret} 117 | 118 | 119 | 500 120 | 121 | 122 | 125 | ``` 126 | **Note**: 127 | + To prevent data loss when the process exits, please remember to add label `DelayingShutdownHook`. 128 | + The LoghubAppender will catch the exceptions in the process of running and put them into `BasicStatusManager`, you can obtain the exception information through `OnConsoleStatusListener` or other means. Reference: https://logback.qos.ch/access.html 129 | 130 | ## Parameter Description 131 | 132 | The `Aliyun Log Logback Appender` provides following parameters. 133 | ``` 134 | # Specify the project name of your log services, required 135 | project = [your project] 136 | # Specify the logstore of your log services, required 137 | logStore = [your logStore] 138 | # Specify the HTTP endpoint of your log services, required 139 | endpoint = [your project endpoint] 140 | # Specify the account information of your log services, required 141 | accessKeyId = [your accesskey id] 142 | accessKeySecret = [your accessKeySecret] 143 | 144 | # The upper limit log size that a single producer instance can hold, default is 100MB. 145 | totalSizeInBytes=104857600 146 | # If the producer has insufficient free space, the caller's maximum blocking time on the send method, defaults is 60 seconds. In order not to block the log printing thread, it is strongly recommended to set this value to 0. 147 | maxBlockMs=0 148 | # The thread pool size for executing log sending tasks, defaults is the number of processors available. 149 | ioThreadCount=8 150 | # When the size of the cached log in a Producer Batch is greater than or equal batchSizeThresholdInBytes, the batch will be send, default is 512KB, maximum can be set to 5MB. 151 | batchSizeThresholdInBytes=524288 152 | # When the number of log entries cached in a ProducerBatch is greater than or equal to batchCountThreshold, the batch will be send. 153 | batchCountThreshold=4096 154 | # A ProducerBatch has a residence time from creation to sending, defaulting is 2 seconds and a minimum of 100 milliseconds. 155 | lingerMs=2000 156 | # The number of times a Producer Batch can be retried if it fails to send for the first time, default is 10. 157 | retries=10 158 | # The backoff time for the first retry, default 100 milliseconds. 159 | baseRetryBackoffMs=100 160 | # The maximum backoff time for retries, default is 50 seconds. 161 | maxRetryBackoffMs=50000 162 | 163 | # Specify the topic of your log, default is "", optional 164 | topic = [your topic] 165 | 166 | # Specify the source of your log, default is host ip, optional 167 | source = [your source] 168 | 169 | # Specify time format of the field time, default is yyyy-MM-dd'T'HH:mmZ, optional 170 | timeFormat = yyyy-MM-dd'T'HH:mmZ 171 | 172 | # Specify timezone of the field time, default is UTC, optional 173 | timeZone = UTC 174 | # Whether to record the Location field, defaults to true, set it false if performance-sensitive 175 | includeLocation = true 176 | # Whether to include field message when encoder exists, defaults to true. 177 | includeMessage = true 178 | # option for maximum exception stack trace recording length. Exceeding this length will result in truncation. Defaults to 500, optional. 179 | maxThrowable=500 180 | 181 | # Specify ingest processor, default is "", optional 182 | processor = [ingest processor] 183 | ``` 184 | 185 | ## Custom CredentialsProvider 186 | The logback-appender supports custom `CredentialsProvider`. By implementing the `CredentialsProvider` interface, you can implement advanced features yourself, such as dynamic rotation of AccessKey. 187 | 188 | > Once a `CredentialsProvider` is provided, it's unnecessary to configure parameter for static credentials (AccessKeyId/AccessKeySecret/SecurityToken); the system will use the credentials fetched through the `CredentialsProvider` provided. 189 | 190 | 1. Defines a class, eg. `MyCredentialsProvider`, and implements the `CredentialsProvider` interface to support the dynamic updates of credentials. Make sure the implementation is thread-safe. 191 | - For performance, it is recommended that method `getCredentials` caches credentials and refreshes them before expired. 192 | 193 | ```java 194 | class MyCredentialsProvider implements CredentialsProvider { 195 | @Override 196 | public synchronized Credentials getCredentials() { 197 | // fetch credentials and caches credentials 198 | } 199 | // constructor 200 | MyCredentialsProvider(String param1, long paramField2) {} 201 | } 202 | ``` 203 | 204 | 2. Defines a class, eg. `MyBuilder`, and implements the `CredentialsProviderBuilder` interface. 205 | - The method `getCredentialsProvider` should return a new instance of `CredentialsProvider` for each call. 206 | ```java 207 | class MyBuilder implements CredentialsProviderBuilder { 208 | @Override 209 | public CredentialsProvider getCredentialsProvider() { 210 | return new MyCredentialsProvider(param1, paramField2); 211 | } 212 | private String param1; 213 | private long paramField2; 214 | public void setParam1(String param1) { 215 | this.param1 = param1; 216 | } 217 | public void setParamField2(long paramField2) { 218 | this.paramField2 = paramField2; 219 | } 220 | } 221 | ``` 222 | 223 | 3. Configure logback with xml file, set the class of `credentialsProviderBuilder` with the fully qualified name of your class, eg. `com.example.MyBuilder`, and pass any custom parameters. 224 | ```xml 225 | 226 | 227 | hello 228 | 123 229 | 230 | 231 | 232 | ``` 233 | ### Custom parameter 234 | To pass in some custom parameters to the `MyBuilder` class, such as param1, paramField2, define the setter methods setParam1 and setParamField2 in the class first. 235 | ```java 236 | class MyBuilder implements CredentialsProviderBuilder { 237 | private String param1; 238 | private long paramField2; 239 | public void setParam1(String param1) { 240 | this.param1 = param1; 241 | } 242 | public void setParamField2(long paramField2) { 243 | this.paramField2 = paramField2; 244 | } 245 | // Omit other codes 246 | } 247 | ``` 248 | Then configure logback with xml file, set the class of `credentialsProviderBuilder` with the fully qualified name of your class, eg. `com.example.MyBuilder`, and fill with custom parameters. 249 | ```xml 250 | 251 | 252 | hello 253 | 123 254 | 255 | 256 | 257 | ``` 258 | 259 | ## Sample Code 260 | 261 | [LogbackAppenderExample.java](/src/main/java/com/aliyun/openservices/log/logback/example/LogbackAppenderExample.java) 262 | 263 | [logback-example.xml](/src/main/resources/logback-example.xml) 264 | 265 | ## Contributors 266 | [@lionbule](https://github.com/lionbule) [@zzboy](https://github.com/zzboy) made a great contribution to this project. 267 | 268 | Thanks for the excellent work by [@lionbule](https://github.com/lionbule) [@zzboy](https://github.com/zzboy). 269 | 270 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Logback Appender 2 | 3 | [![Build Status](https://travis-ci.org/aliyun/aliyun-log-logback-appender.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-log-logback-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 Logback Appender 9 | Logback是由log4j创始人设计的又一个开源日志组件。通过使用Logback,您可以控制日志信息输送的目的地是控制台、文件、GUI 组件、甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;您也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,您能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。 10 | 11 | 通过Aliyun Log Logback Appender,您可以控制日志的输出目的地为阿里云日志服务,写到日志服务中的日志的样式如下: 12 | ``` 13 | level: ERROR 14 | location: com.aliyun.openservices.log.logback.example.LogbackAppenderExample.main(LogbackAppenderExample.java:18) 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.logback.example.LogbackAppenderExample: 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 | + 无需改造:对已使用logback应用,只需简单配置即可采集。 38 | + 异步高吞吐:高并发设计,后台异步发送,适合高并发写入。 39 | + 上下文查询:服务端除了通过关键词检索外,给定日志能够精确还原原始日志文件上下文日志信息。 40 | 41 | 42 | ## 版本支持 43 | * logback 1.2.3 44 | * aliyun-log-producer 0.3.8 45 | * protobuf-java 2.5.0 46 | 47 | 48 | ## 配置步骤 49 | 50 | ### 1. maven 工程中引入依赖 51 | 52 | ``` 53 | 54 | com.google.protobuf 55 | protobuf-java 56 | 2.5.0 57 | 58 | 59 | com.aliyun.openservices 60 | aliyun-log-logback-appender 61 | 0.1.28 62 | 63 | ``` 64 | 65 | ### 2. 修改配置文件 66 | 67 | 以xml型配置文件`logback.xml`为例(不存在则在项目根目录创建),配置Loghub相关的appender与 Logger,例如: 68 | ``` 69 | 70 | 71 | 72 | 73 | 74 | 75 | your project endpoint 76 | your accesskey id 77 | your accesskey 78 | 79 | 80 | your project 81 | your logStore 82 | 83 | 84 | 85 | your topic 86 | your source 87 | 88 | 89 | 104857600 90 | 0 91 | 8 92 | 524288 93 | 4096 94 | 2000 95 | 10 96 | 100 97 | 50000 98 | 99 | 100 | 101 | %d %-5level [%thread] %logger{0}: %msg 102 | 103 | 104 | 105 | yyyy-MM-dd'T'HH:mmZ 106 | 107 | UTC 108 | 109 | true 110 | 111 | true 112 | 113 | 114 | 115 | ${accessKeyId} 116 | ${accessKeySecret} 117 | 118 | 119 | 500 120 | 121 | ``` 122 | **注意**: 123 | + 为了防止进程退出时,LoghubAppender缓存在内存中的少量数据丢失,请记得加上`DelayingShutdownHook`标签。 124 | + LoghubAppender在运行过程中产生的异常会被捕获并放入logback的`BasicStatusManager`类中,您可以通过配置`OnConsoleStatusListener`或其他方式查看出错信息。参阅:https://logback.qos.ch/access.html 125 | 126 | ## 参数说明 127 | 128 | Aliyun Log Logback Appender 可供配置的属性(参数)如下,其中注释为必选参数的是必须填写的,可选参数在不填写的情况下,使用默认值。 129 | 130 | ``` 131 | #日志服务的 project 名,必选参数 132 | project = [your project] 133 | #日志服务的 logstore 名,必选参数 134 | logStore = [your logStore] 135 | #日志服务的 HTTP 地址,必选参数 136 | endpoint = [your project endpoint] 137 | #用户身份标识,必选参数 138 | accessKeyId = [your accesskey id] 139 | accessKeySecret = [your accessKeySecret] 140 | 141 | #单个 producer 实例能缓存的日志大小上限,默认为 100MB。 142 | totalSizeInBytes=104857600 143 | #如果 producer 可用空间不足,调用者在 send 方法上的最大阻塞时间,默认为 60 秒。为了不阻塞打印日志的线程,强烈建议将该值设置成 0。 144 | maxBlockMs=0 145 | #执行日志发送任务的线程池大小,默认为可用处理器个数。 146 | ioThreadCount=8 147 | #当一个 ProducerBatch 中缓存的日志大小大于等于 batchSizeThresholdInBytes 时,该 batch 将被发送,默认为 512 KB,最大可设置成 5MB。 148 | batchSizeThresholdInBytes=524288 149 | #当一个 ProducerBatch 中缓存的日志条数大于等于 batchCountThreshold 时,该 batch 将被发送,默认为 4096,最大可设置成 40960。 150 | batchCountThreshold=4096 151 | #一个 ProducerBatch 从创建到可发送的逗留时间,默认为 2 秒,最小可设置成 100 毫秒。 152 | lingerMs=2000 153 | #如果某个 ProducerBatch 首次发送失败,能够对其重试的次数,默认为 10 次。 154 | #如果 retries 小于等于 0,该 ProducerBatch 首次发送失败后将直接进入失败队列。 155 | retries=10 156 | #该参数越大能让您追溯更多的信息,但同时也会消耗更多的内存。 157 | maxReservedAttempts=11 158 | #首次重试的退避时间,默认为 100 毫秒。 159 | #Producer 采样指数退避算法,第 N 次重试的计划等待时间为 baseRetryBackoffMs * 2^(N-1)。 160 | baseRetryBackoffMs=100 161 | #重试的最大退避时间,默认为 50 秒。 162 | maxRetryBackoffMs=50000 163 | 164 | #指定日志主题,默认为 "",可选参数 165 | topic = [your topic] 166 | 167 | #指的日志来源,默认为应用程序所在宿主机的 IP,可选参数 168 | source = [your source] 169 | 170 | #输出到日志服务的时间的格式,默认是 yyyy-MM-dd'T'HH:mmZ,可选参数 171 | timeFormat = yyyy-MM-dd'T'HH:mmZ 172 | 173 | #输出到日志服务的时间的时区,默认是 UTC,可选参数(如果希望 time 字段的时区为东八区,可将该值设定为 Asia/Shanghai) 174 | timeZone = UTC 175 | #是否要记录 Location 字段(日志打印位置),默认为 true,如果希望减少该选项对性能的影响,可以设为 false 176 | includeLocation = true 177 | #当 encoder 不为空时,是否要包含 message 字段,默认为 true 178 | includeMessage = true 179 | # 可选项,exception 堆栈最大记录长度,超出此长度会被截断,默认值为 500 --> 180 | maxThrowable=500 181 | 182 | # 写入处理器,默认为 "",可选参数 183 | processor = [ingest processor] 184 | ``` 185 | 参阅:https://github.com/aliyun/aliyun-log-producer-java 186 | 187 | ## 自定义凭证提供者 CredentialsProvider 188 | 189 | logback-appender 支持您自定义凭证提供者 `CredentialsProvider`。通过实现凭证获取接口,您能够灵活地实现访问密钥 (AK) 凭证的动态轮换等高级功能。 190 | 191 | > 一旦启用自定义 `CredentialsProvider` 后,就无需再额外配置静态凭证参数(AccessKeyId/AccessKeySecret/SecurityToken),系统将通过您提供的 `CredentialsProvider` 自动获取 AK 凭证。。 192 | 193 | 1. 创建一个类(例如 `MyCredentialsProvider`),并实现 `CredentialsProvider` 接口,来支持动态获取 AK 凭证,请确保该实现是线程安全的。 194 | 195 | > 为了优化性能,建议 `getCredentials` 方法在内部缓存 AK 凭证,并确保在缓存即将过期之前主动刷新。 196 | 197 | ```java 198 | class MyCredentialsProvider implements CredentialsProvider { 199 | @Override 200 | public synchronized Credentials getCredentials() { 201 | // 获取 AK 与缓存 AK 的逻辑 202 | } 203 | // 构造函数 204 | MyCredentialsProvider(String param1, long paramField2) {} 205 | } 206 | ``` 207 | 208 | 2. 创建一个类(比如叫 `MyBuilder`),并实现 `CredentialsProviderBuilder` 接口。 209 | - `getCredentialsProvider` 方法应该在每次调用时都返回一个新的 `CredentialsProvider` 实例。 210 | ```java 211 | class MyBuilder implements CredentialsProviderBuilder { 212 | @Override 213 | public CredentialsProvider getCredentialsProvider() { 214 | return new MyCredentialsProvider(param1, paramField2); 215 | } 216 | private String param1; 217 | private long paramField2; 218 | // 自定义参数 param1 219 | public void setParam1(String param1) { 220 | this.param1 = param1; 221 | } 222 | // 自定义参数 paramField2 223 | public void setParamField2(long paramField2) { 224 | this.paramField2 = paramField2; 225 | } 226 | } 227 | ``` 228 | 229 | 3. 在 logback 的配置文件中,设置 `credentialsProviderBuilder` 的 class 属性为您的自定义类的全限定名,例如 `com.example.MyBuilder`,并根据需要添加自定义参数。 230 | ```xml 231 | 232 | 233 | hello 234 | 123 235 | 236 | 237 | 238 | ``` 239 | 240 | ### 自定义参数 241 | 若需向 `MyBuilder` 类提供自定义参数,如 `param1` 或 `paramField2`,您应在该类中定义相应的 setter 方法,例如 `setParam1` 和 `setParamField2`。 242 | ```java 243 | class MyBuilder implements CredentialsProviderBuilder { 244 | private String param1; 245 | private long paramField2; 246 | public void setParam1(String param1) { 247 | this.param1 = param1; 248 | } 249 | public void setParamField2(long paramField2) { 250 | this.paramField2 = paramField2; 251 | } 252 | // 省略其他方法 253 | } 254 | ``` 255 | 256 | 然后在 logback 的配置文件中,`credentialsProviderBuilder` 选项下,为自定义参数配置参数值即可。 257 | ```xml 258 | 259 | 260 | hello 261 | 123 262 | 263 | 264 | 265 | ``` 266 | 267 | 268 | 269 | ## 使用实例 270 | 项目中提供了一个名为`com.aliyun.openservices.log.logback.LogbackAppenderExample`的实例,它会加载resources目录下的`logback.xml`文件进行logback配置。 271 | 272 | **logback.xml样例说明** 273 | + 配置了三个appender:loghubAppender1、loghubAppender2、STDOUT。 274 | + loghubAppender1:将日志输出到project=test-proj,logstore=store1。输出WARN、ERROR级别的日志。 275 | + loghubAppender2:将日志输出到project=test-proj,logstore=store2。只输出INFO级别的日志。 276 | + STDOUT:将日志输出到控制台。由于没有对日志级别进行过滤,会输出root中配置的日志级及以上的所有日志。 277 | 278 | [LogbackAppenderExample.java](/src/main/java/com/aliyun/openservices/log/logback/example/LogbackAppenderExample.java) 279 | 280 | [logback-example.xml](/src/main/resources/logback-example.xml) 281 | 282 | ## 错误诊断 283 | 284 | 如果您发现数据没有写入日志服务,可通过如下步骤进行错误诊断。 285 | 1. 检查配置文件 logback.xml 是否限定了 appender 只输出特定级别的日志。比如,是否设置了 root 或 logger 的 level 属性,是否在 appender 中设置了 [filter](https://logback.qos.ch/manual/filters.html)。 286 | 2. 检查您项目中引入的 protobuf-java,aliyun-log-logback-appender 这两个 jar 包的版本是否和文档中`maven 工程中引入依赖`部分列出的 jar 包版本一致。 287 | 3. 通过观察控制台的输出来诊断您的问题。Aliyun Log Logback Appender 会将 appender 运行过程中产生的异常写入 `ch.qos.logback.core.BasicStatusManager` 中。您可以通过配置 statusListener 来获取 BasicStatusManager 中的数据。例如,`` 会将 BasicStatusManager 中的数据输出到控制台。 288 | 4. 请检查您的 `logback.xml` 中是否包含选项 ``。数据会定期异步地发往服务端,加上此选项可以保证您的程序在正常退出时,内存中缓存的数据不丢失。 289 | 290 | ## 常见问题 291 | 292 | **Q**:使用Pandora Boot+Spring Boot 启动异常退出? 293 | 294 | **A**:将logback.xml 改成 logback-spring.xml 295 | 296 | **Q**:在debug模式下,大量打印十六进制信息,程序异常退出? 297 | 298 | **A**:在configuration标签中加入: 299 | ``` 300 | 301 | ``` 302 | 303 | **Q**:是否支持自定义 log 格式? 304 | 305 | **A**:在 0.1.12 及以上版本新增了 log 字段。您可以通过在 encoder 中设置 pattern 来自定义 log 格式,例如: 306 | ``` 307 | 308 | %d %-5level [%thread] %logger{0}: %msg 309 | 310 | ``` 311 | log 输出样例: 312 | ``` 313 | log: 2018-07-15 21:12:29,682 INFO [main] TestAppender: info message. 314 | ``` 315 | 316 | **Q**:日志中为何没有 time 字段? 317 | 318 | **A**:0.1.6 以及之前的版本的 LogItem 没有包含 time 字段,请升级至最新版本。 319 | 320 | **Q**:用户可以自定义 source 字段的取值吗? 321 | 322 | **A**:0.1.8 以及之前的版本不支持,在这些版本中 source 字段会被设置成应用程序所在宿主机的 IP。在最新的版本中,您可以参考上面的配置文件指定 source 的取值。 323 | 324 | **Q**: 如何采集宿主机 IP? 325 | 326 | **A**: 不要在 logback.xml 中设置 source 字段的值,这种情况下 source 字段会被设置成应用程序所在宿主机的 IP。 327 | 328 | **Q**:在网络发生异常的情况下,`aliyun-log-logback-appender` 会如何处理待发送的日志? 329 | 330 | **A**:`aliyun-log-logback-appender` 底层使用 `aliyun-log-producer-java` 发送数据。producer 会根据您在配置文件中设置的 `retryTimes` 进行重试,如果超过 `retryTimes` 次数据仍没有发送成功,会将错误信息输出,并丢弃该条日志。关于如何查看错误输出,可以参考错误诊断部分。 331 | 332 | **Q**:如何关闭某些类输出的日志? 333 | 334 | **A**:通过在 logback.xml 文件中添加 `` 可屏蔽相应包下日志的输出。 335 | 例如,当您在 logback.xml 文件中添加如下内容会屏蔽 package 名为 `com.aliyun.openservices.log.producer.inner` 下所有类的日志输出。 336 | ``` 337 | 338 | ``` 339 | 340 | **Q**:应用初始化时出现这样的信息 `A number (N) of logging calls during the initialization phase have been intercepted and are now being replayed. These are subject to the filtering rules of the underlying logging system.`? 341 | 342 | **A**:该信息只会在日志系统初始化阶段产生,并不影响后续日志记录的功能。 343 | 344 | 当应用首次调用`LoggerFactory.getLogger()`方法时,日志系统进入初始化流程。初始化流程还未结束,再次调用`LoggerFactory.getLogger()`方法便会出现上述信息。这时,slf4j 会创建替代记录器(substitute loggers)并返回。在完成初始化后,替代记录器(substitute loggers)会将日志记录请求委托给合适的 logger。 345 | 346 | `aliyun-log-logback-appender` 的依赖库 `aliyun-log-producer-java` 也会使用 slf4j 记录日志,所以会出现上述信息。 347 | 348 | 参阅:https://www.slf4j.org/codes.html#replay 349 | 350 | **Q**:如果想设置 `time` 字段的时区为东八区或其他时区,该如何指定 `timeZone` 的取值? 351 | 352 | **A**:当您将 `timeZone` 指定为 `Asia/Shanghai` 时,`time` 字段的时区将为东八区。timeZone 字段可能的取值请参考 [java-util-timezone](http://tutorials.jenkov.com/java-date-time/java-util-timezone.html)。 353 | 354 | **Q**:为什么程序在运行时会抛出`java.lang.InterruptedException`? 355 | 356 | **A**:aliyun-log-logback-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() 方法,就会抛出这样的异常。 357 | 调用 [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() 方法。 358 | 359 | ## 贡献者 360 | [@lionbule](https://github.com/lionbule) [@zzboy](https://github.com/zzboy) 对项目作了很大贡献。 361 | 362 | 感谢 [@lionbule](https://github.com/lionbule) [@zzboy](https://github.com/zzboy) 的杰出工作。 363 | -------------------------------------------------------------------------------- /RELEASE_CN.md: -------------------------------------------------------------------------------- 1 | # Aliyun LOG Java Producer 新版本发布流程 2 | 3 | ## 发布 4 | 1. 进入`aliyun-log-logback-appender`项目的根目录。 5 | 2. 运行命令`make release`。 6 | 3. 确认弹出的新版本信息后,等待命令执行完成(该命令执行完成后,会自动生成下一个版本的信息)。 7 | 4. 登陆 [stagingRepositories](https://oss.sonatype.org/#stagingRepositories),close 提交的 repository。 8 | 5. 将 close 的 repository release。 9 | 10 | ## 验证 11 | 进入 [nexus-search](https://oss.sonatype.org/index.html#nexus-search;quick~aliyun-log-logback-appender) 查看新版本是否成功发布。 12 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | org.sonatype.oss 6 | oss-parent 7 | 9 8 | 9 | 10 | com.aliyun.openservices 11 | aliyun-log-logback-appender 12 | 0.1.29 13 | jar 14 | 15 | aliyun log logback appender 16 | http://www.aliyun.com 17 | aliyun log logback appender 18 | 19 | 20 | 21 | 22 | repo 23 | Copyright (C) Alibaba Cloud Computing. All rights reserved. 24 | 25 | 26 | 27 | https://github.com/aliyun/aliyun-log-logback-appender 28 | scm:git:https://github.com/aliyun/aliyun-log-logback-appender.git 29 | scm:git:https://github.com/aliyun/aliyun-log-logback-appender.git 30 | 31 | 32 | 33 | 34 | aliyunproducts 35 | Aliyun Log development team 36 | aliyunsdk@aliyun.com 37 | 38 | 39 | 40 | UTF-8 41 | 1.6 42 | 1.6 43 | 44 | 45 | 46 | 47 | com.google.protobuf 48 | protobuf-java 49 | 2.5.0 50 | 51 | 52 | com.aliyun.openservices 53 | aliyun-log-producer 54 | 0.3.24 55 | 56 | 57 | ch.qos.logback 58 | logback-core 59 | 1.2.3 60 | 61 | 62 | ch.qos.logback 63 | logback-classic 64 | 1.2.3 65 | 66 | 67 | org.slf4j 68 | slf4j-api 69 | 1.7.15 70 | 71 | 72 | joda-time 73 | joda-time 74 | 2.9.9 75 | 76 | 77 | junit 78 | junit 79 | 4.12 80 | test 81 | 82 | 83 | 84 | 85 | sonatype-nexus-snapshots 86 | https://s01.oss.sonatype.org/content/repositories/snapshots 87 | 88 | 89 | sonatype-nexus-staging 90 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-gpg-plugin 98 | 1.6 99 | 100 | 101 | sign-artifacts 102 | verify 103 | 104 | sign 105 | 106 | 107 | 108 | 109 | 110 | org.sonatype.plugins 111 | nexus-staging-maven-plugin 112 | 1.6.3 113 | true 114 | 115 | sonatype-nexus-staging 116 | https://s01.oss.sonatype.org/ 117 | true 118 | 119 | 120 | 121 | maven-compiler-plugin 122 | 3.5.1 123 | 124 | ${maven.compiler.source} 125 | ${maven.compiler.target} 126 | ${project.build.sourceEncoding} 127 | 128 | 129 | 130 | org.apache.maven.plugins 131 | maven-source-plugin 132 | 3.0.0 133 | 134 | 135 | attach-sources 136 | 137 | jar 138 | 139 | 140 | 141 | 142 | 143 | org.apache.maven.plugins 144 | maven-javadoc-plugin 145 | 2.10.4 146 | 147 | ${project.build.sourceEncoding} 148 | ${project.build.sourceEncoding} 149 | 150 | 151 | author 152 | X 153 | 154 | 155 | 156 | 157 | 158 | attach-javadocs 159 | 160 | jar 161 | 162 | 163 | 164 | 165 | 166 | org.apache.maven.plugins 167 | maven-release-plugin 168 | 2.1 169 | 170 | forked-path 171 | false 172 | -Psonatype-oss-release -Dmaven.test.skip=true 173 | 174 | 175 | 176 | org.apache.maven.plugins 177 | maven-compiler-plugin 178 | 179 | 8 180 | 8 181 | 182 | 183 | 184 | org.apache.maven.plugins 185 | maven-shade-plugin 186 | 3.1.1 187 | 188 | 189 | package 190 | 191 | shade 192 | 193 | 194 | 195 | 196 | com.aliyun.openservices.log 197 | com.shade.aliyun.openservices.log 198 | 199 | com.aliyun.openservices.log.logback 200 | com.aliyun.openservices.log.logback.* 201 | 202 | 203 | 204 | com.google 205 | com.shade.google 206 | 207 | 208 | net.jpountz 209 | net.shade.jpountz 210 | 211 | 212 | com.alibaba 213 | com.shade.alibaba 214 | 215 | 216 | org.apache 217 | org.shade.apache 218 | 219 | 220 | org.codehaus 221 | org.shade.codehaus 222 | 223 | 224 | org.checkerframework 225 | org.shade.checkerframework 226 | 227 | 228 | jar-with-dependencies 229 | true 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /src/main/java/com/aliyun/openservices/log/logback/CredentialsProviderBuilder.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.logback; 2 | 3 | 4 | import com.aliyun.openservices.log.common.auth.CredentialsProvider; 5 | 6 | /** 7 | * Customized CredentialsProviderBuilder 8 | * Example: 9 | *
10 |  * {@code
11 |  *    class ExampleBuilder implements CredentialsProviderBuilder{
12 |  *      private String param1;
13 |  *      private long paramField2;
14 |  *
15 |  *      public CredentialsProvider getCredentialsProvider(){
16 |  *          return new YourCredentialsProvider(param1, paramField2);
17 |  *      }
18 |  *      public void setParam1(String param1) {
19 |  *          this.param1 = param1;
20 |  *      }
21 |  *      public void setParamField2(long paramField2) {
22 |  *          this.paramField2 = paramField2;
23 |  *      }
24 |  *    }
25 |  * }
26 |  * 
27 | */ 28 | public interface CredentialsProviderBuilder { 29 | /** 30 | * getCredentialsProvider should return a new instance of {@link CredentialsProvider} 31 | * for each call. 32 | * 33 | * @return the returned {@link CredentialsProvider} must be thread-safe, 34 | * and cache credentials to avoid update credentials too frequently. 35 | * 36 | * @throws Exception if fail to create new CredentialsProvider instance 37 | */ 38 | CredentialsProvider getCredentialsProvider() throws Exception; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/aliyun/openservices/log/logback/LoghubAppender.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.logback; 2 | 3 | import java.time.Instant; 4 | import java.time.ZoneId; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | import com.aliyun.openservices.log.common.auth.CredentialsProvider; 11 | import org.joda.time.DateTime; 12 | import org.joda.time.DateTimeZone; 13 | import org.joda.time.format.DateTimeFormat; 14 | import org.joda.time.format.DateTimeFormatter; 15 | 16 | import com.aliyun.openservices.aliyun.log.producer.LogProducer; 17 | import com.aliyun.openservices.aliyun.log.producer.Producer; 18 | import com.aliyun.openservices.aliyun.log.producer.ProducerConfig; 19 | import com.aliyun.openservices.aliyun.log.producer.ProjectConfig; 20 | import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException; 21 | import com.aliyun.openservices.log.common.LogItem; 22 | 23 | import ch.qos.logback.classic.spi.IThrowableProxy; 24 | import ch.qos.logback.classic.spi.LoggingEvent; 25 | import ch.qos.logback.classic.spi.StackTraceElementProxy; 26 | import ch.qos.logback.classic.spi.ThrowableProxyUtil; 27 | import ch.qos.logback.core.CoreConstants; 28 | import ch.qos.logback.core.UnsynchronizedAppenderBase; 29 | import ch.qos.logback.core.encoder.Encoder; 30 | 31 | /** 32 | * ProducerConfig: Default value 33 | * public int packageTimeoutInMS = 3000; 指定被缓存日志的发送超时时间,如果缓存超时,则会被立即发送,单位毫秒 34 | * public int logsCountPerPackage = 4096; 指定每个缓存的日志包中包含日志数量的最大值,取值为1~4096 35 | * public int logsBytesPerPackage = 3145728; 指定每个缓存的日志包的大小上限,取值为1~5242880,单位为字节 36 | * public int memPoolSizeInByte = 104857600; 指定单个Producer实例可以使用的内存的上限,单位字节 37 | * public int retryTimes = 3; 指定发送失败时重试的次数 38 | * public int maxIOThreadSizeInPool = 8; 指定I/O线程池最大线程数量,主要用于发送数据到日志服务 39 | * 40 | * @author 铁生 41 | */ 42 | public class LoghubAppender extends UnsynchronizedAppenderBase { 43 | 44 | private String project; 45 | 46 | private String endpoint; 47 | 48 | private String accessKeyId; 49 | 50 | private String accessKeySecret; 51 | 52 | private String userAgent = "logback"; 53 | 54 | protected Encoder encoder; 55 | 56 | private boolean includeLocation = true; 57 | 58 | private boolean includeMessage = true; 59 | 60 | protected ProducerConfig producerConfig = new ProducerConfig(); 61 | protected ProjectConfig projectConfig; 62 | 63 | protected Producer producer; 64 | 65 | protected String logStore; // 66 | protected String topic = ""; // 67 | protected String source = ""; // 68 | 69 | protected String timeZone = "UTC"; 70 | protected String timeFormat = "yyyy-MM-dd'T'HH:mmZ"; 71 | protected DateTimeFormatter formatter; 72 | 73 | protected java.time.format.DateTimeFormatter formatter1; 74 | private String mdcFields; 75 | private String processor; 76 | 77 | protected int maxThrowable = 500; 78 | 79 | private CredentialsProviderBuilder credentialsProviderBuilder; 80 | private CredentialsProvider credentialsProvider; 81 | 82 | @Override 83 | public void start() { 84 | try { 85 | doStart(); 86 | } catch (Exception e) { 87 | addError("Failed to start LoghubAppender.", e); 88 | } 89 | } 90 | 91 | private void doStart() throws Exception { 92 | try { 93 | formatter = DateTimeFormat.forPattern(timeFormat).withZone(DateTimeZone.forID(timeZone)); 94 | }catch (Exception e){ 95 | formatter1 = java.time.format.DateTimeFormatter.ofPattern(timeFormat).withZone(ZoneId.of(timeZone)); 96 | } 97 | producer = createProducer(); 98 | super.start(); 99 | } 100 | 101 | public Producer createProducer() throws Exception{ 102 | projectConfig = buildProjectConfig(); 103 | Producer producer = new LogProducer(producerConfig); 104 | producer.putProjectConfig(projectConfig); 105 | return producer; 106 | } 107 | 108 | private ProjectConfig buildProjectConfig() throws Exception{ 109 | if (credentialsProviderBuilder != null) { 110 | credentialsProvider = credentialsProviderBuilder.getCredentialsProvider(); 111 | return new ProjectConfig(project, endpoint, credentialsProvider, userAgent); 112 | } 113 | return new ProjectConfig(project, endpoint, accessKeyId, accessKeySecret, null, userAgent); 114 | } 115 | 116 | @Override 117 | public void stop() { 118 | try { 119 | doStop(); 120 | } catch (Exception e) { 121 | addError("Failed to stop LoghubAppender.", e); 122 | } 123 | } 124 | 125 | private void doStop() throws InterruptedException, ProducerException { 126 | if (!isStarted()) { 127 | return; 128 | } 129 | 130 | super.stop(); 131 | producer.close(); 132 | } 133 | 134 | @Override 135 | public void append(E eventObject) { 136 | try { 137 | appendEvent(eventObject); 138 | } catch (Exception e) { 139 | addError("Failed to append event.", e); 140 | } 141 | } 142 | 143 | private void appendEvent(E eventObject) { 144 | //init Event Object 145 | if (!(eventObject instanceof LoggingEvent)) { 146 | return; 147 | } 148 | LoggingEvent event = (LoggingEvent) eventObject; 149 | 150 | List logItems = new ArrayList(); 151 | LogItem item = new LogItem(); 152 | logItems.add(item); 153 | item.SetTime((int) (event.getTimeStamp() / 1000)); 154 | 155 | if(formatter!=null){ 156 | DateTime dateTime = new DateTime(event.getTimeStamp()); 157 | item.PushBack("time", dateTime.toString(formatter)); 158 | }else { 159 | Instant instant = Instant.ofEpochMilli(event.getTimeStamp()); 160 | item.PushBack("time", formatter1.format(instant)); 161 | } 162 | 163 | item.PushBack("level", event.getLevel().toString()); 164 | item.PushBack("thread", event.getThreadName()); 165 | 166 | if (this.includeLocation) { 167 | StackTraceElement[] caller = event.getCallerData(); 168 | if (caller != null && caller.length > 0) { 169 | item.PushBack("location", caller[0].toString()); 170 | } 171 | } 172 | 173 | if (this.encoder == null || this.includeMessage) { 174 | String message = event.getFormattedMessage(); 175 | item.PushBack("message", message); 176 | } 177 | if (this.encoder != null) { 178 | item.PushBack("log", new String(this.encoder.encode(eventObject))); 179 | } 180 | 181 | IThrowableProxy throwableProxy = event.getThrowableProxy(); 182 | if (throwableProxy != null) { 183 | StringBuilder throwable = new StringBuilder(this.getExceptionInfo(throwableProxy)); 184 | 185 | do { 186 | throwable.append(this.fullDump(throwableProxy.getStackTraceElementProxyArray())); 187 | throwableProxy = throwableProxy.getCause(); 188 | if (throwableProxy != null) { 189 | throwable.append("\n\nCaused by:") 190 | .append(this.getExceptionInfo(throwableProxy)); 191 | } 192 | } while (throwableProxy != null); 193 | String throwableSub; 194 | if (throwable.length() > maxThrowable) { 195 | throwableSub = throwable.substring(0, maxThrowable); 196 | } else { 197 | throwableSub = throwable.toString(); 198 | } 199 | item.PushBack("throwable", throwableSub); 200 | } 201 | 202 | // mdcFields can be "*" or format of "fieldA,FieldB,fieldC" 203 | if (mdcFields != null && mdcFields.trim().equals("*")) { // "*" matches all fields, add all fields to item 204 | event.getMDCPropertyMap().entrySet().forEach(e -> item.PushBack(e.getKey(), e.getValue())); 205 | } else { 206 | Optional.ofNullable(mdcFields).ifPresent( 207 | f -> event.getMDCPropertyMap().entrySet().stream() 208 | .filter(v -> Arrays.stream(f.split(",")).anyMatch(i -> i.equals(v.getKey()))) 209 | .forEach(map -> item.PushBack(map.getKey(), map.getValue())) 210 | ); 211 | } 212 | 213 | long ddlInMs = System.currentTimeMillis() + getMaxBlockMs(); 214 | do { 215 | try { 216 | producer.send(projectConfig.getProject(), logStore, topic, source, logItems, new LoghubAppenderCallback(this, projectConfig.getProject(), logStore, topic, source, logItems)); 217 | break; 218 | } catch (InterruptedException e) { 219 | continue; 220 | } catch (Exception e) { 221 | break; 222 | } 223 | } while (System.currentTimeMillis() < ddlInMs); 224 | 225 | } 226 | 227 | public String getTimeFormat() { 228 | return timeFormat; 229 | } 230 | 231 | public void setTimeFormat(String timeFormat) { 232 | this.timeFormat = timeFormat; 233 | } 234 | 235 | private String getExceptionInfo(IThrowableProxy iThrowableProxy) { 236 | String s = iThrowableProxy.getClassName(); 237 | String message = iThrowableProxy.getMessage(); 238 | return (message != null) ? (s + ": " + message) : s; 239 | } 240 | 241 | private String fullDump(StackTraceElementProxy[] stackTraceElementProxyArray) { 242 | StringBuilder builder = new StringBuilder(); 243 | for (StackTraceElementProxy step : stackTraceElementProxyArray) { 244 | builder.append(CoreConstants.LINE_SEPARATOR); 245 | String string = step.toString(); 246 | builder.append(CoreConstants.TAB).append(string); 247 | ThrowableProxyUtil.subjoinPackagingData(builder, step); 248 | } 249 | return builder.toString(); 250 | } 251 | 252 | public String getLogStore() { 253 | return logStore; 254 | } 255 | 256 | public void setLogStore(String logStore) { 257 | this.logStore = logStore; 258 | } 259 | 260 | public String getTopic() { 261 | return topic; 262 | } 263 | 264 | public void setTopic(String topic) { 265 | this.topic = topic; 266 | } 267 | 268 | public String getSource() { 269 | return source; 270 | } 271 | 272 | public void setSource(String source) { 273 | this.source = source; 274 | } 275 | 276 | public String getTimeZone() { 277 | return timeZone; 278 | } 279 | 280 | public void setTimeZone(String timeZone) { 281 | this.timeZone = timeZone; 282 | } 283 | 284 | public int getMaxThrowable() { 285 | return maxThrowable; 286 | } 287 | 288 | public void setMaxThrowable(int maxThrowable) { 289 | this.maxThrowable = maxThrowable; 290 | } 291 | 292 | // **** ==- ProjectConfig -== ********************** 293 | public String getProject() { 294 | return project; 295 | } 296 | 297 | public void setProject(String project) { 298 | this.project = project; 299 | } 300 | 301 | public String getEndpoint() { 302 | return endpoint; 303 | } 304 | 305 | public void setEndpoint(String endpoint) { 306 | this.endpoint = endpoint; 307 | } 308 | 309 | public String getAccessKeyId() { 310 | return accessKeyId; 311 | } 312 | 313 | public void setAccessKeyId(String accessKeyId) { 314 | this.accessKeyId = accessKeyId; 315 | } 316 | 317 | public String getAccessKeySecret() { 318 | return accessKeySecret; 319 | } 320 | 321 | public void setAccessKeySecret(String accessKeySecret) { 322 | this.accessKeySecret = accessKeySecret; 323 | } 324 | 325 | public String getUserAgent() { 326 | return userAgent; 327 | } 328 | 329 | public void setUserAgent(String userAgent) { 330 | this.userAgent = userAgent; 331 | } 332 | 333 | public int getTotalSizeInBytes() { 334 | return producerConfig.getTotalSizeInBytes(); 335 | } 336 | 337 | public void setTotalSizeInBytes(int totalSizeInBytes) { 338 | producerConfig.setTotalSizeInBytes(totalSizeInBytes); 339 | } 340 | 341 | public long getMaxBlockMs() { 342 | return producerConfig.getMaxBlockMs(); 343 | } 344 | 345 | public void setMaxBlockMs(long maxBlockMs) { 346 | producerConfig.setMaxBlockMs(maxBlockMs); 347 | } 348 | 349 | public int getIoThreadCount() { 350 | return producerConfig.getIoThreadCount(); 351 | } 352 | 353 | public void setIoThreadCount(int ioThreadCount) { 354 | producerConfig.setIoThreadCount(ioThreadCount); 355 | } 356 | 357 | public int getBatchSizeThresholdInBytes() { 358 | return producerConfig.getBatchSizeThresholdInBytes(); 359 | } 360 | 361 | public void setBatchSizeThresholdInBytes(int batchSizeThresholdInBytes) { 362 | producerConfig.setBatchSizeThresholdInBytes(batchSizeThresholdInBytes); 363 | } 364 | 365 | public int getBatchCountThreshold() { 366 | return producerConfig.getBatchCountThreshold(); 367 | } 368 | 369 | public void setBatchCountThreshold(int batchCountThreshold) { 370 | producerConfig.setBatchCountThreshold(batchCountThreshold); 371 | } 372 | 373 | public int getLingerMs() { 374 | return producerConfig.getLingerMs(); 375 | } 376 | 377 | public void setLingerMs(int lingerMs) { 378 | producerConfig.setLingerMs(lingerMs); 379 | } 380 | 381 | public int getRetries() { 382 | return producerConfig.getRetries(); 383 | } 384 | 385 | public void setRetries(int retries) { 386 | producerConfig.setRetries(retries); 387 | } 388 | 389 | public int getMaxReservedAttempts() { 390 | return producerConfig.getMaxReservedAttempts(); 391 | } 392 | 393 | public void setMaxReservedAttempts(int maxReservedAttempts) { 394 | producerConfig.setMaxReservedAttempts(maxReservedAttempts); 395 | } 396 | 397 | public long getBaseRetryBackoffMs() { 398 | return producerConfig.getBaseRetryBackoffMs(); 399 | } 400 | 401 | public void setBaseRetryBackoffMs(long baseRetryBackoffMs) { 402 | producerConfig.setBaseRetryBackoffMs(baseRetryBackoffMs); 403 | } 404 | 405 | public long getMaxRetryBackoffMs() { 406 | return producerConfig.getMaxRetryBackoffMs(); 407 | } 408 | 409 | public void setMaxRetryBackoffMs(long maxRetryBackoffMs) { 410 | producerConfig.setMaxRetryBackoffMs(maxRetryBackoffMs); 411 | } 412 | 413 | public Encoder getEncoder() { 414 | return encoder; 415 | } 416 | 417 | public void setEncoder(Encoder encoder) { 418 | this.encoder = encoder; 419 | } 420 | 421 | public void setMdcFields(String mdcFields) { 422 | this.mdcFields = mdcFields; 423 | } 424 | 425 | public boolean getIncludeLocation() { 426 | return this.includeLocation; 427 | } 428 | 429 | public void setIncludeLocation(boolean includeLocation) { 430 | this.includeLocation = includeLocation; 431 | } 432 | 433 | public boolean getIncludeMessage() { 434 | return this.includeMessage; 435 | } 436 | 437 | public void setIncludeMessage(boolean includeMessage) { 438 | this.includeMessage = includeMessage; 439 | } 440 | 441 | public void setCredentialsProviderBuilder(CredentialsProviderBuilder builder) { 442 | this.credentialsProviderBuilder = builder; 443 | } 444 | 445 | public String getProcessor() { 446 | return processor; 447 | } 448 | 449 | public void setProcessor(String processor) { 450 | this.processor = processor; 451 | producerConfig.setProcessor(processor); 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /src/main/java/com/aliyun/openservices/log/logback/LoghubAppenderCallback.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.logback; 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 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by brucewu on 2018/1/5. 11 | */ 12 | public class LoghubAppenderCallback implements Callback { 13 | 14 | protected LoghubAppender loghubAppender; 15 | 16 | protected String project; 17 | 18 | protected String logstore; 19 | 20 | protected String topic; 21 | 22 | protected String source; 23 | 24 | protected List logItems; 25 | 26 | public LoghubAppenderCallback(LoghubAppender loghubAppender, String project, String logstore, String topic, 27 | String source, List logItems) { 28 | super(); 29 | this.loghubAppender = loghubAppender; 30 | this.project = project; 31 | this.logstore = logstore; 32 | this.topic = topic; 33 | this.source = source; 34 | this.logItems = logItems; 35 | } 36 | 37 | @Override 38 | public void onCompletion(Result result) { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/aliyun/openservices/log/logback/example/ExampleCredentialsProviderBuilder.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.logback.example; 2 | 3 | import com.aliyun.openservices.log.common.auth.Credentials; 4 | import com.aliyun.openservices.log.common.auth.CredentialsProvider; 5 | import com.aliyun.openservices.log.common.auth.DefaultCredentials; 6 | import com.aliyun.openservices.log.common.auth.StaticCredentialsProvider; 7 | import com.aliyun.openservices.log.logback.CredentialsProviderBuilder; 8 | 9 | public class ExampleCredentialsProviderBuilder implements CredentialsProviderBuilder { 10 | private String accessKeyId; 11 | private String accessKeySecret; 12 | private String securityToken; 13 | 14 | /** 15 | * getCredentialsProvider should return a new instance of {@link CredentialsProvider} 16 | * for each call. 17 | * 18 | * @return the returned {@link CredentialsProvider} must be thread-safe, 19 | * and cache credentials to avoid update credentials too frequently. 20 | */ 21 | @Override 22 | public CredentialsProvider getCredentialsProvider() throws Exception { 23 | Credentials credentials = new DefaultCredentials(accessKeyId, accessKeySecret, securityToken); 24 | return new StaticCredentialsProvider(credentials); 25 | } 26 | 27 | // Logback uses setter method to inject values that parsed from logback.xml 28 | public void setAccessKeyId(String accessKeyId) { 29 | this.accessKeyId = accessKeyId; 30 | } 31 | 32 | public void setAccessKeySecret(String accessKeySecret) { 33 | this.accessKeySecret = accessKeySecret; 34 | } 35 | 36 | public void setSecurityToken(String securityToken) { 37 | this.securityToken = securityToken; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/aliyun/openservices/log/logback/example/LogbackAppenderExample.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.logback.example; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.slf4j.MDC; 6 | 7 | /** 8 | * Created by brucewu on 2018/1/8. 9 | */ 10 | public class LogbackAppenderExample { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(LogbackAppenderExample.class); 13 | 14 | public static void main(String[] args) { 15 | 16 | MDC.put("MDC_KEY","MDC_VALUE"); 17 | MDC.put("THREAD_ID", String.valueOf(Thread.currentThread().getId())); 18 | 19 | LOGGER.trace("trace log"); 20 | LOGGER.debug("debug log"); 21 | LOGGER.info("info log"); 22 | LOGGER.warn("warn log"); 23 | LOGGER.error("error log"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/logback-example.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | test-proj 14 | store1 15 | 16 | 17 | 18 | topic1 19 | source1 20 | 21 | 22 | 104857600 23 | 60000 24 | 8 25 | 524288 26 | 4096 27 | 2000 28 | 10 29 | 100 30 | 100 31 | 32 | 33 | WARN 34 | 35 | 36 | THREAD_ID,MDC_KEY 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | test-proj 48 | store2 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | topic2 59 | source2 60 | 61 | 62 | 104857600 63 | 0 64 | 8 65 | 524288 66 | 4096 67 | 2000 68 | 10 69 | 100 70 | 50000 71 | 72 | 73 | 74 | %d %-5level [%thread] %logger{0}: %msg 75 | 76 | 77 | 78 | yyyy-MM-dd'T'HH:mmZ 79 | 80 | Asia/Shanghai 81 | 82 | true 83 | 85 | true 86 | 87 | 88 | INFO 89 | ACCEPT 90 | DENY 91 | 92 | 93 | THREAD_ID,MDC_KEY 94 | 95 | 500 96 | 97 | 98 | 99 | 101 | 102 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg %X{THREAD_ID} %n 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/test/java/com/aliyun/openservices/log/logback/TestAppender.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.openservices.log.logback; 2 | 3 | import ch.qos.logback.classic.LoggerContext; 4 | import ch.qos.logback.core.status.Status; 5 | import ch.qos.logback.core.status.StatusManager; 6 | import com.aliyun.openservices.aliyun.log.producer.ProducerConfig; 7 | import org.junit.AfterClass; 8 | import org.junit.Test; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.slf4j.MDC; 12 | 13 | import static org.junit.Assert.assertNotEquals; 14 | 15 | import java.util.List; 16 | 17 | public class TestAppender { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(TestAppender.class); 20 | 21 | private static void sleep() { 22 | ProducerConfig producerConfig = new ProducerConfig(); 23 | try { 24 | Thread.sleep(2 * producerConfig.getLingerMs()); 25 | } catch (InterruptedException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | 30 | @AfterClass 31 | public static void checkStatusList() { 32 | sleep(); 33 | LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); 34 | StatusManager statusManager = lc.getStatusManager(); 35 | List statusList = statusManager.getCopyOfStatusList(); 36 | for (Status status : statusList) { 37 | int level = status.getLevel(); 38 | assertNotEquals(status.getMessage(), Status.ERROR, level); 39 | assertNotEquals(status.getMessage(), Status.WARN, level); 40 | } 41 | } 42 | 43 | @Test 44 | public void testLogCommonMessage() { 45 | LOGGER.warn("This is a test common message logged by logback."); 46 | } 47 | 48 | @Test 49 | public void testLogThrowable() { 50 | MDC.put("MDC_KEY","MDC_VALUE"); 51 | MDC.put("THREAD_ID", String.valueOf(Thread.currentThread().getId())); 52 | LOGGER.error("This is a test error message logged by logback.", 53 | new UnsupportedOperationException("Logback UnsupportedOperationException")); 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ${endpoint} 9 | ${accessKeyId} 10 | ${accessKeySecret} 11 | 12 | 13 | ${project} 14 | ${logStore} 15 | 16 | 17 | 18 | 19 | ${accessKeyId} 20 | ${accessKeySecret} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 104857600 28 | 0 29 | 8 30 | 524288 31 | 4096 32 | 2000 33 | 10 34 | 100 35 | 50000 36 | 37 | 38 | yyyy-MM-dd'T'HH:mmZ 39 | GMT+08 40 | 41 | 42 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg 43 | 44 | 45 | THREAD_ID,MDC_KEY 46 | 47 | 49 | true 50 | 51 | 500 52 | 53 | 54 | 57 | 58 | 60 | 61 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg %n 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | --------------------------------------------------------------------------------